前段时间有需求要做一个活动,入口是一个悬浮可拖拽的按钮。如果只是一个可拖拽的View
也好办,搜文章也能搜到很多自定义可拖动的View
,而且项目中也有一个自定义可拖拽的ImageView
。但是现在需求是这个按钮可以关闭,多加了一个关闭按钮,那只能用ViewGroup
了,最后我自定义了一个LinearLayout
第一个碰到的问题,是要处在LinearLayout
中的活动图片和关闭按钮,防止在移动的时候触发点击事件。根据事件分发机制我们要在onInterceptTouchEvent()
方法里做一下判断是否要进行事件拦截,如果是处在MotionEvent.ACTION_MOVE
中就进行拦截
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { int action = ev.getAction() & MotionEvent.ACTION_MASK; switch (action) { case MotionEvent.ACTION_DOWN: mLastTouchX = ev.getX(); mLastTouchY= ev.getY(); return false; case MotionEvent.ACTION_MOVE: mMoveX = ev.getX(); mMoveY = ev.getY(); //移动很小的一段距离也视为点击 if(Math.abs(mMoveX - mLastTouchX) < 5 || Math.abs(mMoveY - mLastTouchY) < 5) //不进行事件拦截 return false; else return true; } return false; }复制代码
第二个碰到的问题,选择在拖动自定义LinearLayout控件过程中用哪些方法来移动。因为是根据项目中可拖动自定义ImageView
来做的,这个控件中是用了layout(l,t,r,b)
在拖动过程中重新布局,但是有一个问题,在有banner或者下拉刷新、上拉加载的这些页面中,拖动之后只要banner进行轮播或者上拉加载数据的时候会重走onLayout(boolean changed, int l, int t, int r, int b)
方法,这个时候传过来的参数是控件的起始位置,所以又回到了原点。
本来我想在onLayout()
方法中用layout()
方法,传的参数为移动后的位置,但是有个问题,可以点开layout()
,这个方法最后走的还是onLayout()
所以就造成死循环了。这是个死结,后来我还想着在onLayout()
进行判断,如果是拖动后手势抬起就不再走layout()
,加上判断之后果然是可行的,但是后来测试的时候我来回切Fragment
回到页面中的时候死循环又开始了。。。具体什么原因我也没弄很明白。
在onLayout()
方法中用layout()
重新布局是不可行的了,解决这个bug的时候把同事拉过来看了一下这个问题,同事说可以用其他的方式移动,试了几种方法最后用了offsetTopAndBottom/offsetLeftAndRight
完美解决。
最后一个问题,就是拖动之后,要进行吸附在左右两侧,并且控件要限制在屏幕内不要越界,这个还比较好解决的。解决这个问题前要记住getTop()、getRight()、getBottom()、getLeft()
这个四个方法分别是控件顶部到屏幕顶部的距离、控件右边界到屏幕左边界的距离、控件底部到屏幕顶部的距离、控件左边界到屏幕左边界的距离。
思路就是在MotionEvent.ACTION_UP
中判断,获取一下当前的相对位置,如果超过了屏幕的1/2说明吸附屏幕右侧,那么再移动控件右边界到屏幕右边界的距离,就是屏幕宽度减去getRight()
,同理吸附左侧是一样的。限制超过上下边界我是在拖动后手势抬起后,把超过的那一部分再滑动回来这样解决的。或者你可以在move过程中直接限制控件不超过屏幕。完整代码如下
package com.dudou.demo;import android.content.Context;import android.support.annotation.Nullable;import android.support.v4.view.MotionEventCompat;import android.util.AttributeSet;import android.view.MotionEvent;import android.widget.LinearLayout;public class DragLinearLayout extends LinearLayout { private float mLastTouchX = 0; private float mLastTouchY= 0; private float mMoveX = 0; private float mMoveY = 0; private float mLeft; private float mTop; private float mRight; private float mBottom; private int mDx; private int mDy; private boolean isLeft = false; boolean moveRight = false; boolean moveLeft = false; //屏幕宽度 private static final int screenWidth = ScreenUtil.getScreenWidth(); //屏幕高度 private static final int screenHeight = ScreenUtil.getScreenHeight() public TouchLinearLayout(Context context) { super(context); } public TouchLinearLayout(Context context, @Nullable AttributeSet attrs) { super(context, attrs); } public TouchLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { int action = ev.getAction() & MotionEvent.ACTION_MASK; switch (action) { case MotionEvent.ACTION_DOWN: mLastTouchX = ev.getX(); mLastTouchY= ev.getY(); return false; case MotionEvent.ACTION_MOVE: mMoveX = ev.getX(); mMoveY = ev.getY(); //移动很小的一段距离也视为点击 if(Math.abs(mMoveX - mLastTouchX) < 5 || Math.abs(mMoveY - mLastTouchY) < 5) //不进行事件拦截 return false; else return true; } return false; } @Override public boolean onTouchEvent(MotionEvent ev) { final int action = MotionEventCompat.getActionMasked(ev); switch (action) { case MotionEvent.ACTION_DOWN: { break; } case MotionEvent.ACTION_MOVE: { moveRight = false; moveLeft = false; final float x = ev.getX(); final float y = ev.getY(); final float dx = x - mLastTouchX; final float dy = y - mLastTouchY; mLeft = getLeft() + dx; mTop = getTop() + dy; mRight = getRight() + dx; mBottom = getBottom() + dy; if(mLeft < 0){ moveLeft = true; mLeft = 0; mRight = mLeft + getWidth(); } if(mRight > screenWidth){ moveRight = true; mRight = screenWidth; mLeft = mRight - getWidth(); } if(mTop < 0){ mTop = 0; mBottom = mTop + getHeight(); } if(mBottom > screenHeight){ mBottom = screenHeight; mTop = mBottom - getHeight(); } mDx += dx; mDy += dy; offsetLeftAndRight((int)dx); offsetTopAndBottom((int)dy); if(moveLeft){ offsetLeftAndRight(-getLeft()); } if(moveRight){ offsetLeftAndRight(screenWidth-getRight()); } break; } case MotionEvent.ACTION_UP: { int upX = (int) ev.getRawX(); if (upX > (screenWidth / 2)) { isLeft = false; offsetLeftAndRight(screenWidth-getRight()); invalidate(); } else { isLeft = true; offsetLeftAndRight(-getLeft()); invalidate(); } if(getTop()<0){ mDy += -getTop(); offsetTopAndBottom(-getTop()); } if(getBottom()>screenHeight){ mDy += screenHeight-getBottom(); offsetTopAndBottom(screenHeight-getBottom()); } break; } case MotionEvent.ACTION_CANCEL: { break; } case MotionEvent.ACTION_POINTER_UP: { break; } } return true; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); offsetTopAndBottom(mDy); if(isLeft){ offsetLeftAndRight(-getLeft()); }else { offsetLeftAndRight(screenWidth-getRight()); } }}复制代码