IBigerBiger的成长之路

ViewDragerHelper使用与源码简析

ViewDrager是什么呢?

看看官方的解释其实就明白了

1
2
3
4
5
/**
* ViewDragHelper is a utility class for writing custom ViewGroups. It offers a number
* of useful operations and state tracking for allowing a user to drag and reposition
* views within their parent ViewGroup.
*/

从这段解释呢我们可以看到ViewDrager是简化view拖拽操作的帮助类,ViewDragHelper解决了android中手势处理过于复杂的问题。

ViewDragHelper是作用在一个ViewGroup上,也就是说他不能直接作用到被拖拽的view, 其实这也很好理解,因为view在布局中的位置是父ViewGroup决定的。

简单实现


接下来就实现一个内部View随意拖动的demo

分步来完成

1.自定义一个ViewGroup

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class VDHLayout extends LinearLayout{
public VDHLayout(Context context) {
this(context, null);
}
public VDHLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public VDHLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
}

2.创建ViewDragerHelper实例以及实现ViewDragHelper.CallCack相关方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private ViewDragHelper viewDragHelper;
private void initViewDragHelper(){
viewDragHelper = ViewDragHelper.create(this,myCallBack);
viewDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_ALL);
}
ViewDragHelper.Callback myCallBack = new ViewDragHelper.Callback() {
@Override
public boolean tryCaptureView(View child, int pointerId)
{
return true;
}
@Override
public int clampViewPositionHorizontal(View child, int left, int dx)
{
return left;
}
@Override
public int clampViewPositionVertical(View child, int top, int dy)
{
return top;
}
};

3.触摸相关方法

1
2
3
4
5
6
7
8
9
10
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return viewDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
viewDragHelper.processTouchEvent(event);
return true;
}

布局内容就不贴上来了

最后实现的效果如下


图1 ViewDragerHelper简单demo

是不是很神奇,简单的几行代码就实现了这样的效果了,至于为什么会实现呢?

这个到下一篇再从源码角度进行说明

接下来我们看看ViewDragHelper.CallCack里的方法,这里面的方法则是ViewDragHelper实现不同效果和功能的主要地方

ViewDragHelper.CallCack


ViewDragHelper.CallCack里面的方法很多,分别介绍

  • onViewDragStateChanged(int state)

    当View的拖拽状态改变时的回调
    STATE_IDLE: 当前未被拖拽
    STATE_DRAGGING:正在被拖拽
    STATE_SETTLING: 被拖拽后需要被安放到一个位置中的状态

  • onViewPositionChanged(View changedView, int left, int top, int dx, int dy)

    当View拖拽位置发生变化时的回调
    changedView :被拖拽的View
    left : 被拖拽后View的left边缘坐标
    top : 被拖拽后View的top边缘坐标
    dx : 拖动的x偏移量
    dy : 拖动的y偏移量

  • onViewCaptured(View capturedChild, int activePointerId)

    当一个View捕获到准备拖拽时的回调
    capturedChild : 捕获的View
    activePointerId: 对应的PointerId

  • onViewReleased(View releasedChild, float xvel, float yvel)

    当被捕获拖拽View释放的回调
    releasedChild:被释放的View
    xvel: 释放View的x方向上的加速度
    yvel: 释放View的y方向上的加速度

  • onEdgeTouched(int edgeFlags, int pointerId)

    边缘触摸的回调
    edgeFlags : 当前触摸的flag 有: EDGE_LEFT,EDGE_TOP,EDGE_RIGHT,EDGE_BOTTOM
    pointerId : 用来描述边缘触摸操作的id

  • onEdgeLock(int edgeFlags)

    是否锁定该边缘的触摸,默认返回false,返回true表示锁定

  • onEdgeDragStarted(int edgeFlags, int pointerId)

    边缘触摸开始时的回调
    edgeFlags : 当前触摸的flag 有: EDGE_LEFT,EDGE_TOP,EDGE_RIGHT,EDGE_BOTTOM
    pointerId : 用来描述边缘触摸操作的id

  • getOrderedChildIndex(int index)

    如果需要改变子View的遍历查询顺序可改写此方法,例如让下层的View优先于上层的View被选中。

  • tryCaptureView(View child, int pointerId)

    尝试捕获被拖拽的View
    child : 尝试捕获的View
    pointerId: 对应的PointerId

  • getViewHorizontalDragRange(View child)

    获取被拖拽View child 的水平拖拽范围,返回0表示无法被水平拖拽

  • getViewVerticalDragRange(View child)

    获取被拖拽View child 的垂直拖拽范围,返回0表示无法被垂直拖拽

  • clampViewPositionHorizontal(View child, int left, int dx)

    决定拖拽View在水平方向上应该移动到的位置
    child : 被拖拽的View
    left : 期望移动到位置的View的left值
    dx : 移动的水平距离
    返回值 : 直接决定View在水平方向的位置

  • clampViewPositionVertical(View child, int top, int dy)

    决定拖拽View在垂直方向上应该移动到的位置
    child : 被拖拽的View
    top : 期望移动到位置的View的top值
    dy : 移动的垂直距离
    返回值 : 直接决定View在垂直方向的位置

