ViewDragHelper原理解讀

一.拖拽滑動(dòng)

其中拖住滑動(dòng)是通過dragTo方法來實(shí)現(xiàn)的

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) {
            clampedX = mCallback.clampViewPositionHorizontal(mCapturedView, left, dx);
            ViewCompat.offsetLeftAndRight(mCapturedView, clampedX - oldLeft);
        }
        if (dy != 0) {
            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;
            mCallback.onViewPositionChanged(mCapturedView, clampedX, clampedY,
                    clampedDx, clampedDy);
        }
    }

可以看到最終是通過ViewCompat.offsetLeftAndRight()ViewCompat.offsetTopAndBottom()來實(shí)現(xiàn)左右、上下拖動(dòng)的。

二.自動(dòng)滑動(dòng)

ViewDragHelper中有兩種方式可以實(shí)現(xiàn)子View自動(dòng)滑動(dòng)到某個(gè)位置,分別是

public boolean smoothSlideViewTo(@NonNull View child, int finalLeft, int finalTop) {
        **********
}
public boolean settleCapturedViewAt(int finalLeft, int finalTop) {
        **********
}

通過代碼可以看到它們都會(huì)調(diào)用forceSettleCapturedViewAt()方法,代碼如下

private boolean forceSettleCapturedViewAt(int finalLeft, int finalTop, int xvel, int yvel) {
    final int startLeft = mCapturedView.getLeft();
    final int startTop = mCapturedView.getTop();
    final int dx = finalLeft - startLeft;
    final int dy = finalTop - startTop;

    if (dx == 0 && dy == 0) {
        // Nothing to do. Send callbacks, be done.
        mScroller.abortAnimation();
        setDragState(STATE_IDLE);
        return false;
    }

    final int duration = computeSettleDuration(mCapturedView, dx, dy, xvel, yvel);
    mScroller.startScroll(startLeft, startTop, dx, dy, duration);

    setDragState(STATE_SETTLING);
    return true;
}

可以看到最終滑動(dòng)是通過OverScroller來實(shí)現(xiàn)的,我們知道Scroller是需配合在View的computeScroll()方法中不斷調(diào)用invalidate(),從而不斷重新繪制視圖位置達(dá)到視圖自動(dòng)滑動(dòng)的效果的,所以我們使用ViewDragHelper時(shí)需要重寫自定義View的computeScroll()方法,如

@Override
public void computeScroll() {
    super.computeScroll();
    if (mDragHelper != null && mDragHelper.continueSettling(true)) {
        invalidate();
    }
}

ViewDragHelper中還有一個(gè)方法可以實(shí)現(xiàn)在拖拽滑動(dòng)松手后進(jìn)行慣性滑動(dòng),最終停留位置由松手時(shí)的位置和速度來決定,它和上面的settleCapturedViewAt()都必須在mReleaseInProgresstrue的時(shí)候才能生效,也就是必須在CallbackonViewReleased()方法中調(diào)用。它也是通過調(diào)用OverScroller,所以使用時(shí)同樣需重寫自定義View的computeScroll()方法。

public void flingCapturedView(int minLeft, int minTop, int maxLeft, int maxTop) {
    if (!mReleaseInProgress) {
        throw new IllegalStateException("Cannot flingCapturedView outside of a call to "
                + "Callback#onViewReleased");
    }

    mScroller.fling(mCapturedView.getLeft(), mCapturedView.getTop(),
            (int) mVelocityTracker.getXVelocity(mActivePointerId),
            (int) mVelocityTracker.getYVelocity(mActivePointerId),
            minLeft, maxLeft, minTop, maxTop);

    setDragState(STATE_SETTLING);
}

三.邊緣滑動(dòng)

/**
 * Enable edge tracking for the selected edges of the parent view.
 * The callback's {@link Callback#onEdgeTouched(int, int)} and
 * {@link Callback#onEdgeDragStarted(int, int)} methods will only be invoked
 * for edges for which edge tracking has been enabled.
 *
 * @param edgeFlags Combination of edge flags describing the edges to watch
 * @see #EDGE_LEFT
 * @see #EDGE_TOP
 * @see #EDGE_RIGHT
 * @see #EDGE_BOTTOM
 */
public void setEdgeTrackingEnabled(int edgeFlags) {
    mTrackingEdges = edgeFlags;
}

