在项目中用到了可拖拽控件,在网上看了几篇相关的文章,发现有的方案存在瑕疵,于是进行了简单的修改,看代码
private int lastX = 0;
private int lastY = 0; //手指在屏幕上的坐标
private boolean isDraged = false; //View是否被移动过
private boolean isDrag = false; //判断是拖动还是点击
private class DragViewOnTouchListener implements OnTouchListener{
@Override
public boolean onTouch(View v, MotionEvent event) {
int parentRight = ((ViewGroup)v.getParent()).getWidth();
int parentBottom = ((ViewGroup)v.getParent()).getHeight();
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
isDrag = false;
isDraged = false;
lastX = (int) event.getRawX();
lastY = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
int dx = (int) event.getRawX()-lastX;
int dy = (int) event.getRawY()-lastY; //手指在屏幕上移动的距离
if (isDraged){
isDrag = true; //如果已经被拖动过,那么无论本次移动的距离是否为零,都判定本次事件为拖动事件
}else{
if (dx == 0 && dy == 0){
isDraged = false; //如果移动的距离为零,则认为控件没有被拖动过,灵敏度可以自己控制
}else{
isDraged = true;
isDrag = true;
}
}
int l = v.getLeft()+dx;
int b = v.getBottom()+dy;
int r = v.getRight()+dx;
int t = v.getTop()+dy;
if(l < 0){//处理按钮被移动到父布局的上下左右四个边缘时的情况,防止控件被拖出父布局
l = 0;
r = l + v.getWidth();
}
if(t < 0){
t = 0;
b = t + v.getHeight();
}
if(r > parentRight){
r = parentRight;
l = r - v.getWidth();
}
if(b > parentBottom){
b = parentBottom;
t = b - v.getHeight();
}
lastX = (int) event.getRawX();
lastY = (int) event.getRawY();
v.layout(l, t, r, b);
v.postInvalidate(); //其他view刷新时,会导致view回到原点,可以用设置LayoutParams的方式代替
break;
}
return isDrag; //如果没有给view设置点击事件,需返回true,否则不会响应ACTION_MOVE,导致view不会被拖动
}
}
思路很简单,实现OnTouchListener,在move过程中重绘view。但是如果onTouch方法返回true,就会消费掉本次事件,导致即使是点击事件,onClick事件也不再响应,如果返回false,那么在对控件进行拖动的同时,也会响应onClick事件,所以我们需要一个标志位,就是代码中的isDrag。
private boolean isDrag = false; //判断是拖动还是点击
但是在实现过程中,发现了另外一个问题,如果只是点击,onTouch中的ACTION_MOVE也会触发,导致onClick事件不响应,所以我们需要另外一个标志位,isDraged。
private boolean isDraged = false; //View是否被移动过
下面这段代码为判断逻辑
if (isDraged){
isDrag = true; //如果已经被拖动过,那么无论本次移动的距离是否为零,都判定本次事件为拖动事件
}else{
if (dx == 0 && dy == 0){
isDraged = false; //如果移动的距离为零,则认为控件没有被拖动过,灵敏度可以自己控制
}else{
isDraged = true;
isDrag = true;
}
}
如果拖动过,那么则认为本次事件为拖动事件,不需要再判断移动距离(移动距离为0时,也会触发ACTION_MOVE),也就是说,从按下到抬起,中间的过程如果有拖动,那么之后都不再根据move的距离来判定是不是拖动事件。
相反,如果从按下之后到本次ACTION_MOVE事件触发之前,还没有拖动过,那么再根据move的距离进行判断。
这么做主要是为了应对两种情况:
1,就是上边提到的,单纯的点击事件也会触发ACTION_MOVE
2,拖动控件后不动,触发ACTION_MOVE之后,移动距离为零
最后,onTouch方法,再将是否是拖动事件的标志位,也就是isDrag返回就可以了。
另外,说一下下边这个方法
v.layout(l, t, r, b);
该方法是指在父布局中的位置,也就是说
v.layout(0,0,v.getWidth(),v.getBottom());
会布局到父布局的左上角,所以,想要不被拖出父布局,那么四个参数的取值范围如下
left : 0 到 parent.getWidth() - v.getWidth()
top : 0 到 parent.getHeight() - v.getHeight()
right : v.getWidth() 到 parent.getWidth()
bootom : v.getHeight() 到 parent.getHeight()