当前位置: 首页 > article >正文

Android13 launcher循环切页

launcher 常规切页:https://blog.csdn.net/a396604593/article/details/125305234

循环切页

我们知道,launcher切页是在packages\apps\Launcher3\src\com\android\launcher3\PagedView.java的onTouchEvent中实现的。

1、滑动限制
public boolean onTouchEvent(MotionEvent ev) {
	case MotionEvent.ACTION_MOVE:
		mOrientationHandler.setPrimary(this, VIEW_SCROLL_BY, movedDelta);
}
...
//pagedview重写
    @Override
    public void scrollTo(int x, int y) {
    //注释掉x y的坐标显示,让页面能切到首页和末尾继续下发x y
//        x = Utilities.boundToRange(x,
//                mOrientationHandler.getPrimaryValue(mMinScroll, 0), mMaxScroll);
//        y = Utilities.boundToRange(y,
//                mOrientationHandler.getPrimaryValue(0, mMinScroll), mMaxScroll);
        Log.d(TAG," scrollTo: "+x +" , "+y +" mMinScroll: "+mMinScroll+" mMaxScroll: "+mMaxScroll);
        super.scrollTo(x, y);
    }
2、循环切页时,我们需要手动绘制页面上去,让循环切页看上去和正常切页一样

packages\apps\Launcher3\src\com\android\launcher3\Workspace.java

@Override
    protected void dispatchDraw(Canvas canvas) {
        boolean restore = false;
        int restoreCount = 0;
        boolean fastDraw = //mTouchState != TOUCH_STATE_SCROLLING &&
                getNextPage() == INVALID_PAGE;
        if (fastDraw && mIsPageInTransition) {
            Log.d(TAG," dispatchDraw 666  getScrollX(): "+getScrollX()+"  "+mScroller.getCurrX());
            drawChild(canvas, getChildAt(getCurrentPage()), getDrawingTime());
            //在非滑动中、非临界条件的正常情况下绘制屏幕
        } else
        {
            Log.d(TAG," dispatchDraw 000  getScrollX(): "+getScrollX()+"  "+mScroller.getCurrX());
            long drawingTime = getDrawingTime();
            int width = getWidth()+ 22;
            float scrollPos = (float) getScrollX() / width;
            boolean endlessScrolling = true;

            int leftScreen;
            int rightScreen;
            boolean isScrollToRight = false;
            int childCount = getChildCount();//其值为1、2、3----
            if (scrollPos < 0 && endlessScrolling) {
                //屏幕是向左滑到临界
                leftScreen = childCount - 1;
                rightScreen = 0;
            } else {//屏幕向右滑动到临界
                leftScreen = Math.min( (int) scrollPos, childCount - 1 );
                rightScreen = leftScreen + 1;
                if (endlessScrolling) {
                    rightScreen = rightScreen % childCount;
                    isScrollToRight = true;
                }
            }
            if (isScreenNoValid(leftScreen)) {
                if (rightScreen == 0 && !isScrollToRight) { // 向左滑动,如果rightScreen为0
                    int offset = childCount * width;
                    Log.d(TAG," dispatchDraw 111  width: "+width+" getScrollX():  "+getScrollX()+" offset: "+offset);
                    canvas.translate(-offset, 0);
                    drawChild(canvas, getChildAt(leftScreen), drawingTime);
                    canvas.translate(+offset, 0);
                } else {
                    Log.d(TAG," dispatchDraw 222  width: "+width+" getScrollX():  "+getScrollX());
                    drawChild(canvas, getChildAt(leftScreen), drawingTime);
                }
            }
            if (scrollPos != leftScreen && isScreenNoValid(rightScreen)) {//向右滑动
                if (endlessScrolling && rightScreen == 0  && isScrollToRight) {
                    int offset = childCount * width;
                    Log.d(TAG," dispatchDraw 333  width: "+width+ " getScrollX():  "+getScrollX()+" offset: "+offset);
                    canvas.translate(+offset, 0);
                    drawChild(canvas, getChildAt(rightScreen), drawingTime);
                    canvas.translate(-offset, 0);
                } else {
                    Log.d(TAG," dispatchDraw 444  width: "+width+" getScrollX():  "+getScrollX());
                    drawChild(canvas, getChildAt(rightScreen), drawingTime);
                }
            }
        }
    }
    //判断非临界条件下所在的屏幕,如果是//临界则返回false
    private boolean isScreenNoValid(int screen) {
        return screen >= 0 && screen < getChildCount();
    }
3、松手后,我们需要让循环切页和正常切页一样动画自然切过去

假设一共有 0 1 2 三页,我们需要从 0 切到 2 3切到 0 ,而不是 0 1 2 , 2 1 0
重新回到launcher切页是在packages\apps\Launcher3\src\com\android\launcher3\PagedView.java的onTouchEvent

public boolean onTouchEvent(MotionEvent ev) {
	case MotionEvent.ACTION_UP:
		int finalPage;
                    // We give flings precedence over large moves, which is why we short-circuit our
                    // test for a large move if a fling has been registered. That is, a large
                    // move to the left and fling to the right will register as a fling to the right.

                    if (((isSignificantMove && !isDeltaLeft && !isFling) ||
                            (isFling && !isVelocityLeft))
//                            && mCurrentPage > 0 //切到0时继续走这里,finalPage = -1
                    ) {
                        finalPage = returnToOriginalPage
                                ? mCurrentPage : mCurrentPage - getPanelCount();
                        snapToPageWithVelocity(finalPage, velocity);
                    } else if (((isSignificantMove && isDeltaLeft && !isFling) ||
                            (isFling && isVelocityLeft))
//                            &&mCurrentPage < getChildCount() - 1 //切到最后一页时继续切页,finalPage = 4
                    ) {
                        finalPage = returnToOriginalPage
                                ? mCurrentPage : mCurrentPage + getPanelCount();
                        snapToPageWithVelocity(finalPage, velocity);
                    } else {
                        snapToDestination();
                    }
}

上面修改后,进入snapToPageWithVelocity(finalPage, velocity);这个方法的finalPage值在循环切页时就会超出 0 1 2,变成 -1 或者4。那么我们需要在snapToPageWithVelocity中继续处理一下

切页最终会调用到protected boolean snapToPage(int whichPage, int delta, int duration, boolean immediate)方法,
whichPage和delta是分开的,这就让0到-1(2)、2 - 3(0)成为可能。
因为scroll本身是一条线,mScroller.startScroll(mOrientationHandler.getPrimaryScroll(this), 0, delta, 0, duration);关键的2个参数是whichPage和delta。
假设 0 到 -1切页
我们可以给whichPage传入2,给delta传入0到-1的值,在切页结束后,再把页面瞬移到最后一页的scroll值。
这样就完成了循环切页,并且保证whichPage和delta最终结果正确。

    protected boolean snapToPageWithVelocity(int whichPage, int velocity) {
	    //缓慢滑动
        if (Math.abs(velocity) < mMinFlingVelocity) {
            // If the velocity is low enough, then treat this more as an automatic page advance
            // as opposed to an apparent physical response to flinging
            return snapToPage(whichPage, mPageSnapAnimationDuration);
        }
		//快速滑动
        Log.d(TAG," snapToPageWithVelocity whichPage 111: "+whichPage);
        //循环切页页面数修正
        boolean isLoopLeft = false;
        boolean isLoopRight = false;
        if (whichPage ==-1){
            whichPage = getPageCount() -1;
            isLoopLeft = true;
        }
        if (whichPage == getPageCount()){
            whichPage = 0;
            isLoopRight = true;
        }

        Log.d(TAG," snapToPageWithVelocity whichPage 222: "+whichPage);
        whichPage = validateNewPage(whichPage);
        int halfScreenSize = mOrientationHandler.getMeasuredSize(this) / 2;
		//关键在这里,newLoc的值
        int newLoc = getScrollForPage(whichPage,isLoopLeft,isLoopRight);

        int delta = newLoc - mOrientationHandler.getPrimaryScroll(this);
        Log.d(TAG," snapToPageWithVelocity whichPage 666 delta: "+delta);
        int duration = 0;
	}
	//重写getScrollForPage方法,根据isLoopLeft和isLoopRight计算滚动坐标
    public int getScrollForPage(int index ,boolean isLoopLeft,boolean isLoopRight) {
        Log.d(TAG," getScrollForPage 111  index: "+index);
        if (isLoopLeft){
            Log.d(TAG," getScrollForPage 222  index: "+index);
            return -mPageScrolls[1];
        }
        if (isLoopRight){
            Log.d(TAG," getScrollForPage 333  index: "+index);
            return mPageScrolls[1] * (mPageScrolls.length) ;
        }
        return getScrollForPage(index);
    }
    public int getScrollForPage(int index) {
        // TODO(b/233112195): Use !pageScrollsInitialized() instead of mPageScrolls == null, once we
        // root cause where we should be using runOnPageScrollsInitialized().
        if (mPageScrolls == null || index >= mPageScrolls.length || index < 0) {
            return 0;
        } else {
            return mPageScrolls[index];
        }
    }

