当前位置: 首页 > 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才去修复到正常坐标,连续的滑动会在错的坐标上去滑动,导致松手后滚动动画异常。

相关文章:

Android13 launcher循环切页

launcher 常规切页&#xff1a;https://blog.csdn.net/a396604593/article/details/125305234 循环切页 我们知道&#xff0c;launcher切页是在packages\apps\Launcher3\src\com\android\launcher3\PagedView.java的onTouchEvent中实现的。 1、滑动限制 public boolean onT…...

Java学习路线第一篇:Java基础(2)

这篇则分享Java学习路线第一part&#xff1a;Java基础&#xff08;2&#xff09; 从看到这篇内容开始&#xff0c;你就是被选定的天命骚年&#xff0c;将承担起学完Java基础的使命&#xff0c;本使命为单向契约&#xff0c;你可选择YES或者选择YES。 具体路线安排&#xff1a…...

网络工程师精华篇,50种网络故障及解决方法大集合

上午好&#xff0c;我的网工朋友。 做网络工程师&#xff0c;自然离不开网络&#xff0c;而日常工作中能搞多少大项目&#xff1f;最常见的其实还是网络故障的处理了。 怎么最高效地排查网络故障&#xff1f;怎么简单几招通网&#xff1f; 今天就从基础的入手&#xff0c;分…...

Unity播放网络视频

using System.Collections; using System.Collections.Generic; using UnityEngine; using Mx.UI; using Mx.Utils; using UnityEngine.UI; using UnityEngine.Video; /// <summary> 视频UI面板 </summary> public class VideoUIForm : BaseUIForm { private …...

SCI一区级 | Matlab实现GWO-CNN-LSTM-selfAttention多变量多步时间序列预测

SCI一区级 | Matlab实现GWO-CNN-LSTM-selfAttention多变量多步时间序列预测 目录 SCI一区级 | Matlab实现GWO-CNN-LSTM-selfAttention多变量多步时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.Matlab实现GWO-CNN-LSTM-selfAttention灰狼算法优化卷积长短…...

线性分类器--图像表示

整个模型 图像表示 二进制图像 灰度图像 彩色图像 大多数分类算法都要求输入向量&#xff01; rbg的图像矩阵转列向量 大小为 32X32 的话&#xff0c;图像矩阵转列向量是多少维&#xff1f; 32x32x3 3072 维列向量...

车载通信架构 —— 传统车内通信网络FlexRay(较高速度高容错、较灵活拓扑结构)

车载通信架构 —— 传统车内通信网络FlexRay(较高速度高容错、较灵活拓扑结构) 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,…...

如何在Ubuntu的Linux系统中安装MySQL5.7数据库

前往MySQL数据库官网链接地址下载5.7数据库。 MySQL :: Download MySQL Community Server (Archived Versions)使用ssh的可视化工具将下载的mysql-5.7.40-linux-glibc2.12-x86_64.tar.gz文件上传到Linux服务器&#xff0c;并解压文件 tar -zxvf mysql-5.7.40-linux-glibc2.12-x…...

基于Hadoop的区块链海量数据存储的设计与实现

点我完整下载&#xff1a;基于Hadoop的区块链海量数据存储的设计与实现.docx 基于Hadoop的区块链海量数据存储的设计与实现 Design and Implementation of Mass Data Storage for Blockchain based on Hadoop 目录 目录 2 摘要 3 关键词 4 第一章 引言 4 1.1 研究背景 4 1.2 研…...

运行时错误/缺陷到底是什么缺陷

运行时错误(Run-time Error)是一种跟程序运行状态相关的缺陷。这类缺陷不能通过直接禁用相关特性来屏蔽&#xff0c;而是需要通过分析变量的数值状态来发现可能的异常。简单来说&#xff0c;这些缺陷通常只有当程序执行起来以后&#xff0c;才能逐渐暴露出的缺陷&#xff0c;一…...

应用Web3.0的5种方法提升你的点击量

Web3.0早已成为互联网的全新方向标&#xff0c;为用户带来全新的手机上网感受。它也变成吸引住点击量疯涨的秘密武器。我们将要详细介绍Web3.0的五种使用方法&#xff0c;帮助你更好的了解并应用Web3.0技术性&#xff0c;以提升你的点击量。 1.可靠的身份认证Web3.0技术性提供了…...

计算机服务器中了mallox勒索病毒如何处理,mallox勒索病毒解密文件恢复

科技技术的发展推动了企业的生产运营&#xff0c;网络技术的不断应用&#xff0c;极大地方便了企业日常生产生活&#xff0c;但网络毕竟是一把双刃剑&#xff0c;网络安全威胁一直存在&#xff0c;近期&#xff0c;云天数据恢复中心接到很多企业的求助&#xff0c;企业的计算机…...

408—电子笔记分享

一、笔记下载 链接&#xff1a;https://pan.baidu.com/s/1bFz8IX6EkFMWTfY9ozvVpg?pwddeng 提取码&#xff1a;deng b站视频&#xff1a;408-计算机网络-笔记分享_哔哩哔哩_bilibili 包含了408四门科目&#xff08;数据结构、操作系统、计算机组成原理、计算机网络&#xff09…...