源码简析

看前面的代码我们可以看到在ViewGroup的onInterceptTouchEvent与onTouchEvent中的方法分别调用了ViewDragerHelper的方法,onInterceptTouchEvent与onTouchEvent这里大家应该都清楚了解吧,就不对这个进行更多的介绍了,所以其实ViewDragerHelper之所以能够实现上面的效果是与在onInterceptTouchEvent与onTouchEvent调用的方法有关的

1
2
3
4
5
6
7
8
9
10
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return viewDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
viewDragHelper.processTouchEvent(event);
return true;
}

那么接下来我们看看这两个方法里面到底是做了什么

一.shouldInterceptTouchEvent


直接上源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
public boolean shouldInterceptTouchEvent(MotionEvent ev) {
final int action = MotionEventCompat.getActionMasked(ev);
final int actionIndex = MotionEventCompat.getActionIndex(ev);
if (action == MotionEvent.ACTION_DOWN) {
// Reset things for a new event stream, just in case we didn't get
// the whole previous stream.
cancel();
}
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev);
switch (action) {
case MotionEvent.ACTION_DOWN: {
final float x = ev.getX();
final float y = ev.getY();
final int pointerId = MotionEventCompat.getPointerId(ev, 0);
saveInitialMotion(x, y, pointerId);
final View toCapture = findTopChildUnder((int) x, (int) y);
// Catch a settling view if possible.
if (toCapture == mCapturedView && mDragState == STATE_SETTLING) {
tryCaptureViewForDrag(toCapture, pointerId);
}
final int edgesTouched = mInitialEdgesTouched[pointerId];
if ((edgesTouched & mTrackingEdges) != 0) {
mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId);
}
break;
}
case MotionEventCompat.ACTION_POINTER_DOWN: {
final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex);
final float x = MotionEventCompat.getX(ev, actionIndex);
final float y = MotionEventCompat.getY(ev, actionIndex);
saveInitialMotion(x, y, pointerId);
// A ViewDragHelper can only manipulate one view at a time.
if (mDragState == STATE_IDLE) {
final int edgesTouched = mInitialEdgesTouched[pointerId];
if ((edgesTouched & mTrackingEdges) != 0) {
mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId);
}
} else if (mDragState == STATE_SETTLING) {
// Catch a settling view if possible.
final View toCapture = findTopChildUnder((int) x, (int) y);
if (toCapture == mCapturedView) {
tryCaptureViewForDrag(toCapture, pointerId);
}
}
break;
}
case MotionEvent.ACTION_MOVE: {
if (mInitialMotionX == null || mInitialMotionY == null) break;
// First to cross a touch slop over a draggable view wins. Also report edge drags.
final int pointerCount = MotionEventCompat.getPointerCount(ev);
for (int i = 0; i < pointerCount; i++) {
final int pointerId = MotionEventCompat.getPointerId(ev, i);
// If pointer is invalid then skip the ACTION_MOVE.
if (!isValidPointerForActionMove(pointerId)) continue;
final float x = MotionEventCompat.getX(ev, i);
final float y = MotionEventCompat.getY(ev, i);
final float dx = x - mInitialMotionX[pointerId];
final float dy = y - mInitialMotionY[pointerId];
final View toCapture = findTopChildUnder((int) x, (int) y);
final boolean pastSlop = toCapture != null && checkTouchSlop(toCapture, dx, dy);
if (pastSlop) {
// check the callback's
// getView[Horizontal|Vertical]DragRange methods to know
// if you can move at all along an axis, then see if it
// would clamp to the same value. If you can't move at
// all in every dimension with a nonzero range, bail.
final int oldLeft = toCapture.getLeft();
final int targetLeft = oldLeft + (int) dx;
final int newLeft = mCallback.clampViewPositionHorizontal(toCapture,
targetLeft, (int) dx);
final int oldTop = toCapture.getTop();
final int targetTop = oldTop + (int) dy;
final int newTop = mCallback.clampViewPositionVertical(toCapture, targetTop,
(int) dy);
final int horizontalDragRange = mCallback.getViewHorizontalDragRange(
toCapture);
final int verticalDragRange = mCallback.getViewVerticalDragRange(toCapture);
if ((horizontalDragRange == 0 || horizontalDragRange > 0
&& newLeft == oldLeft) && (verticalDragRange == 0
|| verticalDragRange > 0 && newTop == oldTop)) {
break;
}
}
reportNewEdgeDrags(dx, dy, pointerId);
if (mDragState == STATE_DRAGGING) {
// Callback might have started an edge drag
break;
}
if (pastSlop && tryCaptureViewForDrag(toCapture, pointerId)) {
break;
}
}
saveLastMotion(ev);
break;
}
case MotionEventCompat.ACTION_POINTER_UP: {
final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex);
clearMotionHistory(pointerId);
break;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
cancel();
break;
}
}
return mDragState == STATE_DRAGGING;
}