缓慢滑动直接调用的return snapToPage(whichPage, mPageSnapAnimationDuration);
还需要额外处理一下滚动坐标

    protected boolean snapToPage(int whichPage, int duration, boolean immediate) {
    	//循环切页页面数修正
    	//这段代码很蠢,快速滑动和缓慢滑动有相同的逻辑,但是没有提炼出来,写了两遍
        Log.d(TAG," snapToPage whichPage 111: "+whichPage);
        boolean isLoopLeft = false;
        boolean isLoopRight = false;
        if (whichPage ==-1){
            whichPage = getPageCount() -1;
            isLoopLeft = true;
        }
        if (whichPage == getPageCount()){
            whichPage = 0;
            isLoopRight = true;
        }
        Log.d(TAG," snapToPage whichPage 222: "+whichPage);
        whichPage = validateNewPage(whichPage);
        Log.d(TAG," snapToPage whichPage 333: "+whichPage);
		//关键在这里,newLoc的值
        int newLoc = getScrollForPage(whichPage,isLoopLeft,isLoopRight);
        final int delta = newLoc - mOrientationHandler.getPrimaryScroll(this);
        Log.d(TAG," snapToPage whichPage 666 delta: "+delta);
        return snapToPage(whichPage, delta, duration, immediate);
    }
4、onPageEndTransition 页面切换结束后,修正scroll值

packages\apps\Launcher3\src\com\android\launcher3\Workspace.java

    protected void onPageEndTransition() {
        super.onPageEndTransition();
        updateChildrenLayersEnabled();

        if (mDragController.isDragging()) {
            if (workspaceInModalState()) {
                // If we are in springloaded mode, then force an event to check if the current touch
                // is under a new page (to scroll to)
                mDragController.forceTouchMove();
            }
        }

        if (mStripScreensOnPageStopMoving) {
            stripEmptyScreens();
            mStripScreensOnPageStopMoving = false;
        }

        // Inform the Launcher activity that the page transition ended so that it can react to the
        // newly visible page if it wants to.
        mLauncher.onPageEndTransition();
        //页面切换结束后,修正scroll值
        Log.d(TAG," snapToPage whichPage 777 getNextPage(): "+getNextPage()+" getScrollX(): "+getScrollX()+"  "+mMaxScroll+"  "+mMinScroll);
        if(getScrollX()< mMinScroll || getScrollX() > mMaxScroll){
            Log.e(TAG," snapToPage snapToPageImmediately 888 getNextPage(): "+getNextPage());
            snapToPageImmediately(getNextPage());
        }
    }

以上基本上完成了循环切页的功能。

5、循环切页不跟手

假设0 到-1切页,0页继续向右滑动,可以跟手,但是向左滑动页面不动。
排查滑动问题。
发现workspace中dispatchDraw里面的getScrollX拿到的值不变。

