Android NestedScrolling 實戰

race604發表於2015-08-24

從 Android 5.0 Lollipop 開始提供一套 API 來支援嵌入的滑動效果。同樣在最新的 Support V4 包中也提供了前向的相容。有了嵌入滑動機制,就能實現很多很複雜的滑動效果。在 Android Design Support 庫中非常重要的 CoordinatorLayout 元件就是使用了這套機制,實現了 Toolbar 的收起和展開功能,如下圖所示:

10-1-1

NestedScrolling 提供了一套父 View 和子 View 滑動互動機制。要完成這樣的互動,父 View 需要實現 NestedScrollingParent 介面,而子 View 需要實現 NestedScrollingChild介面。

實現 NestedScrollingChild

首先來說 NestedScrollingChild。如果你有一個可以滑動的 View,需要被用來作為嵌入滑動的子 View,就必須實現本介面。在此 View 中,包含一個 NestedScrollingChildHelper輔助類。NestedScrollingChild 介面的實現,基本上就是呼叫本 Helper 類的對應的函式即可,因為 Helper 類中已經實現好了 Child 和 Parent 互動的邏輯。原來的 View 的處理 Touch 事件,並實現滑動的邏輯大體上不需要改變。

需要做的就是,如果要準備開始滑動了,需要告訴 Parent,你要準備進入滑動狀態了,呼叫 startNestedScroll()。你在滑動之前,先問一下你的 Parent 是否需要滑動,也就是呼叫 dispatchNestedPreScroll()。如果父類滑動了一定距離,你需要重新計算一下父類滑動後剩下給你的滑動距離餘量。然後,你自己進行餘下的滑動。最後,如果滑動距離還有剩餘,你就再問一下,Parent 是否需要在繼續滑動你剩下的距離,也就是呼叫 dispatchNestedScroll()

以上是一些基本原理,有了上面的基本思路,可以參考這篇文章,這裡面有原理的詳細解析。如果還是不清楚,這裡有對應的程式碼可以參考。

實現 NestedScrollingParent

作為一個可以嵌入 NestedScrollingChild 的父 View,需要實現 NestedScrollingParent,這個介面方法和 NestedScrollingChild 大致有一一對應的關係。同樣,也有一個 NestedScrollingParentHelper 輔助類來默默的幫助你實現和 Child 互動的邏輯。滑動動作是 Child 主動發起,Parent 就收滑動回撥並作出響應。

從上面的 Child 分析可知,滑動開始的呼叫 startNestedScroll(),Parent 收到 onStartNestedScroll() 回撥,決定是否需要配合 Child 一起進行處理滑動,如果需要配合,還會回撥 onNestedScrollAccepted()

每次滑動前,Child 先詢問 Parent 是否需要滑動,即 dispatchNestedPreScroll(),這就回撥到 Parent 的 onNestedPreScroll(),Parent 可以在這個回撥中“劫持”掉 Child 的滑動,也就是先於 Child 滑動。

Child 滑動以後,會呼叫 onNestedScroll(),回撥到 Parent 的 onNestedScroll(),這裡就是 Child 滑動後,剩下的給 Parent 處理,也就是 後於 Child 滑動。

最後,滑動結束,呼叫 onStopNestedScroll() 表示本次處理結束。

其實,除了上面的 Scroll 相關的呼叫和回撥,還有 Fling 相關的呼叫和回撥,處理邏輯基本一致。

實戰

有了這一套官方的巢狀滑動的解決方案,打算把我的 FlyRefresh 的滑動和下來部分用 NestedScrolling 來實現。我在這篇部落格中講了,之前是通過在 PullHeaderLayout 的 dispatchTouchEvent() 中小心處理 Touch 事件來實現的。現在回想起來,這種方法相對複雜,需要清楚知道 Parent 和 Child 的滑動狀態,這就導致了,只能支援有限的 Child 型別,例如當時只支援 ListView 和 RecyclerView,為了支援更多的型別,還定義了一個 IScrollHandler 介面來支援。

讓 FlyRefresh 實現 NestedScrollingParent,就可以支援所有的 NestedScrollingChild 作為FlyRefreshLayout 的子 View。另外,因為 CoordinatorLayout 是如此的重要,大部分的 App 都需要使用它作為頂層的 Layout,為了讓 FlyRefreshLayout 能夠在 CoordinatorLayout 也能使用,所以我還打算同時實現 NestedScrollingChild 介面。關鍵實現程式碼如下:

完整的修改,可以看這個 commit。整個修改下來,程式碼減少了不少,而且更加整潔了。

總結

總體來說,NestedScroll 初看起來有些讓人費解,但是真的理解以後,就發現這種設計的優秀之處。把滑動整體封裝起來,通過 Helper 來實現 Child 和 Parent 之間的連線和互動。通過介面來回撥,實現了 Child 和 Parent 的邏輯獨立。

Android 5.0的大部分可以滑動的控制元件都支援了 NestScrolling 介面,最新的 Support V4 中也一樣,相信以後越來越多的第三方庫都會支援,到時候各種控制元件的巢狀滑動就能無縫整合了。

相關文章