分成几个部分分析

1.准备工作

1
2
3
4
5
6
7
8
9
10
11
12
13
final int action = MotionEventCompat.getActionMasked(ev);
final int actionIndex = MotionEventCompat.getActionIndex(ev);
if (action == MotionEvent.ACTION_DOWN) {
// Reset things for a new event stream, just in case we didn't get
// the whole previous stream.
cancel();
}
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev);

那这里主要做一些准备工作,

  • 获取action
  • 获取action对应的index
  • 如果是按下的action则重置一些信息
  • 初始化VelocityTracker,VelocityTracker(用于追踪滑动速度)是什么不做介绍,前面的文章有介绍

2.ACTION_DOWN相关解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
case MotionEvent.ACTION_DOWN: {
final float x = ev.getX();
final float y = ev.getY();
final int pointerId = MotionEventCompat.getPointerId(ev, 0);
saveInitialMotion(x, y, pointerId);
final View toCapture = findTopChildUnder((int) x, (int) y);
// Catch a settling view if possible.
if (toCapture == mCapturedView && mDragState == STATE_SETTLING) {
tryCaptureViewForDrag(toCapture, pointerId);
}
final int edgesTouched = mInitialEdgesTouched[pointerId];
if ((edgesTouched & mTrackingEdges) != 0) {
mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId);
}
break;
}

