[UI]抽屜菜單DrawerLayout分析(三)


在[UI]抽屜菜單DrawerLayout分析(一)和[UI]抽屜菜單DrawerLayout分析(二)中分別介紹了DrawerLayout得基本框架結構和ViewDragerHelper的作用以及手勢分發,本文一起來分析其中的Scroller的使用情況。

      在ViewDragerHelper中可以發現private ScrollerCompat mScroller;說明抽屜菜單的具體滑動也是依賴於Scroller的使用,檢索一下mScroller的引用,定位到forceSettleCapturedViewAt,這個方法回調用Scroller的startScroll來計算位移,它本身適用於計算和保存位移在特定時間的變化情況,最終的在繪制view時我可以獲取其保存的x,y坐標值。

/**

 * Settle the captured view at the given (left, top) position.

 *

 * @param finalLeft Target left position for the captured view

 * @param finalTop Target top position for the captured view

 * @param xvel Horizontal velocity

 * @param yvel Vertical velocity

 * @return true if animation should continue through {@link #continueSettling(boolean)} calls

 */

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

        returnfalse;

    }

 

    final int duration = computeSettleDuration(mCapturedView, dx, dy, xvel, yvel);

    mScroller.startScroll(startLeft, startTop, dx, dy, duration);

 

    setDragState(STATE_SETTLING);

    returntrue;

}

      這里用的是v4擴展包里的ScrollerCompat用於低版本兼容,它繼承自ScrollerCompatImpl,可以看到里面主要的方法聲明:

interface ScrollerCompatImpl{

    Object createScroller(Context context, Interpolator interpolator);

    boolean isFinished(Object scroller);

    int getCurrX(Object scroller);

    int getCurrY(Object scroller);

    float getCurrVelocity(Object scroller);

    boolean computeScrollOffset(Object scroller);

    void startScroll(Object scroller, int startX, int startY, int dx, int dy);

    void startScroll(Object scroller, int startX, int startY, int dx, int dy, int duration);

    void fling(Object scroller, int startX, int startY, int velX, int velY,

            int minX, int maxX, int minY, int maxY);

    void fling(Object scroller, int startX, int startY, int velX, int velY,

            int minX, int maxX, int minY, int maxY, int overX, int overY);

    void abortAnimation(Object scroller);

    void notifyHorizontalEdgeReached(Object scroller, int startX, int finalX, int overX);

    void notifyVerticalEdgeReached(Object scroller, int startY, int finalY, int overY);

    boolean isOverScrolled(Object scroller);

    int getFinalX(Object scroller);

    int getFinalY(Object scroller);

}

DragActionMethodFlow

從Scroller一直往上追溯,可以得到如圖的調用流程。

當滑動屏幕時,DrawerLayout中的手勢分發被觸發,先執行onInterceptTouchEvent根據返回結果確定是否執行onTouchEvent,之后就是一些和ViewDragHelper之間的回調接口處理。

接下來追蹤一下什么時候從Scroller中取出x,y來使用:

Scroller

在View里面有一個實現為空的computeScroll,DrawerLayout對它進行重寫,這個方法應該是在view自動重繪是會被調用,回到continueSettling:

/**

 * Move the captured settling view by the appropriate amount for the current time.

 * If <code>continueSettling</code> returns true, the caller should call it again

 * on the next frame to continue.

 *

 * @param deferCallbacks true if state callbacks should be deferred via posted message.

 *                       Set this to true if you are calling this method from

 *                       {@link android.view.View#computeScroll()} or similar methods

 *                       invoked as part of layout or drawing.

 * @return true if settle is still in progress

 */

public boolean continueSettling(boolean deferCallbacks) {

    if (mDragState == STATE_SETTLING) {

        boolean keepGoing = mScroller.computeScrollOffset();

        final int x = mScroller.getCurrX();

        final int y = mScroller.getCurrY();

        final int dx = x - mCapturedView.getLeft();

        final int dy = y - mCapturedView.getTop();

 

        if (dx != 0) {

            mCapturedView.offsetLeftAndRight(dx);

        }

        if (dy != 0) {

            mCapturedView.offsetTopAndBottom(dy);

        }

 

        if (dx != 0 || dy != 0) {

            mCallback.onViewPositionChanged(mCapturedView, x, y, dx, dy);

        }

 

        if (keepGoing && x == mScroller.getFinalX() && y == mScroller.getFinalY()) {

            // Close enough. The interpolator/scroller might think we're still moving

            // but the user sure doesn't.

            mScroller.abortAnimation();

            keepGoing = mScroller.isFinished();

        }

 

        if (!keepGoing) {

            if (deferCallbacks) {

                mParentView.post(mSetIdleRunnable);

            } else {

                setDragState(STATE_IDLE);

            }

        }

    }

 

    return mDragState == STATE_SETTLING;

}

 

當狀態處於STATE_SETTLING時開始獲取Scroller中的x,y值,結合當前運動view的left,top位置,計算出偏移量,通過offsetLeftAndRight設置,里面是一些具體的位置改變,挺復雜的。

/**

 * Offset this view's horizontal location by the specified amount of pixels.

 *

 * @param offset the number of pixels to offset the view by

 */

public void offsetLeftAndRight(int offset) {

    if (offset != 0) {

        updateMatrix();

        final boolean matrixIsIdentity = mTransformationInfo == null

                || mTransformationInfo.mMatrixIsIdentity;

        if (matrixIsIdentity) {

            if (mDisplayList != null) {

                invalidateViewProperty(false, false);

            } else {

                final ViewParent p = mParent;

                if (p != null && mAttachInfo != null) {

                    final Rect r = mAttachInfo.mTmpInvalRect;

                    int minLeft;

                    int maxRight;

                    if (offset < 0) {

                        minLeft = mLeft + offset;

                        maxRight = mRight;

                    } else {

                        minLeft = mLeft;

                        maxRight = mRight + offset;

                    }

                    r.set(0, 0, maxRight - minLeft, mBottom - mTop);

                    p.invalidateChild(this, r);

                }

            }

        } else {

            invalidateViewProperty(false, false);

        }

 

        mLeft += offset;

        mRight += offset;

        if (mDisplayList != null) {

            mDisplayList.offsetLeftAndRight(offset);

            invalidateViewProperty(false, false);

        } else {

            if (!matrixIsIdentity) {

                invalidateViewProperty(false, true);

            }

            invalidateParentIfNeeded();

        }

    }

}

小結

至此DrawerLayout的基本工作流程分析完畢,簡單做一個總結,v4包提供了ViewDragHelper類,里面封裝了對scroller合view的位移操作,和Callback接口,通過DrawerLayout內的onInterceptTouchEvent和onTouchEvent的重載,觸發ViewDragHelper內的相關方法,同時在DrawerLayout內實現ViewDragHelp.Callback.

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM