即刻 5.3 版本的時候,隨着圈子詳情頁的內容愈來愈豐富,以前的頁面結構已經不能知足咱們的需求,須要一個新的佈局方案承載各類圈子元素並知足咱們的自定義交互。android
改版前的結構比較簡單,頭部顯示圈子的基本信息好比圖片、標題和簡介等信息,底部展現圈子內的消息列表,向上滑動可摺疊頭部區域讓用戶更加專一地瀏覽消息列表,結構以下:git
CoordinatorLayout 做爲容器負責兩部分的佈局和聯動滑動,AppBarLayout 負責展現頭部信息,底部經過 ViewPager 和 Fragment 實現多 tab 頁面,Fragment 內部經過 RecyclerView 實現消息列表。github
改版後頭部新增了一些元素好比插件、建立者,原有的元素展現區域擴大,致使頭部高度增大。使得用戶剛進入圈子頁時幾乎看不到消息列表區域,爲了解決這個問題咱們須要頁面支持快速地在頭部和列表之間切換,而且當頭部超過一屏時也能夠滑動。簡單總結下咱們的需求:ide
第一條需求原有的 CoordinatorLayout 就能夠支持,問題是第二條中的快速切換如何實現,最終咱們的產品同事給出的解決方案以下:佈局
從這個截圖看上去好像和原來的將頭部摺疊同樣,其實否則。將頭部摺疊須要先將頭部滑到界面外,而這裏頭部其實沒有滑動,列表是蓋在頭部上面,當想查看頭部時再將列表滑下去。動畫
有人可能會說,這和原來的有什麼區別,都須要滑動。這種實現的好處主要有三點:spa
這裏的 scrollbar 是個輔助組件,後面會講到,這裏先不展開。插件
原有的 CoordinatorLayout 不能知足上述需求,因此咱們須要實現一個自定義組件,因爲這個組件的主要功能就是將頁面底部滑出滑進,因此咱們將這個組件命名爲 SlideLayout。設計
不論是原有邏輯仍是新增的,都屬於一對嵌套組件的聯動交互,不難看出須要用到嵌套滾動機制來實現。首先咱們簡單瞭解下嵌套滾動的機制:3d
圖中 Parent 表示實現了 NestedScrollingParent 接口的組件,Child 表示實現了 NestedScrollingChild 接口的組件,Parent 接受 Child 分發的滾動事件,並且他們不直接關聯。
如上圖在 SlideLayout 中當下拉出刷新動畫時能夠看到三個組件:refresh、header 和 slider,它們的含義以下:
實現 NestedScrollingChild 的組件有 NestedScrollView、RecyclerView 等,就圈子詳情頁這個頁面來講,NestedScrollView 實現了頁面頭部,RecyclerView 實現了消息列表。
在處理 SlideLayout 中的滾動事件時,咱們用一個枚舉類型定義了三個狀態:
enum class SlideGesture { SCROLL, SLIDE, REFRESH }
複製代碼
SCROLL
slider 和 header 處於鏈接的狀態,即 header 的底邊連着 slider 的頂邊沒有重疊。此狀態時須要和老版本保持一致,即頭部隨着列表的滾動而滾動。以下圖:
SLIDE
slider 蓋在 header 上面,slider 此時有可能展現也有可能隱藏,主要工做是將 slider 滑出或者隱藏。以下圖:
REFRESH
展現刷新動畫,即刷新動畫部分高度大於 0。下圖只展現了從 Scroll 狀態轉換而來的狀況,其實從 Slide 狀態也能夠進入 Refresh 狀態,和這個相似會在頭部上面出現一個刷新動畫展現區域,這裏就不列出了。
三個狀態的彼此轉換關係以下圖:
確認了狀態定義後剩下的工做基本就分爲兩部分:狀態識別和滾動處理。
根據前面講的狀態定義可得出狀態判斷邏輯以下:
咱們將 Refresh 狀態的優先級設爲最高,先判斷刷新區域的高度是否大於 0 來檢查是否是 Refresh 狀態。因爲 Slide 的定義是 slider 和 header 有重疊,而 slider 在 SlideLayout 中是經過 sliderTop 來表示位置的,因此咱們能夠經過 sliderTop < headerHeight 來判斷是否是 Slide 狀態。最後兩個條件都不知足的話就是 Scroll 狀態了。
針對不一樣狀態,對滾動事件定義了不一樣的處理規則,從而實現咱們須要的交互效果。具體的處理邏輯見下表:
橫向:狀態 豎向:滾動類型 |
Refresh | Scroll | Slide |
---|---|---|---|
nestedPreScroll 向上滾動 | 消費滾動事件,摺疊 刷新動畫區域 | 消費滾動事件,摺疊頭部 | 消費滾動事件,展開 slider |
nestedPreScroll 向下滾動 | 不消費 | 不消費 | 不消費 |
nestedScroll 向上滾動 | 不消費 | 不消費 | 不消費 |
nestedScroll 向下滾動 | 消費滾動事件,展開刷新動畫區域 | 消費滾動事件,展開頭部 | 消費滾動事件,摺疊 slider |
橫向表示三種狀態,豎向表示兩種滾動事件類型,組合出六種不一樣的 case。這裏給出的邏輯處理比較簡單,實際實現時會遇到不少須要特殊處理的狀況,這裏就不一一列出了,感興趣的同窗能夠查看項目源碼,項目地址會在最後給出。
咱們經過動圖來看看最終實現的效果,第一種是頭部沒超過屏幕的狀況:
而後再看看頭部超過屏幕的狀況:
頁面佈局結構以下:
<SlideLayout>
<!-- header -->
<FrameLayout>
<androidx.core.widget.NestedScrollView>
<!-- header content here -->
</androidx.core.widget.NestedScrollView>
</FrameLayout>
<!-- slider -->
<FrameLayout>
<LinearLayout>
<SlideBarLayout>
<!-- slide bar content here -->
</SlideBarLayout>
<androidx.recyclerview.widget.RecyclerView />
</LinearLayout>
</FrameLayout>
<!-- refresh -->
<RefreshViewLayout/>
</SlideLayout>
複製代碼
本文介紹了爲何須要 SlideLayout,並簡單闡述了設計思路和實現機制,但願給讀者有所啓發和幫助。做爲嵌套滾動機制的一種具體實現,在開發過程當中讓我深切感覺到這套接口功能的強大,定義雖然簡單,但卻幾乎能實現各類頁面聯動效果。因爲本人水平有限,文章或者代碼若是有任何問題實屬不免,歡迎評論指正或者提 issue。
SlideLayout 項目地址: iftech-android-slide-layout,若是對具體實現感興趣能夠前去查看,歡迎 star 和關注。
參考文章: