SlideLayout 雙列表頁面實現

即刻 5.3 版本的時候,隨着圈子詳情頁的內容愈來愈豐富,以前的頁面結構已經不能知足咱們的需求,須要一個新的佈局方案承載各類圈子元素並知足咱們的自定義交互。android

改版前

改版前的結構比較簡單,頭部顯示圈子的基本信息好比圖片、標題和簡介等信息,底部展現圈子內的消息列表,向上滑動可摺疊頭部區域讓用戶更加專一地瀏覽消息列表,結構以下:git

  • CoordinatorLayout
    • AppBarLayout
    • ViewPager
      • Fragment
        • RecyclerView
      • Fragment
        • RecyclerView

CoordinatorLayout 做爲容器負責兩部分的佈局和聯動滑動,AppBarLayout 負責展現頭部信息,底部經過 ViewPager 和 Fragment 實現多 tab 頁面,Fragment 內部經過 RecyclerView 實現消息列表。github

改版後

改版後頭部新增了一些元素好比插件、建立者,原有的元素展現區域擴大,致使頭部高度增大。使得用戶剛進入圈子頁時幾乎看不到消息列表區域,爲了解決這個問題咱們須要頁面支持快速地在頭部和列表之間切換,而且當頭部超過一屏時也能夠滑動。簡單總結下咱們的需求:ide

  1. 當頭部信息較少,即沒有達到一屏時表現和原有實現一致,頭部隨列表滑動能夠摺疊。
  2. 當頭部信息較多,即超過一屏時除了頭部隨着列表滑動摺疊外,還能夠在頭部和列表之間快速切換。
解決方案

第一條需求原有的 CoordinatorLayout 就能夠支持,問題是第二條中的快速切換如何實現,最終咱們的產品同事給出的解決方案以下:佈局

從這個截圖看上去好像和原來的將頭部摺疊同樣,其實否則。將頭部摺疊須要先將頭部滑到界面外,而這裏頭部其實沒有滑動,列表是蓋在頭部上面,當想查看頭部時再將列表滑下去。動畫

有人可能會說,這和原來的有什麼區別,都須要滑動。這種實現的好處主要有三點:spa

  1. 列表只會存在展開和隱藏兩種狀態,不會存在顯示一半的狀況,當將列表拖到屏幕中間鬆手時會自動滑動到展開或隱藏,下降頭部和列表切換的難度。
  2. 當列表滑動幾屏後,此時仍然能夠拖動 scroll bar 將列表滑出展現頭部,不須要將列表滑到最頂部再拉出頭部。
  3. 當頭部很長時,頭部內容滑動在任何位置均可以拖動 scrollbar 滑出列表,不須要將頭部滑到最底部。

這裏的 scrollbar 是個輔助組件,後面會講到,這裏先不展開。插件

原有的 CoordinatorLayout 不能知足上述需求,因此咱們須要實現一個自定義組件,因爲這個組件的主要功能就是將頁面底部滑出滑進,因此咱們將這個組件命名爲 SlideLayout設計

思路詳述
嵌套滾動

不論是原有邏輯仍是新增的,都屬於一對嵌套組件的聯動交互,不難看出須要用到嵌套滾動機制來實現。首先咱們簡單瞭解下嵌套滾動的機制:3d

圖中 Parent 表示實現了 NestedScrollingParent 接口的組件,Child 表示實現了 NestedScrollingChild 接口的組件,Parent 接受 Child 分發的滾動事件,並且他們不直接關聯。

SlideLayout 結構

如上圖在 SlideLayout 中當下拉出刷新動畫時能夠看到三個組件:refresh、header 和 slider,它們的含義以下:

  1. refresh:當用戶下拉刷新頁面時 refresh 負責展現加載動畫。
  2. header:負責頁面頭部,須要包含實現 NestedScrollingChild 的組件從而向 SlideLayout 分發滾動事件。
  3. slider:負責列表區域,也須要包含實現 NestedScrollingChild 的組件,緣由同 header。

實現 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>
複製代碼
  • SlideBarLayout:上面 gif 圖裏的 scroll bar,參考 AppBarLayout 實現的滑動條組件,咱們開源的項目中有源碼,感興趣的同窗能夠前去查看。
  • RefreshViewLayout:用於存放刷新動畫組件的容器,能夠經過實現 RefreshView 接口建立自定義的刷新動畫,並設置給 RefreshViewLayout 的 refreshInterface 來生效。
  • 更多的使用方式能夠訪問 SlideLayout 項目主頁查看。
總結

本文介紹了爲何須要 SlideLayout,並簡單闡述了設計思路和實現機制,但願給讀者有所啓發和幫助。做爲嵌套滾動機制的一種具體實現,在開發過程當中讓我深切感覺到這套接口功能的強大,定義雖然簡單,但卻幾乎能實現各類頁面聯動效果。因爲本人水平有限,文章或者代碼若是有任何問題實屬不免,歡迎評論指正或者提 issue。

SlideLayout 項目地址: iftech-android-slide-layout,若是對具體實現感興趣能夠前去查看,歡迎 star 和關注。

參考文章:

相關文章
相關標籤/搜索