ViewDragHelper通過setEdgeTrackingEnabled()方法設(shè)置要監(jiān)測(cè)哪個(gè)方向屏幕邊緣的滑動(dòng),而是否是邊緣滑動(dòng)是通過對(duì)觸摸事件中Action_Down時(shí)的位置進(jìn)行判令的

    public boolean shouldInterceptTouchEvent(@NonNull MotionEvent ev) {
        **********
        switch (action) {
            case MotionEvent.ACTION_DOWN: {
                final float x = ev.getX();
                final float y = ev.getY();
                final int pointerId = ev.getPointerId(0);
                saveInitialMotion(x, y, pointerId);

               **********

                final int edgesTouched = mInitialEdgesTouched[pointerId];
                if ((edgesTouched & mTrackingEdges) != 0) {
                    mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId);
                }
                break;
            }

            case MotionEvent.ACTION_POINTER_DOWN: {
                final int pointerId = ev.getPointerId(actionIndex);
                final float x = ev.getX(actionIndex);
                final float y = ev.getY(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) {
                   **********
                }
                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 = ev.getPointerCount();
                for (int i = 0; i < pointerCount; i++) {
                    final int pointerId = ev.getPointerId(i);

                    // If pointer is invalid then skip the ACTION_MOVE.
                    if (!isValidPointerForActionMove(pointerId)) continue;

                    final float x = ev.getX(i);
                    final float y = ev.getY(i);
                    final float dx = x - mInitialMotionX[pointerId];
                    final float dy = y - mInitialMotionY[pointerId];

                    **********
                    reportNewEdgeDrags(dx, dy, pointerId);
                    **********
                }
                saveLastMotion(ev);
                break;
            }

            **********
        }

        return mDragState == STATE_DRAGGING;
    }
    public void processTouchEvent(@NonNull MotionEvent ev) {
        **********
        switch (action) {
            case MotionEvent.ACTION_DOWN: {
                final float x = ev.getX();
                final float y = ev.getY();
                final int pointerId = ev.getPointerId(0);
                final View toCapture = findTopChildUnder((int) x, (int) y);

                saveInitialMotion(x, y, pointerId);

               **********

                final int edgesTouched = mInitialEdgesTouched[pointerId];
                if ((edgesTouched & mTrackingEdges) != 0) {
                    mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId);
                }
                break;
            }

            case MotionEvent.ACTION_POINTER_DOWN: {
                final int pointerId = ev.getPointerId(actionIndex);
                final float x = ev.getX(actionIndex);
                final float y = ev.getY(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 (isCapturedViewUnder((int) x, (int) y)) {
                    **********
                }
                break;
            }

            case MotionEvent.ACTION_MOVE: {
                if (mDragState == STATE_DRAGGING) {
                    **********
                } else {
                    // Check to see if any pointer is now over a draggable view.
                    final int pointerCount = ev.getPointerCount();
                    for (int i = 0; i < pointerCount; i++) {
                        final int pointerId = ev.getPointerId(i);

                        // If pointer is invalid then skip the ACTION_MOVE.
                        if (!isValidPointerForActionMove(pointerId)) continue;

                        final float x = ev.getX(i);
                        final float y = ev.getY(i);
                        final float dx = x - mInitialMotionX[pointerId];
                        final float dy = y - mInitialMotionY[pointerId];

                        reportNewEdgeDrags(dx, dy, pointerId);
                        **********
                    }
                    saveLastMotion(ev);
                }
                break;
            }

            **********
        }
    }

其中saveInitialMotion()方法如下

private void saveInitialMotion(float x, float y, int pointerId) {
    ensureMotionHistorySizeForId(pointerId);
    mInitialMotionX[pointerId] = mLastMotionX[pointerId] = x;
    mInitialMotionY[pointerId] = mLastMotionY[pointerId] = y;
    mInitialEdgesTouched[pointerId] = getEdgesTouched((int) x, (int) y);
    mPointersDown |= 1 << pointerId;
}
private int getEdgesTouched(int x, int y) {
    int result = 0;

    if (x < mParentView.getLeft() + mEdgeSize) result |= EDGE_LEFT;
    if (y < mParentView.getTop() + mEdgeSize) result |= EDGE_TOP;
    if (x > mParentView.getRight() - mEdgeSize) result |= EDGE_RIGHT;
    if (y > mParentView.getBottom() - mEdgeSize) result |= EDGE_BOTTOM;

    return result;
}

可以看出ACTION_DOWN時(shí)會(huì)記錄觸摸點(diǎn)的x,y坐標(biāo)位置,手指id,以及當(dāng)前手指是否是觸摸在屏幕某一個(gè)邊緣,具體是通過當(dāng)前觸摸點(diǎn)x或y是否在邊緣一個(gè)很小的距離范圍來來判定。如果是邊緣觸摸并且方向和setEdgeTrackingEnabled()方法設(shè)置要監(jiān)測(cè)的方向一致,則會(huì)回調(diào)CallbackonEdgeTouched()方法。

通過上面的ACTION_MOVE中代碼看到手指滑動(dòng)過程中,會(huì)調(diào)用reportNewEdgeDrags()方法來對(duì)邊緣滑動(dòng)做進(jìn)一步的處理

private void reportNewEdgeDrags(float dx, float dy, int pointerId) {
    int dragsStarted = 0;
    if (checkNewEdgeDrag(dx, dy, pointerId, EDGE_LEFT)) {
        dragsStarted |= EDGE_LEFT;
    }
    if (checkNewEdgeDrag(dy, dx, pointerId, EDGE_TOP)) {
        dragsStarted |= EDGE_TOP;
    }
    if (checkNewEdgeDrag(dx, dy, pointerId, EDGE_RIGHT)) {
        dragsStarted |= EDGE_RIGHT;
    }
    if (checkNewEdgeDrag(dy, dx, pointerId, EDGE_BOTTOM)) {
        dragsStarted |= EDGE_BOTTOM;
    }

    if (dragsStarted != 0) {
        mEdgeDragsInProgress[pointerId] |= dragsStarted;
        mCallback.onEdgeDragStarted(dragsStarted, pointerId);
    }
}
private boolean checkNewEdgeDrag(float delta, float odelta, int pointerId, int edge) {
    final float absDelta = Math.abs(delta);
    final float absODelta = Math.abs(odelta);

    if ((mInitialEdgesTouched[pointerId] & edge) != edge  || (mTrackingEdges & edge) == 0
            || (mEdgeDragsLocked[pointerId] & edge) == edge
            || (mEdgeDragsInProgress[pointerId] & edge) == edge
            || (absDelta <= mTouchSlop && absODelta <= mTouchSlop)) {
        return false;
    }
    if (absDelta < absODelta * 0.5f && mCallback.onEdgeLock(edge)) {
        mEdgeDragsLocked[pointerId] |= edge;
        return false;
    }
    return (mEdgeDragsInProgress[pointerId] & edge) == 0 && absDelta > mTouchSlop;
}

可以看到會(huì)通過mInitialEdgesTouched中記錄的該手指最開始Action_down的時(shí)候是否是邊緣觸摸,要監(jiān)測(cè)的是否是該方向,該邊緣方向是否是鎖定狀態(tài),以及是否是Action_down后首次該邊緣方向的ACTION_MOVE觸發(fā)多個(gè)條件來進(jìn)行判定,如果判定成功則會(huì)調(diào)用CallbackonEdgeDragStarted()方法。

其中某個(gè)邊緣方向是否是鎖定狀態(tài)通過CallbackonEdgeLock()方法來獲取,我們可以在這個(gè)回調(diào)中動(dòng)態(tài)改變返回值來動(dòng)態(tài)設(shè)置是否可以觸發(fā)onEdgeDragStarted()方法,而真正要實(shí)現(xiàn)邊緣觸摸的時(shí)候可以拖拽出某個(gè)view(比如某一個(gè)下拉菜單)還需要在onEdgeDragStarted()中做其他處理,如

ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {
      **********
        @Override
        public void onEdgeTouched(int edgeFlags, int pointerId) {
        super.onEdgeTouched(edgeFlags, pointerId);
      }

      @Override
      public boolean onEdgeLock(int edgeFlags) {
        return super.onEdgeLock(edgeFlags);
      }

      @Override
      public void onEdgeDragStarted(int edgeFlags, int pointerId) {
        mDragHelper.captureChildView(mBottomMenu, pointerId);
      }
      **********
};

參考鏈接:
ViewDragHelper 的基本使用(一)
ViewDragHelper原理與使用
ViewDragHelper源碼解析
Android ViewDragHelper源碼解析

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • ViewDragHelper實(shí)例的創(chuàng)建 ViewDragHelper重載了兩個(gè)create()靜態(tài)方法public...
    傀儡世界閱讀 731評(píng)論 0 3
  • ViewDragHelper作為官方推出的手勢(shì)滑動(dòng)輔助工具,極大的簡(jiǎn)化了我們對(duì)手勢(shì)滑動(dòng)的處理邏輯,v4包中的Sli...
    zhangke3016閱讀 8,535評(píng)論 5 60
  • 讀者閱讀本文后將會(huì)有如下收獲: 不借助于 ViewDragHelper 實(shí)現(xiàn)基本的拖拽效果。 借助于 ViewDr...
    as_pixar閱讀 1,190評(píng)論 1 16
  • 滑動(dòng)返回是ios設(shè)備中默認(rèn)支持的一種滑動(dòng)退出效果,由于IPhone設(shè)備沒有返回鍵,所以滑動(dòng)退出使用起來十分方便。而...
    健叔閱讀 7,926評(píng)論 2 18
  • 我每周會(huì)寫一篇源代碼分析的文章,以后也可能會(huì)有其他主題.如果你喜歡我寫的文章的話,歡迎關(guān)注我的新浪微博@達(dá)達(dá)達(dá)達(dá)s...
    SkyKai閱讀 3,496評(píng)論 3 30

友情鏈接更多精彩內(nèi)容