滚动值是PagedView#scrollTo回调回来的。怀疑PagedView#onTouchEvent 中move时传入的值有问题。
打断点发现走入了边缘回弹逻辑,delta值被改了。

                float direction = mOrientationHandler.getPrimaryValue(dx, dy);
                float delta = mLastMotion + mLastMotionRemainder - direction;
                Log.d(TAG," ACTION_MOVE 111  delta: "+delta);
                int width = getWidth();
                int height = getHeight();
                int size = mOrientationHandler.getPrimaryValue(width, height);

                final float displacement = mOrientationHandler.getSecondaryValue(dx, dy)
                        / mOrientationHandler.getSecondaryValue(width, height);
                mTotalMotion += Math.abs(delta);

                if (mAllowOverScroll) {
                		//注释掉边缘回弹效果的坐标修正
//                    float consumed = 0;
//                    if (delta < 0 && mEdgeGlowRight.getDistance() != 0f) {
//                        consumed = size * mEdgeGlowRight.onPullDistance(delta / size, displacement);
//                    } else if (delta > 0 && mEdgeGlowLeft.getDistance() != 0f) {
//                        consumed = -size * mEdgeGlowLeft.onPullDistance(
//                                -delta / size, 1 - displacement);
//                    }
//                    delta -= consumed;
                }
                delta /= mOrientationHandler.getPrimaryScale(this);
                Log.d(TAG," ACTION_MOVE 222  delta: "+delta);
                // Only scroll and update mLastMotionX if we have moved some discrete amount.  We
                // keep the remainder because we are actually testing if we've moved from the last
                // scrolled position (which is discrete).
                mLastMotion = direction;
                int movedDelta = (int) delta;
                mLastMotionRemainder = delta - movedDelta;

                if (delta != 0) {
                    Log.d(TAG," ACTION_MOVE movedDelta: "+movedDelta);
                    mOrientationHandler.setPrimary(this, VIEW_SCROLL_BY, movedDelta);

尾注

以上基本上实现了循环切页功能。自己写的demo功能,自测ok了,有bug后面再改。

bug1:快速切页的时候,比如从 0 到-1 ,再从-1 到0. 因为scroll了负的位置,在onPageEndTransition才去修复到正常坐标,连续的滑动会在错的坐标上去滑动,导致松手后滚动动画异常。


http://www.kler.cn/news/147927.html

相关文章:

  • SQLITE 日期格式转换
  • Hands-on Machine Learning with Scikit-Learn,Keras TensorFlow
  • 【Kotlin精简】第9章 Kotlin Flow
  • 算法刷题-动态规划3(未完待续---------
  • C++初阶(十二)string的模拟实现
  • openGauss学习笔记-130 openGauss 数据库管理-参数设置-重设参数
  • 美创科技受邀亮相第二届全球数字贸易博览会
  • 008 OpenCV matchTemplate 模板匹配
  • 【UGUI】中Content Size Fitter)组件-使 UI 元素适应其内容的大小
  • 【嵌入式】开源shell命令行的移植和使用(2)——letter-shell
  • 贪心 376. 摆动序列
  • 顶象s_v3滑块
  • 设计师不可错过的免费素材网站,快快收藏!
  • 数据结构与算法之美代码:二分查找2
  • 分享几个可以免费使用GPT的网站
  • docker常见问题汇总
  • 建筑木模板厂家批发
  • Linux fork笔试练习题
  • C++STL——string类详解及其模拟实现
  • 【深度学习】学习率及多种选择策略
  • 前端学习网站推荐
  • c/c++ header_only 头文件实现的关键点
  • Spring加载Bean的多种方式
  • 红旗Asianux Server Linux V8 安装万里数据库(GreatSQL)
  • Spring Cloud,注册中心,配置中心,原理详解
  • 社区新零售:重塑零售业的全新模式
  • 使用Python+Redis实现文章投票网站后端功能
  • 【文献阅读笔记】关于GANomaly的异常检测方法
  • 【开源威胁情报挖掘1】引言 + 开源威胁情报挖掘框架 + 开源威胁情报采集与识别提取
  • C#,《小白学程序》第十九课:随机数(Random)第六,随机生成任意长度的大数(BigInteger)