依次介绍里面的做了什么

  • (1).获取按下的x,y位置与获取这个事件对应的pointerId,然后保存(saveInitialMotion)这些信息

    注意pointerId一般情况下只有一个手指触摸时为0,两个手指触摸时第二个手指触摸返回的pointerId为1,以此类推

    看下保存信息这里的方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    private void saveInitialMotion(float x, float y, int pointerId) {
    //确保各个数组的大小足够存放数据
    ensureMotionHistorySizeForId(pointerId);
    //保存x坐标
    mInitialMotionX[pointerId] = mLastMotionX[pointerId] = x;
    //保存y坐标
    mInitialMotionY[pointerId] = mLastMotionY[pointerId] = y;
    //保存是否触摸到边缘
    mInitialEdgesTouched[pointerId] = getEdgesTouched((int) x, (int) y);
    //保存当前id是否在触摸,用于后续验证
    mPointersDown |= 1 << pointerId;
    }
  • (2).获取当前触摸点下最顶层的子View(findTopChildUnder),并捕获(tryCaptureViewForDrag)

    先看下findTopChildUnder方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public View findTopChildUnder(int x, int y) {
    final int childCount = mParentView.getChildCount();
    for (int i = childCount - 1; i >= 0; i--) {
    final View child = mParentView.getChildAt(mCallback.getOrderedChildIndex(i));
    if (x >= child.getLeft() && x < child.getRight() &&
    y >= child.getTop() && y < child.getBottom()) {
    return child;
    }
    }
    return null;
    }

    这里做的就是遍历整个整个ViewGroup的子View通过位置来查找指定的View,这里有个mCallback.getOrderedChildIndex(i),大家应该通过上面一篇文章,对这个方法有点眼熟吧,(如果需要改变子View的遍历查询顺序可改写此方法,例如让下层的View优先于上层的View被选中。)所以我们可以在这里返回指定的View的index

    接下来看一下tryCaptureViewForDrag方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    boolean tryCaptureViewForDrag(View toCapture, int pointerId) {
    //如果已经捕获该View 直接返回true
    if (toCapture == mCapturedView && mActivePointerId == pointerId) {
    // Already done!
    return true;
    }
    //根据mCallback.tryCaptureView()方法来最终决定是否可以捕获View
    if (toCapture != null && mCallback.tryCaptureView(toCapture, pointerId)) {
    mActivePointerId = pointerId;
    captureChildView(toCapture, pointerId);
    return true;
    }
    return false;
    }
    public void captureChildView(View childView, int activePointerId) {
    if (childView.getParent() != mParentView) {
    throw new IllegalArgumentException("captureChildView: parameter must be a descendant " +
    "of the ViewDragHelper's tracked parent view (" + mParentView + ")");
    }
    mCapturedView = childView;
    mActivePointerId = activePointerId;
    mCallback.onViewCaptured(childView, activePointerId);
    setDragState(STATE_DRAGGING);
    }

    代码的注释其实大家大致可以理解是什么意思了吧,主要这里有一个mCallback.tryCaptureView()的方法,而这个tryCaptureView()方法,其实我们可以自己去定义的,所以是否捕获某个子View,其实是我们可以控制的,当可以捕获这个View后就会把这个View赋值给mCapturedView,同时会回调方法mCallback.onViewCaptured()并且设定mDragState的状态为STATE_DRAGGING

*(3). 如果触摸了边缘,回调callback的onEdgeTouched()方法

在第一步保存(saveInitialMotion)信息的时候,保存是否触摸到边缘,这里就直接拿出来判断是否触摸到了边缘,还有一个参数为mTrackingEdges,这个参数其实是我们来设置的

1
viewDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_ALL);

除了EDGE_ALL,还有EDGE_LEFT,EDGE_TOP,EDGE_RIGHT,EDGE_BOTTOM,具体表示什么大家应该一眼明了,

所以当我们设置了这个mTrackingEdges并且触摸到了边缘会回调mCallback.onEdgeTouched()这个方法

3.ACTION_POINTER_DOWN(又有一个手指触摸时)相关解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
case MotionEventCompat.ACTION_POINTER_DOWN: {
final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex);
final float x = MotionEventCompat.getX(ev, actionIndex);
final float y = MotionEventCompat.getY(ev, actionIndex);
saveInitialMotion(x, y, pointerId);
//因为同一时间ViewDragHelper只能操控一个View,所以当有新的手指触摸时
//只讨论当无触摸发生时,回调边缘触摸的callback
//或者正在处于释放状态时重新捕获View
// A ViewDragHelper can only manipulate one view at a time.
if (mDragState == STATE_IDLE) {
final int edgesTouched = mInitialEdgesTouched[pointerId];
if ((edgesTouched & mTrackingEdges) != 0) {
mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId);
}
} else if (mDragState == STATE_SETTLING) {
// Catch a settling view if possible.
final View toCapture = findTopChildUnder((int) x, (int) y);
if (toCapture == mCapturedView) {
tryCaptureViewForDrag(toCapture, pointerId);
}
}
break;
}

这里面的方法在ACTION_DOWN中基本都已经说明过了,就不做过多的解释了

4.ACTION_MOVE相关解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
case MotionEvent.ACTION_MOVE: {
if (mInitialMotionX == null || mInitialMotionY == null) break;
//得到触摸点的数量,并循环处理,只处理第一个发生了拖拽的事件
// First to cross a touch slop over a draggable view wins. Also report edge drags.
final int pointerCount = MotionEventCompat.getPointerCount(ev);
for (int i = 0; i < pointerCount; i++) {
final int pointerId = MotionEventCompat.getPointerId(ev, i);
// If pointer is invalid then skip the ACTION_MOVE.
if (!isValidPointerForActionMove(pointerId)) continue;
final float x = MotionEventCompat.getX(ev, i);
final float y = MotionEventCompat.getY(ev, i);
final float dx = x - mInitialMotionX[pointerId];
final float dy = y - mInitialMotionY[pointerId];
final View toCapture = findTopChildUnder((int) x, (int) y);
final boolean pastSlop = toCapture != null && checkTouchSlop(toCapture, dx, dy);
if (pastSlop) {
// check the callback's
// getView[Horizontal|Vertical]DragRange methods to know
// if you can move at all along an axis, then see if it
// would clamp to the same value. If you can't move at
// all in every dimension with a nonzero range, bail.
final int oldLeft = toCapture.getLeft();
final int targetLeft = oldLeft + (int) dx;
final int newLeft = mCallback.clampViewPositionHorizontal(toCapture,
targetLeft, (int) dx);
final int oldTop = toCapture.getTop();
final int targetTop = oldTop + (int) dy;
final int newTop = mCallback.clampViewPositionVertical(toCapture, targetTop,
(int) dy);
final int horizontalDragRange = mCallback.getViewHorizontalDragRange(
toCapture);
final int verticalDragRange = mCallback.getViewVerticalDragRange(toCapture);
if ((horizontalDragRange == 0 || horizontalDragRange > 0
&& newLeft == oldLeft) && (verticalDragRange == 0
|| verticalDragRange > 0 && newTop == oldTop)) {
break;
}
}
reportNewEdgeDrags(dx, dy, pointerId);
if (mDragState == STATE_DRAGGING) {
// Callback might have started an edge drag
break;
}
if (pastSlop && tryCaptureViewForDrag(toCapture, pointerId)) {
break;
}
}
saveLastMotion(ev);
break;
}

依次介绍里面的做了什么

  • (1). 获取当前触摸点下最顶层的子View(findTopChildUnder),判断是否产生拖动(checkTouchSlop)

    findTopChildUnder上面已经做过介绍了,那对checkTouchSlop进行分析

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    private boolean checkTouchSlop(View child, float dx, float dy) {
    if (child == null) {
    return false;
    }
    final boolean checkHorizontal = mCallback.getViewHorizontalDragRange(child) > 0;
    final boolean checkVertical = mCallback.getViewVerticalDragRange(child) > 0;
    if (checkHorizontal && checkVertical) {
    return dx * dx + dy * dy > mTouchSlop * mTouchSlop;
    } else if (checkHorizontal) {
    return Math.abs(dx) > mTouchSlop;
    } else if (checkVertical) {
    return Math.abs(dy) > mTouchSlop;
    }
    return false;
    }

    根据mTouchSlop最小拖动的距离来判断是否属于拖动,mTouchSlop根据我们设定的灵敏度决定,同时我们可以看到这里有获取mCallback.getViewHorizontalDragRange(child)mCallback.getViewVerticalDragRange(child)的值,而这个值则是我们通过Callback设置的

  • (2).根据callback的四个方法
    getView[Horizontal|Vertical]DragRange和clampViewPosition[Horizontal|Vertical]来检查是否可以拖动

  • (3).记录并回调是否有边缘触摸(reportNewEdgeDrags)

  • (4).保存触摸点的信息

