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后面再改。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/166746.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/166746.shtml
英文地址,請注明出處:http://en.pswp.cn/news/166746.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Python與設計模式--門面模式

8-Python與設計模式–門面模式 一、火警報警器&#xff08;1&#xff09; 假設有一組火警報警系統&#xff0c;由三個子元件構成&#xff1a;一個警報器&#xff0c;一個噴水器&#xff0c; 一個自動撥打電話的裝置。其抽象如下&#xff1a; class AlarmSensor:def run(self):…

c語言習題1124

分別定義函數求圓的面積和周長。 寫一個函數&#xff0c;分別求三個數當中的最大數。 寫一個函數&#xff0c;計算輸入n個數的乘積 一個判斷素數的函數&#xff0c;在主函數輸入一個整數&#xff0c;輸出是否為素數的信息 寫一個函數求n! ,利用該函數求1&#xff01;2&…

功率半導體器件CV測試系統

概述 電容-電壓(C-V)測量廣泛用于測量半導體參數&#xff0c;尤其是MOS CAP和MOSFET結構。MOS(金屬-氧化物-半導體)結構的電容是外加電壓的函數&#xff0c;MOS電容隨外加電壓變化的曲線稱之為C-V曲線&#xff08;簡稱C-V特性&#xff09;&#xff0c;C-V 曲線測試可以方便的確…

opencv-使用 Haar 分類器進行面部檢測

Haar 分類器是一種用于對象檢測的方法&#xff0c;最常見的應用之一是面部檢測。Haar 分類器基于Haar-like 特征&#xff0c;這些特征可以通過計算圖像中的積分圖來高效地計算。 在OpenCV中&#xff0c;Haar 分類器被廣泛用于面部檢測。以下是一個簡單的使用OpenCV進行面部檢測…

鴻蒙系統使用hdc_std.exe使用身份證讀卡器等外設USB獲得權限方法

hdc_std.exe是OpenHarmony 的命令行工具&#xff0c;由于使用的開源鴻蒙開發板上面沒有文件管理器&#xff0c;所以無法通過U盤等方式進行安裝.hap應用。 下面是使用hdc_std.exe安裝身份證讀卡器的步驟&#xff1a; 1、hdc_std.exe放桌面&#xff0c;然后WINR&#xff0c;打開…

CBTC 2023氫能展倒計時6天,最新同期會議活動Plus版發布

隨著時間的推移&#xff0c;CBTC2023深圳氫能技術展覽會即將拉開序幕。這場盛會將于11月30日在深圳福田會展中心盛大開幕&#xff0c;以“以儲賦能&#xff0c;智造未來”為主題&#xff0c;旨在搭建一個商務交流、供需合作、創新產品發布的平臺&#xff0c;讓氫能全產業鏈之間…

尋找質數 II

題目描述 輸入兩個整數 a&#xff0c;b&#xff0c;計算并輸出小于 a 的 b個質數&#xff0c;所有符合條件的質數里&#xff0c;輸出最大的 b 個質數&#xff0c;按照從大到小輸出&#xff0c;使用空格隔開。 假如符合條件的數量不夠&#xff0c;則輸出已經滿足的質數。 如果…

詳解Java中的異常體系機構(throw,throws,try catch,finally)

目錄 一.異常的概念 二.異常的體系結構 三.異常的處理 異常處理思路 LBYL&#xff1a;Look Before You Leap EAFP: Its Easier to Ask Forgiveness than Permission 異常拋出throw 異常的捕獲 提醒聲明throws try-catch捕獲處理 finally的作用 四.自定義異常類 一.異…

微信小程序:This Mini Program cannot be opened as your Weixin version is out-of-date.

項目場景&#xff1a; 問題描述 升級基礎庫3.2.0&#xff0c;然后PC端整個小程序都打不開了&#xff0c;點擊小程序提示”This Mini Program cannot be opened as your Weixin version is out-of-date. Update Weixin to the latest version.“&#xff0c;并且點擊Update Wei…

一個悄然崛起的國產軟件!!AI 又進化了!!

大家好&#xff0c;我是 Jack。 AI 寫代碼想必很多人都體驗過了&#xff0c;使用 AI 編程工具是一個大趨勢&#xff0c;越早學會使用 AI 輔助你寫代碼&#xff0c;你的效率也會越高。 甚至有些公司已經要求員工具備 AI 編程能力。 對于學生黨&#xff0c;AI 編程可以幫助我們…

