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

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重写@Overridepublic 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

@Overrideprotected 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为0int 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);}}}}//判断非临界条件下所在的屏幕,如果是//临界则返回falseprivate 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 flingingreturn 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.lryc.cn/news/246434.html

相关文章:

  • Java学习路线第一篇:Java基础(2)
  • 网络工程师精华篇,50种网络故障及解决方法大集合
  • Unity播放网络视频
  • SCI一区级 | Matlab实现GWO-CNN-LSTM-selfAttention多变量多步时间序列预测
  • 线性分类器--图像表示
  • 车载通信架构 —— 传统车内通信网络FlexRay(较高速度高容错、较灵活拓扑结构)
  • 如何在Ubuntu的Linux系统中安装MySQL5.7数据库
  • 基于Hadoop的区块链海量数据存储的设计与实现
  • 运行时错误/缺陷到底是什么缺陷
  • 应用Web3.0的5种方法提升你的点击量
  • 计算机服务器中了mallox勒索病毒如何处理,mallox勒索病毒解密文件恢复
  • 408—电子笔记分享
  • 【每日一题】子数组的最小值之和
  • 【docker】docker总结
  • [英语学习][3][Word Power Made Easy]的精读与翻译优化
  • 使用UIActivityViewController分享图片,没有preview
  • linux安装终端连接工具Tabby
  • Linux telnet命令详解:通过TCP/IP网络连接与管理远程机器(附实例教程和注意事项)
  • linux 磁盘管理、分区管理常用命令
  • Milvus入门手册1.0
  • PCL 计算两点云之间的最小距离
  • 基于YOLOv5的视频计数 — 汽车计数实现
  • jetson nano 串口通信
  • Vue基础入门(三):Vue3的使用
  • 基于M估计样本一致性算法的点云平面拟合
  • 【VRTK】【VR开发】【Unity】8-可交互对象
  • Huggingface 超详细介绍
  • pycharm 怎么切换Anaconda简单粗暴
  • 笔记二十二、使用路由state进行传递参数
  • 2023 OI 总结