5.剩余部分

1
2
3
4
5
6
7
8
9
10
11
12
13
//当有一个手指抬起时,清除这个手指的触摸数据
case MotionEventCompat.ACTION_POINTER_UP: {
final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex);
clearMotionHistory(pointerId);
break;
}
//清除所有触摸数据
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
cancel();
break;
}

这里不做解释了

6.注意

1
return mDragState == STATE_DRAGGING;

最后的这个则是说明当在STATE_DRAGGING状态下将事件消费掉,不像子View传递了

二. processTouchEvent


先直接上源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
public void processTouchEvent(MotionEvent ev) {
final int action = MotionEventCompat.getActionMasked(ev);
final int actionIndex = MotionEventCompat.getActionIndex(ev);
if (action == MotionEvent.ACTION_DOWN) {
// Reset things for a new event stream, just in case we didn't get
// the whole previous stream.
cancel();
}
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev);
switch (action) {
case MotionEvent.ACTION_DOWN: {
final float x = ev.getX();
final float y = ev.getY();
final int pointerId = MotionEventCompat.getPointerId(ev, 0);
final View toCapture = findTopChildUnder((int) x, (int) y);
saveInitialMotion(x, y, pointerId);
// Since the parent is already directly processing this touch event,
// there is no reason to delay for a slop before dragging.
// Start immediately if possible.
tryCaptureViewForDrag(toCapture, pointerId);
final int edgesTouched = mInitialEdgesTouched[pointerId];
if ((edgesTouched & mTrackingEdges) != 0) {
mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId);
}
break;
}
case MotionEventCompat.ACTION_POINTER_DOWN: {
final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex);
final float x = MotionEventCompat.getX(ev, actionIndex);
final float y = MotionEventCompat.getY(ev, actionIndex);
saveInitialMotion(x, y, pointerId);
// A ViewDragHelper can only manipulate one view at a time.
if (mDragState == STATE_IDLE) {
// If we're idle we can do anything! Treat it like a normal down event.
final View toCapture = findTopChildUnder((int) x, (int) y);
tryCaptureViewForDrag(toCapture, pointerId);
final int edgesTouched = mInitialEdgesTouched[pointerId];
if ((edgesTouched & mTrackingEdges) != 0) {
mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId);
}
} else if (isCapturedViewUnder((int) x, (int) y)) {
// We're still tracking a captured view. If the same view is under this
// point, we'll swap to controlling it with this pointer instead.
// (This will still work if we're "catching" a settling view.)
tryCaptureViewForDrag(mCapturedView, pointerId);
}
break;
}
case MotionEvent.ACTION_MOVE: {
if (mDragState == STATE_DRAGGING) {
// If pointer is invalid then skip the ACTION_MOVE.
if (!isValidPointerForActionMove(mActivePointerId)) break;
final int index = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
final float x = MotionEventCompat.getX(ev, index);
final float y = MotionEventCompat.getY(ev, index);
final int idx = (int) (x - mLastMotionX[mActivePointerId]);
final int idy = (int) (y - mLastMotionY[mActivePointerId]);
dragTo(mCapturedView.getLeft() + idx, mCapturedView.getTop() + idy, idx, idy);
saveLastMotion(ev);
} else {
// Check to see if any pointer is now over a draggable view.
final int pointerCount = MotionEventCompat.getPointerCount(ev);
for (int i = 0; i < pointerCount; i++) {
final int pointerId = MotionEventCompat.getPointerId(ev, i);
// If pointer is invalid then skip the ACTION_MOVE.
if (!isValidPointerForActionMove(pointerId)) continue;
final float x = MotionEventCompat.getX(ev, i);
final float y = MotionEventCompat.getY(ev, i);
final float dx = x - mInitialMotionX[pointerId];
final float dy = y - mInitialMotionY[pointerId];
reportNewEdgeDrags(dx, dy, pointerId);
if (mDragState == STATE_DRAGGING) {
// Callback might have started an edge drag.
break;
}
final View toCapture = findTopChildUnder((int) x, (int) y);
if (checkTouchSlop(toCapture, dx, dy) &&
tryCaptureViewForDrag(toCapture, pointerId)) {
break;
}
}
saveLastMotion(ev);
}
break;
}
case MotionEventCompat.ACTION_POINTER_UP: {
final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex);
if (mDragState == STATE_DRAGGING && pointerId == mActivePointerId) {
// Try to find another pointer that's still holding on to the captured view.
int newActivePointer = INVALID_POINTER;
final int pointerCount = MotionEventCompat.getPointerCount(ev);
for (int i = 0; i < pointerCount; i++) {
final int id = MotionEventCompat.getPointerId(ev, i);
if (id == mActivePointerId) {
// This one's going away, skip.
continue;
}
final float x = MotionEventCompat.getX(ev, i);
final float y = MotionEventCompat.getY(ev, i);
if (findTopChildUnder((int) x, (int) y) == mCapturedView &&
tryCaptureViewForDrag(mCapturedView, id)) {
newActivePointer = mActivePointerId;
break;
}
}
if (newActivePointer == INVALID_POINTER) {
// We didn't find another pointer still touching the view, release it.
releaseViewForPointerUp();
}
}
clearMotionHistory(pointerId);
break;
}
case MotionEvent.ACTION_UP: {
if (mDragState == STATE_DRAGGING) {
releaseViewForPointerUp();
}
cancel();
break;
}
case MotionEvent.ACTION_CANCEL: {
if (mDragState == STATE_DRAGGING) {
dispatchViewReleased(0, 0);
}
cancel();
break;
}
}
}