MyBatisPlus總結

MyBatis-Plus時Mybatis的Best Partner MyBatis-Plus (opens new window)&#xff08;簡稱 MP&#xff09;是一個 MyBatis (opens new window)的增強工具&#xff0c;在 MyBatis 的基礎上只做增強不做改變&#xff0c;為簡化開發、提高效率而生。 特性 無侵入損耗小強大的 CR…

Android開發從0開始(廣播)

應用廣播 發送標準廣播的三步驟 發送標準廣播&#xff1a; //發送標準廣播 Intent intent new Intent("com.dongnaoedu.chapter09.standard"); sendBroadcast(intent); 定義廣播接受者: public class StanderdReceiver extends BroadcastReceiver { public s…

在ASP.NET Core 中使用 .NET Aspire 消息傳遞組件

前言 云原生應用程序通常需要可擴展的消息傳遞解決方案&#xff0c;以提供消息隊列、主題和訂閱等功能。.NET Aspire 組件簡化了連接到各種消息傳遞提供程序&#xff08;例如 Azure 服務總線&#xff09;的過程。在本教程中&#xff0c;小編將為大家介紹如何創建一個 ASP.NET …

PLC通過RS232轉PROFINET與電子分析天平秤通訊案例

本案例是通過用興達易控的XD-PNR200型RS232轉Profinet網關連接電子分析天平秤與PLC通訊的配置案例&#xff0c;用到設備為西門子S7-1200PLC&#xff0c;RS232轉Profinet網關&#xff0c;電子分析天平秤。 打開博圖&#xff0c;添加PLC&#xff1b;本案例使用的是1200PLC。 添加…

『接口測試干貨』| Newman+Postman接口自動化測試完整過程

『接口測試干貨』| NewmanPostman接口自動化測試完整過程 1 Newman簡介2 如何安裝Newman&#xff1f;2.1 安裝NodeJs2.2 安裝Newman2.2 解決Newman不是內部命令 3 Newman使用3.1 Newman如何運行集合&#xff1f;3.2 如何查看幫助文檔&#xff1f;3.3 環境變量設置3.4 關于全局變…

微信小程序制作

如果你也想搭建一個小程序&#xff0c;但不知道如何入手&#xff0c;那么今天我就教你如何使用第三方制作平臺&#xff0c;在短短三十分鐘內搭建一個小程序。 一、登錄小程序制作平臺 首先&#xff0c;登錄到小程序制作平臺的官方網站或應用程序&#xff0c;進入后臺管理系統。…

【Oracle OCP考試】1z0-082(4)

1.Which two statements are true about the PMON background process? A. It rolls back transactions when a process fails&#xff08;當進程失敗時&#xff0c;它回滾事務&#xff09; B. It registers database services with all local and remote listeners known to…

文章解讀與仿真程序復現思路——電網技術 EI\CSCD\北大核心《考慮5G基站儲能可調度容量的有源配電網協同優化調度方法》

這篇文章的標題涉及到以下關鍵概念&#xff1a; 5G基站&#xff1a; 提到了5G基站&#xff0c;這表明文章的焦點可能是與第五代移動通信技術相關的內容。5G技術對于提高通信速度、降低延遲以及支持大規模連接等方面有顯著的改進&#xff0c;因此對于基站的電力需求和供應可能存…

2023年ESG投資研究報告

第一章 ESG投資概況 1.1 定義 ESG投資&#xff0c;亦稱負責任投資&#xff0c;是一種融合環境&#xff08;Environment&#xff09;、社會&#xff08;Social&#xff09;和治理&#xff08;Governance&#xff09;考量的投資方法&#xff0c;旨在通過綜合這些因素來優化投資…

美國汽車零部件巨頭 AutoZone 遭遇網絡攻擊

Security Affairs 網站披露&#xff0c;美國汽車配件零售商巨頭 AutoZone 稱其成為了 Clop MOVEit 文件傳輸網絡攻擊的受害者&#xff0c;導致大量數據泄露。 AutoZone 是美國最大的汽車零配件售后市場經銷商之一&#xff0c;在美國、墨西哥、波多黎各、巴西和美屬維爾京群島經…