【每日一题】子数组的最小值之和

文章目录 Tag题目来源题目解读解题思路方法一&#xff1a;贡献法单调栈 写在最后 Tag 【贡献法】【单调栈】【数组】【2023-11-27】 题目来源 907. 子数组的最小值之和 题目解读 计算整数数组的连续子数组中最小值的和。 解题思路 本题朴素的解决思想是求出所有的连续子数组…...

【docker】docker总结

一、Docker简介 Docker是开源应用容器引擎&#xff0c;轻量级容器技术。基于Go语言&#xff0c;并遵循Apache2.0协议开源Docker可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中&#xff0c;然后发布到任何流行的Linux系统上&#xff0c;也可以实现虚拟化容…...

[英语学习][3][Word Power Made Easy]的精读与翻译优化

[序言] 这次翻译校验, 难度有点大, 原版中英翻译已出现了严重地偏差. 昨晚11点开始阅读如下段落, 花费了1个小时也没有理解原作者的核心表达, 索性睡觉了. 今早学习完朗文单词之后, 9点半开始继续揣摩. 竟然弄到了中午11点30, 终于明白原作者要表达的意思了. 废话不多说&#x…...

使用UIActivityViewController分享图片,没有preview

以前都是用第三方sdk来分享的&#xff0c;最近使用官方的UIActivityViewController来做分享&#xff0c;结果分享图片的时候preview不了分享的图片。 自定义一个继承UIActivityItemProvider的类。关于分享的内容自定义可以自己实现UIActivityItemSource这个协议。首先看看协议的…...

linux安装终端连接工具Tabby

参考&#xff1a;https://zhuanlan.zhihu.com/p/645787655...

Linux telnet命令详解:通过TCP/IP网络连接与管理远程机器(附实例教程和注意事项)

Linux telnet命令介绍 telnet命令&#xff0c;全称为teletype network&#xff0c;是一个使用telnet网络协议来连接并管理远程机器的命令。它通过TCP/IP网络使用端口23来建立连接&#xff0c;并提供了一种使用命令行界面&#xff08;CLI&#xff09;管理远程系统的方式。虽然t…...

linux 磁盘管理、分区管理常用命令

文章目录 基础命令挂载新硬盘/分区添加内存交换分区swaplvm分区管理模式 基础命令 查看目录文件大小 du -sh /backup du -sh /backup/* du -sh *查看磁盘挂载信息 df -lhT查看某个目录挂载在哪个分区&#xff0c;以及分区的磁盘使用情况 df [目录] #例如&#xff1a;df /ho…...

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…...

脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)

一、数据处理与分析实战 &#xff08;一&#xff09;实时滤波与参数调整 基础滤波操作 60Hz 工频滤波&#xff1a;勾选界面右侧 “60Hz” 复选框&#xff0c;可有效抑制电网干扰&#xff08;适用于北美地区&#xff0c;欧洲用户可调整为 50Hz&#xff09;。 平滑处理&…...

DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径

目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...

Admin.Net中的消息通信SignalR解释

定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...

3.3.1_1 检错编码(奇偶校验码)

从这节课开始&#xff0c;我们会探讨数据链路层的差错控制功能&#xff0c;差错控制功能的主要目标是要发现并且解决一个帧内部的位错误&#xff0c;我们需要使用特殊的编码技术去发现帧内部的位错误&#xff0c;当我们发现位错误之后&#xff0c;通常来说有两种解决方案。第一…...

linux arm系统烧录

1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 &#xff08;忘了有没有这步了 估计有&#xff09; 刷机程序 和 镜像 就不提供了。要刷的时…...

【AI学习】三、AI算法中的向量

在人工智能&#xff08;AI&#xff09;算法中&#xff0c;向量&#xff08;Vector&#xff09;是一种将现实世界中的数据&#xff08;如图像、文本、音频等&#xff09;转化为计算机可处理的数值型特征表示的工具。它是连接人类认知&#xff08;如语义、视觉特征&#xff09;与…...

JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案

JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停​​ 1. ​​安全点(Safepoint)阻塞​​ ​​现象​​:JVM暂停但无GC日志,日志显示No GCs detected。​​原因​​:JVM等待所有线程进入安全点(如…...

R语言速释制剂QBD解决方案之三

本文是《Quality by Design for ANDAs: An Example for Immediate-Release Dosage Forms》第一个处方的R语言解决方案。 第一个处方研究评估原料药粒径分布、MCC/Lactose比例、崩解剂用量对制剂CQAs的影响。 第二处方研究用于理解颗粒外加硬脂酸镁和滑石粉对片剂质量和可生产…...

【分享】推荐一些办公小工具

1、PDF 在线转换 https://smallpdf.com/cn/pdf-tools 推荐理由&#xff1a;大部分的转换软件需要收费&#xff0c;要么功能不齐全&#xff0c;而开会员又用不了几次浪费钱&#xff0c;借用别人的又不安全。 这个网站它不需要登录或下载安装。而且提供的免费功能就能满足日常…...