分成几个部分分析

1.准备工作

2.ACTION_DOWN相关解析

3.ACTION_POINTER_DOWN(又有一个手指触摸时)相关解析

与上面shouldInterceptTouchEvent部分类似不做更多的分析了

4. ACTION_MOVE相关解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
case MotionEvent.ACTION_MOVE: {
if (mDragState == STATE_DRAGGING) {
// If pointer is invalid then skip the ACTION_MOVE.
if (!isValidPointerForActionMove(mActivePointerId)) break;
final int index = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
final float x = MotionEventCompat.getX(ev, index);
final float y = MotionEventCompat.getY(ev, index);
final int idx = (int) (x - mLastMotionX[mActivePointerId]);
final int idy = (int) (y - mLastMotionY[mActivePointerId]);
dragTo(mCapturedView.getLeft() + idx, mCapturedView.getTop() + idy, idx, idy);
saveLastMotion(ev);
} else {
// Check to see if any pointer is now over a draggable view.
final int pointerCount = MotionEventCompat.getPointerCount(ev);
for (int i = 0; i < pointerCount; i++) {
final int pointerId = MotionEventCompat.getPointerId(ev, i);
// If pointer is invalid then skip the ACTION_MOVE.
if (!isValidPointerForActionMove(pointerId)) continue;
final float x = MotionEventCompat.getX(ev, i);
final float y = MotionEventCompat.getY(ev, i);
final float dx = x - mInitialMotionX[pointerId];
final float dy = y - mInitialMotionY[pointerId];
reportNewEdgeDrags(dx, dy, pointerId);
if (mDragState == STATE_DRAGGING) {
// Callback might have started an edge drag.
break;
}
final View toCapture = findTopChildUnder((int) x, (int) y);
if (checkTouchSlop(toCapture, dx, dy) &&
tryCaptureViewForDrag(toCapture, pointerId)) {
break;
}
}
saveLastMotion(ev);
}
break;
}

这里其实很好很好理解,因为其实本身自带的英文解释也解释的很清楚了

  • 1.当mDragState为STATE_DRAGGING状态时,拖拽至指定位置(dragTo)

    看下dragTo方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    private void dragTo(int left, int top, int dx, int dy) {
    int clampedX = left;
    int clampedY = top;
    final int oldLeft = mCapturedView.getLeft();
    final int oldTop = mCapturedView.getTop();
    if (dx != 0) {
    //回调callback来决定View最终被拖拽的x方向上的偏移量
    clampedX = mCallback.clampViewPositionHorizontal(mCapturedView, left, dx);
    ViewCompat.offsetLeftAndRight(mCapturedView, clampedX - oldLeft);
    }
    if (dy != 0) {
    //回调callback来决定View最终被拖拽的y方向上的偏移量
    clampedY = mCallback.clampViewPositionVertical(mCapturedView, top, dy);
    ViewCompat.offsetTopAndBottom(mCapturedView, clampedY - oldTop);
    }
    if (dx != 0 || dy != 0) {
    final int clampedDx = clampedX - oldLeft;
    final int clampedDy = clampedY - oldTop;
    //回调callback
    mCallback.onViewPositionChanged(mCapturedView, clampedX, clampedY,
    clampedDx, clampedDy);
    }
    }

这里主要回调了callback的三个方法

  • 2.当mDragStat不为STATE_DRAGGING状态时,就检测当前的位置是否经在一个View上,进行重新捕获View

    方法内部与shouldInterceptTouchEvent的ACTION_MOVE类似,大家理解就好了

5.ACTION_POINTER_UP(当多个手指中的一个手机松开时)相关解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
case MotionEventCompat.ACTION_POINTER_UP: {
final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex);
if (mDragState == STATE_DRAGGING && pointerId == mActivePointerId) {
// Try to find another pointer that's still holding on to the captured view.
int newActivePointer = INVALID_POINTER;
final int pointerCount = MotionEventCompat.getPointerCount(ev);
for (int i = 0; i < pointerCount; i++) {
final int id = MotionEventCompat.getPointerId(ev, i);
if (id == mActivePointerId) {
// This one's going away, skip.
continue;
}
final float x = MotionEventCompat.getX(ev, i);
final float y = MotionEventCompat.getY(ev, i);
if (findTopChildUnder((int) x, (int) y) == mCapturedView &&
tryCaptureViewForDrag(mCapturedView, id)) {
newActivePointer = mActivePointerId;
break;
}
}
if (newActivePointer == INVALID_POINTER) {
// We didn't find another pointer still touching the view, release it.
releaseViewForPointerUp();
}
}
clearMotionHistory(pointerId);
break;
}

这里主要做的工作是,当正在STATE_DRAGGING状态时多个手指中的一个松开,则再剩余还在触摸的点钟寻找是否正在View上(findTopChildUnder((int) x, (int) y) == mCapturedView && tryCaptureViewForDrag(mCapturedView, id))如果没找到则释放View(releaseViewForPointerUp())

6.剩余部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
case MotionEvent.ACTION_UP: {
//如果是拖拽状态的释放则调用
//releaseViewForPointerUp()
if (mDragState == STATE_DRAGGING) {
releaseViewForPointerUp();
}
cancel();
break;
}
case MotionEvent.ACTION_CANCEL: {
if (mDragState == STATE_DRAGGING) {
dispatchViewReleased(0, 0);
}
cancel();
break;
}

这里注释也已经写的很清楚了

写在后面的几句话


好了到这里ViewDragerHelper源码部分基本介绍完毕了,当然并不是特别详细,但是大家对照看下就可以理解了,后面会介绍下ViewDragerHelper更多的使用方式了,peace~~~