• [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 != || 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.

     

    作者:小文字
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
  • 相关阅读:
    关于添加“服务引用”和“添加引用”的一点总结
    nodejs+express工程 在npm install之后或使用npm install bootstrap命令安装bootstrap之后
    Mongo基础使用,以及在Express项目中使用Mongoose
    Express URL跳转(重定向)的实现
    node 开发web 登陆功能
    node js实战:带数据库,加密的注册登录表单
    nodejs 进阶:封装所有对数据库的常用操作
    nodejs进阶:密码加盐:随机盐值
    Nodejs进阶:密码加盐
    express 路由能力
  • 原文地址:https://www.cnblogs.com/Free-Thinker/p/4204918.html
Copyright © 2020-2023  润新知