ViewDragHelper源代码分析

来源:互联网 发布:淘宝9888 编辑:程序博客网 时间:2024/06/05 08:08

原文地址   http://www.jianshu.com/p/07d717ef0b28


ViewDragHelper源代码分析

字数4944 阅读102 评论0 

1.简介

在上一篇文章SwipeBackLayout源代码分析中,我们了解了ViewDragHelper是可以帮助我们处理各种拖拽事件的类.使用好ViewDragHelper能帮助我们做出各种酷炫的交互,今天我们就来分析一下ViewDragHelper的使用与实现:
<!-- more -->

2.使用方法

我们这里就以翔总的这篇文章中的例子来介绍一下ViewDragHelper的使用.另外,本文中的demo可以在
这里找到

首先我们创建一个DragLayout类并继承自LinearLayout,然后我们准备在DragLayout放置三个View第一个用来被我们拖动然后停止在松手的位置,第二个可以被我们拖动,松手的时候滑动到指定位置,第三个只可以通过触摸边缘来进行拖动,

public class DragLayout extends LinearLayout {    private ViewDragHelper mDragger;    private View mDragView;    private View mAutoBackView;    private View mEdgeTrackerView;    private Point mAutoBackOriginPos = new Point();    public DragLayout(Context context) {        this(context, null);    }    public DragLayout(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public DragLayout(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        initViewDragHelper();    }    private void initViewDragHelper() {        mDragger = ViewDragHelper.create(this,myCallback);        mDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_ALL);    }    ViewDragHelper.Callback myCallback = new ViewDragHelper.Callback() {        @Override        //child为当前触摸区域下的View,如果返回true,就可以拖拽.        public boolean tryCaptureView(View child, int pointerId) {            return child == mDragView || child == mAutoBackView;        }        //松手时的回调        @Override        public void onViewReleased(View releasedChild, float xvel, float yvel) {            if (releasedChild == mAutoBackView) {                mDragger.settleCapturedViewAt(mAutoBackOriginPos.x, mAutoBackOriginPos.y);                invalidate();            }        }        //边缘触摸开始时的回调        @Override        public void onEdgeDragStarted(int edgeFlags, int pointerId) {            mDragger.captureChildView(mEdgeTrackerView, pointerId);        }        //获取水平方向允许拖拽的区域,这里是父布局的宽-子控件的宽        @Override        public int getViewHorizontalDragRange(View child) {            return getMeasuredWidth() - child.getMeasuredWidth();        }        //获取垂直方向允许拖拽的范围        @Override        public int getViewVerticalDragRange(View child) {            return getMeasuredHeight() - child.getMeasuredHeight();        }        //left为child即将移动到的水平位置的值,但是返回值会最终决定移动到的值        //这里直接返回了left        @Override        public int clampViewPositionHorizontal(View child, int left, int dx) {            return left;        }        //同上只是这里是垂直方向        @Override        public int clampViewPositionVertical(View child, int top, int dy) {            return top;        }    };    @Override    public void computeScroll() {        if (mDragger.continueSettling(true)) {            invalidate();        }    }    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        return mDragger.shouldInterceptTouchEvent(ev);    }    @Override    public boolean onTouchEvent(MotionEvent event) {        mDragger.processTouchEvent(event);        return true;    }    @Override    protected void onFinishInflate() {        super.onFinishInflate();        mDragView = getChildAt(0);        mAutoBackView = getChildAt(1);        mEdgeTrackerView = getChildAt(2);    }    @Override    protected void onLayout(boolean changed, int l, int t, int r, int b) {        super.onLayout(changed, l, t, r, b);        mAutoBackOriginPos.x = mAutoBackView.getLeft();        mAutoBackOriginPos.y = mAutoBackView.getTop();    }}
  1. 我们首先在构造方法里传入了当前类的对象和我们定义的ViewDragHelper.Callback对象初始化了我们的ViewDragHelper,然后我们希望所有的边缘触摸都能触发mEdgeTrackerView的拖动,所以我们紧接着调用了mDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_ALL);方法.
  2. 在我们定义的Callback中,有多个回调方法,每个回调方法都有它的作用,在代码里注释比较清楚了,我们下面也会解析每一个Callback中回调方法的作用.
  3. 第三步我们需要在onInterceptTouchEvent()方法和onTouchEvent()将事件委托给ViewDragHelper去处理,这样ViewDragHelper才能根据响应的事件并回调我们自己编写的Callback接口来进行响应的处理,
  4. 由于ViewDragHelper中的滑动是交给Srcoller类来处理的所以这里我们要重写computeScroll()方法,配合Scroller完成滚动动画.
  5. 最后在onFinishInflate()里获取到我们的View对象即可.

3.类关系图

由于就一个类类图我们就不画了,但是作为一个强迫症患者,这个标题必须有...

4.源码分析

1.ViewDragHelper.Callback的实现

在分析ViewDragHelper之前,我们先来分析一下Callback的定义,看看Callback都定义了哪些方法:

    public static abstract class Callback {        //当View的拖拽状态改变时回调,state为STATE_IDLE,STATE_DRAGGING,STATE_SETTLING的一种        //STATE_IDLE: 当前未被拖拽        //STATE_DRAGGING:正在被拖拽        //STATE_SETTLING: 被拖拽后需要被安放到一个位置中的状态        public void onViewDragStateChanged(int state) {}        //当View被拖拽位置发生改变时回调        //changedView :被拖拽的View        //left : 被拖拽后View的left边缘坐标        //top : 被拖拽后View的top边缘坐标        //dx : 拖动的x偏移量        //dy : 拖动的y偏移量        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {}        //当一个View被捕获到准备开始拖动时回调,        //capturedChild : 捕获的View        //activePointerId : 对应的PointerId        public void onViewCaptured(View capturedChild, int activePointerId) {}        //当被捕获拖拽的View被释放是回调        //releasedChild : 被释放的View        //xvel : 释放View的x方向上的加速度        //yvel : 释放View的y方向上的加速度        public void onViewReleased(View releasedChild, float xvel, float yvel) {}        //如果parentView订阅了边缘触摸,则如果有边缘触摸就回调的接口        //edgeFlags : 当前触摸的flag 有: EDGE_LEFT,EDGE_TOP,EDGE_RIGHT,EDGE_BOTTOM        //pointerId : 用来描述边缘触摸操作的id        public void onEdgeTouched(int edgeFlags, int pointerId) {}        //是否锁定该边缘的触摸,默认返回false,返回true表示锁定        public boolean onEdgeLock(int edgeFlags) {            return false;        }        //边缘触摸开始时回调        //edgeFlags : 当前触摸的flag 有: EDGE_LEFT,EDGE_TOP,EDGE_RIGHT,EDGE_BOTTOM        //pointerId : 用来描述边缘触摸操作的id        public void onEdgeDragStarted(int edgeFlags, int pointerId) {}        //在寻找当前触摸点下的子View时会调用此方法,寻找到的View会提供给tryCaptureViewForDrag()来尝试捕获。        //如果需要改变子View的遍历查询顺序可改写此方法,例如让下层的View优先于上层的View被选中。        public int getOrderedChildIndex(int index) {            return index;        }        //获取被拖拽View child 的水平拖拽范围,返回0表示无法被水平拖拽        public int getViewHorizontalDragRange(View child) {            return 0;        }        //获取被拖拽View child 的垂直拖拽范围,返回0表示无法被水平拖拽        public int getViewVerticalDragRange(View child) {            return 0;        }        //尝试捕获被拖拽的View        public abstract boolean tryCaptureView(View child, int pointerId);        //决定拖拽View在水平方向上应该移动到的位置        //child : 被拖拽的View        //left : 期望移动到位置的View的left值        //dx : 移动的水平距离        //返回值 : 直接决定View在水平方向的位置        public int clampViewPositionHorizontal(View child, int left, int dx) {            return 0;        }        //决定拖拽View在垂直方向上应该移动到的位置        //child : 被拖拽的View        //top : 期望移动到位置的View的top值        //dy : 移动的垂直距离        //返回值 : 直接决定View在垂直方向的位置        public int clampViewPositionVertical(View child, int top, int dy) {            return 0;        }    }

想必注释已经很清楚了,正是这些回调方法,再结合ViewDragHelper中的各种方法,来帮助我们实现各种各样的拖拽的效果。

2.shouldInterceptTouchEvent()方法的实现

在这里我们假设大家都清楚了Android的事件分发机制,如果不清楚请看这里,要想处理触摸事件,我们需要在onInterceptTouchEvent(MotionEvent ev)方法里判断是否需要拦截这次触摸事件,如果此方法返回true则触摸事件将会交给onTouchEvent(MotionEvent event)处理,这样我们就能处理触摸事件了,所以我们在上面的使用方法里会这样写:

    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        return mDragger.shouldInterceptTouchEvent(ev);    }    @Override    public boolean onTouchEvent(MotionEvent event) {        mDragger.processTouchEvent(event);        return true;    }

这样就将是否拦截触摸事件,以及处理触摸事件委托给ViewDragHelper来处理了,所以我们先来看看ViewDragHelpershouldInterceptTouchEvent();方法的实现:

    public boolean shouldInterceptTouchEvent(MotionEvent ev) {        //获取action        final int action = MotionEventCompat.getActionMasked(ev);        //获取action对应的index        final int actionIndex = MotionEventCompat.getActionIndex(ev);        //如果是按下的action则重置一些信息,包括各种事件点的数组        if (action == MotionEvent.ACTION_DOWN) {            // Reset things for a new event stream, just in case we didn't get            // the whole previous stream.            cancel();        }        //初始化mVelocityTracker        if (mVelocityTracker == null) {            mVelocityTracker = VelocityTracker.obtain();        }        mVelocityTracker.addMovement(ev);        //根据action来做相应的处理        switch (action) {            case MotionEvent.ACTION_DOWN: {                final float x = ev.getX();                final float y = ev.getY();                //获取这个事件对应的pointerId,一般情况下只有一个手指触摸时为0                //两个手指触摸时第二个手指触摸返回的pointerId为1,以此类推                final int pointerId = MotionEventCompat.getPointerId(ev, 0);                //保存点的数据                //TODO (1)                saveInitialMotion(x, y, pointerId);                //获取当前触摸点下最顶层的子View                //TODO (2)                final View toCapture = findTopChildUnder((int) x, (int) y);                //如果toCapture是已经捕获的View,而且正在处于被释放状态                //那么就重新捕获                if (toCapture == mCapturedView && mDragState == STATE_SETTLING) {                    tryCaptureViewForDrag(toCapture, pointerId);                }                //如果触摸了边缘,回调callback的onEdgeTouched()方法                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);                //因为同一时间ViewDragHelper只能操控一个View,所以当有新的手指触摸时                //只讨论当无触摸发生时,回调边缘触摸的callback                //或者正在处于释放状态时重新捕获View                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);                    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];                    //获取当前触摸点下最顶层的子View                    final View toCapture = findTopChildUnder((int) x, (int) y);                    //如果找到了最顶层View,并且产生了拖动(checkTouchSlop()返回true)                    //TODO (3)                    final boolean pastSlop = toCapture != null && checkTouchSlop(toCapture, dx, dy);                    if (pastSlop) {                        //根据callback的四个方法getView[Horizontal|Vertical]DragRange和                        //clampViewPosition[Horizontal|Vertical]来检查是否可以拖动                        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;                    }                    //如果产生了拖动则调用tryCaptureViewForDrag()                    //TODO (4)                    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;            }        }        //如果mDragState等于正在拖拽则返回true        return mDragState == STATE_DRAGGING;    }

上面就是整个shouldInterceptTouchEvent()的实现,上面的注释也足够清楚了,我们这里就先不分析某一种触摸事件,大家可以看到我上面留了几个TODO,下文会一起分析,这里我假设大家都已经对触摸事件分发处理都有充分的理解了,我们下面就直接看ViewDragHelperprocessTouchEvent()方法的实现.

3.processTouchEvent()方法的实现

    public void processTouchEvent(MotionEvent ev) {        final int action = MotionEventCompat.getActionMasked(ev);        final int actionIndex = MotionEventCompat.getActionIndex(ev);        ...(省去部分代码)        switch (action) {            case MotionEvent.ACTION_DOWN: {                ...(省去部分代码)                break;            }            case MotionEventCompat.ACTION_POINTER_DOWN: {                ...(省去部分代码)                break;            }            case MotionEvent.ACTION_MOVE: {                //如果现在已经是拖拽状态                if (mDragState == STATE_DRAGGING) {                    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]);                    //拖拽至指定位置                    //TODO (5)                    dragTo(mCapturedView.getLeft() + idx, mCapturedView.getTop() + idy, idx, idy);                    saveLastMotion(ev);                } else {                    // Check to see if any pointer is now over a draggable view.                    //如果还不是拖拽状态,就检测是否经过了一个View                    final int pointerCount = MotionEventCompat.getPointerCount(ev);                    for (int i = 0; i < pointerCount; i++) {                        final int pointerId = MotionEventCompat.getPointerId(ev, i);                        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);                //如果当前点正在被拖拽,则再剩余还在触摸的点钟寻找是否正在View上                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.                        //如果没找到则释放View                        //TODO (6)                        releaseViewForPointerUp();                    }                }                clearMotionHistory(pointerId);                break;            }            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;            }        }    }

上面就是processTouchEvent()方法的实现,我们省去了部分大致与shouldInterceptTouchEvent()相同的逻辑代码,通过事件传递机制我们知道,如果程序已经进入到processTouchEvent()中,也就意味着触摸事件就不会再向下传递,都会交给此方法处理,所以在这里我们就需要处理拖拽事件了,通过上面的注释,我们也看到了在MotionEvent.ACTION_MOVE,MotionEventCompat.ACTION_POINTER_UP,MotionEvent.ACTION_UPMotionEvent.ACTION_CANCEL都分别进行了处理 ,我们知道触摸事件大致的流程是:

ACTION_DOWN -> ACTION_MOVE -> ... -> ACTION_MOVE -> ACTION_UP

再配合事件的分发机制,我们就能很清晰的分析出一次完整的事件调用过程,所以整个ViewDragHelper的拖拽过程也能很清晰的分为三个步骤:

捕获拖拽目标View -> 拖拽目标View -> 处理目标View释放操作

最后我们再分析上面两段代码的6个TODO:

4.saveInitialMotion()方法

    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;    }

5.findTopChildUnder()方法

    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;    }

代码很简单就是根据xy坐标和来找到指定View,注意这里回调了callback中的getOrderedChildIndex()方法,所以我们可以在这里返回指定的Viewindex.

6.checkTouchSlop()方法

    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根据我们设定的灵敏度决定.

7.tryCaptureViewForDrag()方法

    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(),并返回true            captureChildView(toCapture, pointerId);            return true;        }        return false;    }

可以看到如果可以捕获View则调用了captureChildView()方法:

    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        mCapturedView = childView;        mActivePointerId = activePointerId;        //回调callback        mCallback.onViewCaptured(childView, activePointerId);        //设定mDragState的状态为STATE_DRAGGING        setDragState(STATE_DRAGGING);    }

如果程序执行到这里,就证明View已经处于拖拽状态了,后续的触摸操作,将直接根据mDragStateSTATE_DRAGGING的状态处理.

8.dragTo()方法的实现

    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);            //移动View            ViewCompat.offsetLeftAndRight(mCapturedView, clampedX - oldLeft);        }        if (dy != 0) {            //回调callback来决定View最终被拖拽的y方向上的偏移量            clampedY = mCallback.clampViewPositionVertical(mCapturedView, top, dy);            //移动View            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);        }    }

因为dragTo()方法是在processTouchEvent()中的MotionEvent.ACTION_MOVE case被调用所以当程序运行到这里时View就会不断的被拖动了。如果一旦手指释放则最终会调用releaseViewForPointerUp()方法

8.releaseViewForPointerUp()方法的实现

    private void releaseViewForPointerUp() {        //计算出当前x和y方向上的加速度        mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);        final float xvel = clampMag(                VelocityTrackerCompat.getXVelocity(mVelocityTracker, mActivePointerId),                mMinVelocity, mMaxVelocity);        final float yvel = clampMag(                VelocityTrackerCompat.getYVelocity(mVelocityTracker, mActivePointerId),                mMinVelocity, mMaxVelocity);        dispatchViewReleased(xvel, yvel);    }

计算完加速度后就调用了dispatchViewReleased():

    private void dispatchViewReleased(float xvel, float yvel) {        //设定当前正处于释放阶段        mReleaseInProgress = true;        //回调callback的onViewReleased()方法        mCallback.onViewReleased(mCapturedView, xvel, yvel);        mReleaseInProgress = false;        //设定状态        if (mDragState == STATE_DRAGGING) {            // onViewReleased didn't call a method that would have changed this. Go idle.            //如果onViewReleased()中没有调用任何方法,则状态设定为STATE_IDLE            setDragState(STATE_IDLE);        }    }

所以最后释放后的处理交给了callback中的onViewReleased()方法,如果我们什么都不做,那么这个被拖拽的View就是停止在当前位置,或者我们可以调用ViewDragHelper提供给我们的这几个方法:

  • settleCapturedViewAt(int finalLeft, int finalTop)
    以松手前的滑动速度为初速动,让捕获到的View自动滚动到指定位置。只能在Callback的onViewReleased()中调用。
  • flingCapturedView(int minLeft, int minTop, int maxLeft, int maxTop)
    以松手前的滑动速度为初速动,让捕获到的View在指定范围内fling。只能在Callback的onViewReleased()中调用。
  • smoothSlideViewTo(View child, int finalLeft, int finalTop)
    指定某个View自动滚动到指定的位置,初速度为0,可在任何地方调用。

引用自这篇文章,具体释放后的原理我们就不分析了,其实就是配合Scroller这个类来实现,具体也可以参照上面这篇文章。好,我们关于ViewDragHelper的源码分析就到这里.

5.开源项目中的使用

ViewDragHelper在各种关于拖拽和各种手势动画的开源库中使用广泛,我这里就简要列出一些,大家可以多去看看是如何使用ViewDragHelper的:

  • SwipeBackLayout
  • android-card-slide-panel
  • FlowingDrawer

6.个人评价

ViewDragHelper的出现,大大简化了我们开发相关触摸和拖拽功能的复杂度和代码量,帮助我们比较容易的实现各种效果,让我们开发酷炫的交互更加容易了。但是从一些开源项目中发现,ViewDragHelper中还是有一些不足之处,比如给Scroller提供了一个固定的Interpolator,导致如果我们想实现例如反弹效果的话,还要把ViewDragHelper的代码拷贝一份并修改Interpolator,这样做肯定是不太好的.当然建议我们自己修改一个ViewDragHelper后如果项目里有多处使用,可以包装成一个提供给我们自己项目的模块使用,防止出现更多的多余代码.


1 0