Android 三級(jí)NestedScroll嵌套滾動(dòng)實(shí)踐
嵌套滾動(dòng)介紹
我們知道 NestedScrolling(Parent/Child) 這對(duì)接口是用來(lái)實(shí)現(xiàn)嵌套滾動(dòng)的,一般實(shí)現(xiàn)這對(duì)接口的 Parent 和 Child 沒(méi)有直接嵌套,否則直接用 onInterceptTouchEvent() 和 onTouchEvent() 這對(duì)方法實(shí)現(xiàn)就可以了。能夠越級(jí)嵌套滾動(dòng)正是它的厲害之處。
嵌套滾動(dòng)的接口有兩對(duì):NestedScrolling(Parent/Child) 和 NestedScrolling(Parent2/Child2) 后者相比前者對(duì) fling 的處理更加細(xì)致。相比第一代 Child 簡(jiǎn)單地將 fling 拋給 Parent,第二代 Child 將 fling 轉(zhuǎn)化為 scroll 后再分發(fā)給 Parent,為了和普通的 scroll 區(qū)分增加了一個(gè)參數(shù) type, 當(dāng) type 是 ViewCompat.TYPE_TOUCH 時(shí)表示普通的 scroll,當(dāng)是 ViewCompat.TYPE_NON_TOUCH 時(shí)表示由 fling 轉(zhuǎn)化而來(lái)的 scroll。這樣做的好處是當(dāng) Child 檢測(cè)到一個(gè) fling 時(shí),它可以選擇將這個(gè) fling 引起的 scroll 一部分作用在 Parent 上一部分作用在自己身上,而不是只作用在 Parent 或者 Child 上?;蛟S你會(huì)問(wèn) fling 為什么不能選擇 Parent 和 Child 都作用,事實(shí)上你可以,但 fling 的話(huà) Parent 沒(méi)法告訴 Child 消費(fèi)了多少,剩下多少,因?yàn)?fling 傳遞的值是速度,不像 scroll 是距離。所以通過(guò) NestedScrolling(Parent2/Child2) 實(shí)現(xiàn)嵌套滾動(dòng)時(shí),當(dāng)你觸發(fā)了一個(gè) fling 時(shí),也可以做很順滑連貫的交替滾動(dòng),而 1 就很難達(dá)到相同的效果?,F(xiàn)在官方 View 的實(shí)現(xiàn)也是通過(guò) NestedScrolling(Parent2/Child2),所以我們?cè)趯?shí)現(xiàn)自定義的嵌套滾動(dòng)時(shí)盡量用 2。
上面簡(jiǎn)單介紹了 NestedScrolling 2 和 1 的區(qū)別以及為什么要使用2?,F(xiàn)在我們來(lái)看看 NestedScrolling(Parent2/Child2) 的方法,1 就不看了,和 2 差不多。
public interface NestedScrollingChild2 { void setNestedScrollingEnabled(boolean enabled); boolean isNestedScrollingEnabled(); boolean startNestedScroll(@ScrollAxis int axes, @NestedScrollType int type); void stopNestedScroll(@NestedScrollType int type); boolean hasNestedScrollingParent(@NestedScrollType int type); boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow, @NestedScrollType int type); boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed, @Nullable int[] offsetInWindow, @NestedScrollType int type); }
public interface NestedScrollingParent2 { boolean onStartNestedScroll(@NonNull View child, @NonNull View target, @ScrollAxis int axes, @NestedScrollType int type); void onNestedScrollAccepted(@NonNull View child, @NonNull View target, @ScrollAxis int axes, @NestedScrollType int type); void onStopNestedScroll(@NonNull View target, @NestedScrollType int type); void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, @NestedScrollType int type); void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed, @NestedScrollType int type); }
從這兩個(gè)接口的方法可以看出這些方法都是一一對(duì)應(yīng)的,比如 startNestedScroll 和 onStartNestedScroll,stopNestedScroll 和 onStopNestedScroll 等。從這些方法的命名上也能看出來(lái)嵌套滾動(dòng)的交互順序是 Child 主動(dòng)觸發(fā),Parent 被動(dòng)接受,所以決定是否打開(kāi)嵌套滾動(dòng)的方法 setNestedScrollingEnabled 由 Child 實(shí)現(xiàn),決定開(kāi)始和結(jié)束的方法 startNestedScroll 和 stopNestedScroll 也由 Child 實(shí)現(xiàn)。
這里用一個(gè)圖來(lái)表示嵌套滾動(dòng)流程
整個(gè)過(guò)程大概分為兩部分:綁定和滾動(dòng)分發(fā)。綁定部分可以理解為 Child 向上遍歷找 NestedScrollingParent2 的過(guò)程,找到后調(diào)用它的 onStartNestedScroll 方法,如果返回 true 則說(shuō)明這個(gè) Parent 想接收 nested scroll,Child 會(huì)緊接著調(diào) onNestedScrollAccepted 方法表示同意 Parent 處理自己分發(fā)的 nested scroll,對(duì)應(yīng)上圖中的 1 2 3。滾動(dòng)分發(fā)部分 Child 將自己的 scroll 分為三個(gè)階段 before scroll after,before 和 after 分發(fā)給 parent 消費(fèi),scroll 階段讓自己消費(fèi),這三個(gè)階段是按順序進(jìn)行的,換句話(huà)說(shuō)如果前一步消耗完了 scroll,那后面的階段就沒(méi)有 scroll 可以消費(fèi)。這樣做的好處是讓 Parent 可以在自己消費(fèi)之前或者之后消費(fèi) scroll,如果 Parent 想在 Child 之前消費(fèi)就在 onNestedPreScroll 方法里處理,否則就在 onNestedScroll 方法里,對(duì)應(yīng)上圖中的 4 5 步。上面介紹到的一些通用邏輯被封裝在 NestedScrollingChildHelper 和 NestedScrollingParentHelper 中,在 NestedScrolling(Parent2/Child2) 的方法中可以調(diào)用 Helper 類(lèi)中的同名方法,比如 NestedScrollingChild2.startNestedScroll 方法中實(shí)現(xiàn)了向上遍歷尋找 NestedScrollingParent 的邏輯。
三級(jí)嵌套滾動(dòng)
一個(gè)常見(jiàn)的嵌套滾動(dòng)例子是 CoordinatorLayout/AppbarLayout - RecyclerView, 實(shí)現(xiàn)的效果是向上滑動(dòng)列表時(shí),會(huì)先將 AppbarLayout 向上滑動(dòng)直到完全折疊,向下滑動(dòng)至列表最頂部后會(huì)展開(kāi) AppbarLayout, 如下圖:
這里實(shí)現(xiàn) NestedScrollingParent2 的是 CoordinatorLayout/AppbarLayout, 實(shí)現(xiàn) NestedScrollingChild2 的是 RecyclerView。對(duì)于這種兩級(jí)嵌套滾動(dòng)的需求使用 CoordinatorLayout 幾乎都能實(shí)現(xiàn),如果遇到特殊的業(yè)務(wù)需求基于 CoordinatorLayout 和 RecyclerView 的實(shí)現(xiàn)改改也能實(shí)現(xiàn)。
我這里遇到的需求是即刻首頁(yè)的樣式(可參考即刻5.4.2版本),除了要有 AppbarLayout 折疊效果之外還要在 AppbarLayout 頂部展示搜索框和刷新動(dòng)畫(huà)。這里的滑動(dòng)邏輯是:
- 向上滑動(dòng)時(shí),最先折疊刷新動(dòng)畫(huà),向下滑動(dòng)時(shí)最后展開(kāi)刷新動(dòng)畫(huà)。
- 向上滑動(dòng)列表時(shí)先折疊 AppbarLayout,AppbarLayout 完全折疊后再折疊搜索框。
- 向下滑動(dòng)列表時(shí)在展開(kāi) AppbarLayout 之前先展開(kāi)搜索框。
- 列表沒(méi)滑動(dòng)到頂部時(shí)可以通過(guò)觸發(fā)一定速度的向下 fling 來(lái)展開(kāi)搜索框。
可以發(fā)現(xiàn)這里除了 CoordinatorLayout/AppbarLayout - RecyclerView 這對(duì)嵌套滾動(dòng)的 Parent 和 Child 之外還多了搜索框和刷新動(dòng)畫(huà),而這三者之間的滑動(dòng)邏輯需要通過(guò)嵌套滾動(dòng)實(shí)現(xiàn),只是傳統(tǒng)的兩級(jí)嵌套滾動(dòng)不能滿(mǎn)足,所以需要實(shí)現(xiàn)三級(jí)嵌套滾動(dòng)。
所謂三級(jí)嵌套滾動(dòng)是在兩級(jí)嵌套滾動(dòng)之上再添加一個(gè) Parent,這里為了表述方便將三級(jí)嵌套滾動(dòng)的三級(jí)由上到下分別稱(chēng)為 Grand Parent Child。具體是由兩對(duì) NestedScrolling(Parent2/Child2) 接口實(shí)現(xiàn),Grand 實(shí)現(xiàn)第一對(duì)接口的 Parent,Parent 實(shí)現(xiàn)第一對(duì)接口的 Child 和第二對(duì)接口的 Parent,Child 實(shí)現(xiàn)第二對(duì)接口的 Child。與兩級(jí)嵌套滾動(dòng)相比三級(jí)嵌套的 Grand 和 Child 和兩級(jí)的 Parent 和 Child 區(qū)別不大,變化比較大的是三級(jí)的 Parent 既要實(shí)現(xiàn)兩級(jí)的 Parent 接口又要實(shí)現(xiàn) Child 接口,示意圖如下:
在即刻首頁(yè)這個(gè)例子里,CoordinatorLayout/AppbarLayout 屬于三級(jí)嵌套的 Parent 實(shí)現(xiàn)了第二對(duì)接口的 NestedScrollingParent2,RecyclerView 屬于 Child 實(shí)現(xiàn)了第二對(duì)接口的 NestedScrollingChild2。這里我們需要做的是實(shí)現(xiàn)第一對(duì)嵌套接口,新建一個(gè)自定義 Layout 實(shí)現(xiàn) NestedScrollingParent2 接口作為三級(jí)嵌套的 Grand,負(fù)責(zé)搜索框和刷新動(dòng)畫(huà)的折疊和展開(kāi)。再新建一個(gè)自定義 Layout 繼承 CoordinatorLayout 實(shí)現(xiàn) NestedScrollingChild2 接口,負(fù)責(zé)攔截列表分發(fā)上來(lái)的滾動(dòng)事件或者處理 AppbarLayout 消費(fèi)后剩下的滾動(dòng)事件。
二級(jí)嵌套滾動(dòng)可以理解為給 Parent 提供了攔截 Child 滾動(dòng)事件和處理 Child 剩余滾動(dòng)事件的能力,具體邏輯可參考本文最開(kāi)始介紹嵌套滾動(dòng)的部分。相應(yīng)的三級(jí)嵌套滾動(dòng)給 Grand 提供了攔截 Parent 和處理剩余滾動(dòng)事件的能力,只是攔截和處理的時(shí)機(jī)多了一些,如下圖:
二級(jí)嵌套滾動(dòng)對(duì)滾動(dòng)處理時(shí)機(jī)只有三個(gè)階段:preScroll、scroll 和 afterScroll。而三級(jí)嵌套滾動(dòng)的處理時(shí)機(jī)就多一些,有七個(gè)階段:prePreScroll、preScroll、afterPreScroll、scroll、preAfterScroll、afterScroll 和 afterAfterScroll,可以看出相比二級(jí)嵌套多了 prePreScroll、afterPreScroll、preAfterScroll 和 afterAfterScroll 這四個(gè)階段,多出的這幾個(gè)階段都是給 Grand 用的。到這里可以發(fā)現(xiàn) NestedScrollingParent2 其實(shí)不能完全描述 Grand 的能力,確實(shí)最理想的方案應(yīng)該是新建一對(duì)接口 NestedScrollingGrand2 和 NestedScrollingGrandChild2 來(lái)描述新增的四個(gè)對(duì)滾動(dòng)事件的處理階段,但考慮到我這里的例子 Grand 對(duì) Parent 的處理沒(méi)有那么精細(xì)化,所以還是通過(guò)復(fù)用 NestedScrolling(Parent2/Child2) 和一些附加方法來(lái)實(shí)現(xiàn)。以后如果實(shí)現(xiàn)了 NestedScrolling(Grand2/GrandChild2) 接口,也會(huì)及時(shí)更新。根據(jù)上圖即刻首頁(yè)滑動(dòng)的實(shí)現(xiàn)思路就很簡(jiǎn)單了:
- onPrePreScroll 中執(zhí)行折疊刷新動(dòng)畫(huà)的邏輯,onAfterAfterScroll 中執(zhí)行展開(kāi)刷新動(dòng)畫(huà)的邏輯。
- onPreScroll 中執(zhí)行折疊 AppbarLayout 的邏輯,onAfterPreScroll 中執(zhí)行搜索框折疊的邏輯。
- onAfterScroll 中執(zhí)行展開(kāi) AppbarLayout 的邏輯,onPreAfterScroll 中執(zhí)行搜索框展開(kāi)的邏輯。
- 列表沒(méi)滑到頂部根據(jù) fling 展開(kāi)搜索框的邏輯單獨(dú)在 Parent 的 onNestedPreFling 里做,這條算是一個(gè)特殊處理。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
android Socket實(shí)現(xiàn)簡(jiǎn)單聊天小程序
這篇文章主要為大家詳細(xì)介紹了android Socket實(shí)現(xiàn)簡(jiǎn)單聊天小程序,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06Android圖片異步加載框架Android-Universal-Image-Loader
這篇文章主要介紹了Android圖片異步加載框架Android-Universal-Image-Loader,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-05-05Android中Image的簡(jiǎn)單實(shí)例詳解
這篇文章主要為大家詳細(xì)介紹了Android中Image的簡(jiǎn)單實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12Android編程之滑動(dòng)按鈕事件實(shí)例詳解
這篇文章主要介紹了Android編程之滑動(dòng)按鈕事件,結(jié)合具體實(shí)例形式分析了Android滑動(dòng)按鈕功能的具體實(shí)現(xiàn)步驟、布局及功能實(shí)現(xiàn)相關(guān)操作技巧,需要的朋友可以參考下2017-03-03android開(kāi)發(fā)環(huán)境中SDK文件夾下的所需內(nèi)容詳解
在本篇文章里小編給大家整理的是關(guān)于android開(kāi)發(fā)環(huán)境中SDK文件夾下的所需內(nèi)容詳解,有興趣的朋友們參考學(xué)習(xí)下。2019-09-09Android的權(quán)限設(shè)置及自啟動(dòng)設(shè)置方法
今天小編就為大家分享一篇Android的權(quán)限設(shè)置及自啟動(dòng)設(shè)置方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-07-07Android Listview 滑動(dòng)過(guò)程中提示圖片重復(fù)錯(cuò)亂的原因及解決方法
android中l(wèi)istview是比較常見(jiàn)的組件,通過(guò)本文主要給大家分析Android中Listview滾動(dòng)過(guò)程造成的圖片顯示重復(fù)、錯(cuò)亂、閃爍的原因及解決方法,順便跟進(jìn)Listview的緩存機(jī)制,感興趣的朋友一起看下吧2016-08-08monkeyrunner之電腦安裝驅(qū)動(dòng)(5)
這篇文章主要為大家詳細(xì)介紹了monkeyrunner之電腦安裝驅(qū)動(dòng)的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-12-12Android入門(mén)之實(shí)現(xiàn)手工發(fā)送一個(gè)BroadCast
這篇文章主要通過(guò)手工來(lái)發(fā)送一條BroadCast進(jìn)一步來(lái)帶大家深入了解BroadCast,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Android有一定幫助,感興趣的可以收藏一下2022-12-12