先上图
- 这个效果和IOS的Assistive Touch效果类似,可以实现全局拖拽,设置点击事件,并且点击和拖拽不冲突。
- Demo的GitHub地址:https://github.com/icecreammmm/DragView
使用方法
- FloatTouchListener里传入的第二个为父布局的Layout需要设置为FrameLayout。
第三个参数,也就是我们需要拖拽的View,需要给这个View外面包上一层FrameLayout,即可实现我们所需要的效果。
private void setTouchListener() {
margin = (int) (10 * getResources().getDisplayMetrics().density + 0.5f);
mFloatTouchListener = new FloatTouchListener(this, flParent, flChild, margin);
flChild.setOnTouchListener(mFloatTouchListener);
flChild.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(MainActivity.this, "点击事件", Toast.LENGTH_SHORT).show();
}
});
}
实现方法
从事件分发机制中我们知道,就优先级而言:onTouchListener>onClickListenr。
上面的拖拽事件已经消费了onTouchListener(即onTouch方法中返回true),那么就不会下发到onClickListenr,自然就不会产生点击事件。
也许你想让onTouchListener不消费,然后不就下发到onClickListenr了么?
确实这样可以实现点击事件,但是拖拽功能又实现不了了。
- 通过上面的分析,最终的解决办法就是:
onTouch方法中,在接收到ACTION_DOWN后,返回false,交给onClickListenr处理。剩下的ACTION_MOVE/ACTION_UP等事件,返回true,交给onTouchListener处理。这样自然就可以既实现拖拽效果又实现点击效果了。
public class FloatTouchListener implements View.OnTouchListener {
private View mParentView;
private View mFloatView;
private FrameLayout.LayoutParams mFloatViewWindowParam;
private float mPreviousX = -1;
private float mPreviousY = -1;
private boolean mHasMoved = false;
private int mTouchSlop;
private int mDownPointerId = -1;
private Interpolator mInterpolator;
private FloatAnimatorUpdateListener mUpdateListener;
private int mMargin;
public FloatTouchListener(Context context, View parentView, View floatView, int margin) {
mParentView = parentView;
mFloatView = floatView;
mFloatViewWindowParam = (FrameLayout.LayoutParams) floatView.getLayoutParams();
mInterpolator = new DecelerateInterpolator();
ViewConfiguration configuration = ViewConfiguration.get(context);
mTouchSlop = configuration.getScaledTouchSlop();
mMargin = margin;
}
private boolean adjustMarginParams(View v, MotionEvent event) {
float x = event.getX();
float y = event.getY();
float deltaX = x - mPreviousX;
float deltaY = y - mPreviousY;
if (!mHasMoved) {
if (Math.abs(deltaX) < mTouchSlop && Math.abs(deltaY) < mTouchSlop) {
return false;
}
}
if ((mFloatViewWindowParam.gravity & Gravity.BOTTOM) == Gravity.BOTTOM) {
mFloatViewWindowParam.topMargin = mParentView.getBottom() - mMargin - mFloatView
.getHeight();
}
if ((mFloatViewWindowParam.gravity & Gravity.RIGHT) == Gravity.RIGHT) {
mFloatViewWindowParam.leftMargin = mParentView.getRight() - mMargin - mFloatView
.getWidth();
}
mFloatViewWindowParam.gravity = Gravity.NO_GRAVITY;
//左上角位置
int newX = (int) (mFloatViewWindowParam.leftMargin + deltaX);
int newY = (int) (mFloatViewWindowParam.topMargin + deltaY);
newX = Math.max(newX, mParentView.getLeft() + mMargin);
newX = Math.min(newX, mParentView.getRight() - mMargin - mFloatView.getWidth());
newY = Math.max(newY, mParentView.getTop() + mMargin);
newY = Math.min(newY, mParentView.getBottom() - mMargin - mFloatView.getHeight());
mFloatViewWindowParam.leftMargin = newX;
mFloatViewWindowParam.topMargin = newY;
return true;
}
@Override
public boolean onTouch(View view, MotionEvent event) {
int action = MotionEventCompat.getActionMasked(event);
boolean result = false;
switch (action) {
case MotionEvent.ACTION_DOWN: {
mDownPointerId = MotionEventCompat.getPointerId(event, 0);
mPreviousX = event.getX();
mPreviousY = event.getY();
break;
}
case MotionEvent.ACTION_MOVE: {
if (mDownPointerId >= 0) {
int index = MotionEventCompat.getActionIndex(event);
int id = MotionEventCompat.getPointerId(event, index);
if (id == mDownPointerId) {
boolean update = adjustMarginParams(view, event);
if (!update) {
break;
}
mFloatView.requestLayout();
mHasMoved = true;
result = true;
}
}
break;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
if (mDownPointerId >= 0 && mHasMoved) {
event.setAction(MotionEvent.ACTION_CANCEL);
adjustMarginParams(view, event);
mFloatView.requestLayout();
int center = (mParentView.getWidth() - mFloatView.getWidth()) / 2;
int x = mFloatViewWindowParam.leftMargin;
int destX = 0;
if (x < center) {
destX = mParentView.getLeft() + mMargin;
} else {
destX = mParentView.getRight() - mMargin - mFloatView.getWidth();
}
int deltaHorizon = destX - x;
if (Math.abs(deltaHorizon) < 100) {
mFloatViewWindowParam.leftMargin = destX;
mFloatView.requestLayout();
} else {
ValueAnimator animator = ValueAnimator.ofInt(x, destX);
animator.setInterpolator(mInterpolator);
if (mUpdateListener == null) {
mUpdateListener = new FloatAnimatorUpdateListener();
mUpdateListener.setUpdateView(FloatTouchListener.this);
}
animator.addUpdateListener(mUpdateListener);
animator.setDuration(300);
animator.start();
}
}
resetStatus();
break;
}
default:
break;
}
return result;
}
private void resetStatus() {
mDownPointerId = -1;
mPreviousX = -1;
mPreviousY = -1;
mHasMoved = false;
}
private class FloatAnimatorUpdateListener implements ValueAnimator.AnimatorUpdateListener {
private WeakReference mListener;
public void setUpdateView(FloatTouchListener listener) {
mListener = new WeakReference(listener);
}
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Integer value = (Integer) animation.getAnimatedValue();
FloatTouchListener listener = null;
if (mListener == null || (listener = mListener.get()) == null) {
return;
}
listener.mFloatViewWindowParam.leftMargin = value;
mFloatView.requestLayout();
}
}
}
注:文章参考Ruheng写的:https://www.jianshu.com/p/cc4d3c53d476,并加以修改,更易于使用。