上下滑切換翻頁大概是這樣的效果:
目前網上有諸多如 “仿抖音上下滑...” “仿花椒映客直播...” 之類的技術分享,都有講述實現上下滑切換頁面的方案,其中以 ViewPager
和 RecyclerView
+ SnapHelper
兩種方案為多,但是都有明顯的缺點。以下是一些個人的看法:
為什麼ViewPager不合適
ViewPager
自帶的滑動效果完全滿足場景,而且支援 Fragment
和 View
等UI繫結,只要對佈局和觸控事件部分作一些修改,就可以把橫向的 ViewPager
改成豎向。
但是沒有複用是個最致命的問題。在 onLayout
方法中,所有子View會例項化並一字排開在佈局上。當Item數量很大時,將會是很大的效能浪費。
其次是可見性判斷的問題。很多人會以為 Fragment
在 onResume
的時候就是可見的,而 ViewPager
中的 Fragment
就是個反例,尤其是多個 ViewPager
巢狀時,會同時有多個父 Fragment
多個子 Fragment
處於 onResume
的狀態,卻只有其中一個是可見的。除非放棄 ViewPager
的預載入機制。在頁面內容曝光等重要的資料上報時,就需要判斷很多條件:onResumed
、 setUserVisibleHint
、 setOnPageChangeListener
等。
最後是巢狀滑動的問題。同向巢狀滑動是很常見的場景,Google 新出的滑動佈局基本都使用 NestedScrolling 機制來解決巢狀滑動。但是 ViewPager 依然需要開發者自己來處理複雜的滑動衝突。
為什麼RecyclerView不合適
RecyclerView
+ SnapHelper
的方案比 ViewPager
好得多,既有對 View
的複用,滑動事件也已經處理好。
但是依然無法雙向無限滑動。我們可以在 getItemCount
方法中返回 Integer.MAX_VALUE 來假裝無限個滑動元素。但是為了從頭開始就可以下拉滑到上一個,元素列表的索引就不能初始化為0,那初始值為 Integer.MAX_VALUE/2 ?
無論怎麼掩飾,理論上還是有滑動到頭的一天。
更優的一種解決方案
使用兩個 View 輪流切換就能完成上下滑的場景。這種方案也有APP在用,但是網上幾乎找不到原始碼。因此我把它抽成獨立的庫放在Github倉庫:致力於打造通用、易用和流暢的上下滑動翻頁佈局SlidableLayout。
SlidableLayout 本質是一個包含兩個相同大小子 View
的 FrameLayout
。兩個子 View
分別作為 TopView 和 BackView 。
靜止狀態下,使用者只會看見 TopView ,而 BackView 被移除或隱藏。
手指向上拖動時, TopView 在y軸上向上偏移, BackView 開始出現,而且 BackView 的頂部與 TopView 的底部相接。
手指向上拖動一定距離後放手,TopView 繼續在y軸上做動畫直到完全消失, BackView 向上直到完全出現。然後 TopView 和 BackView 互換身份,原來的 BackView 成為現在的 TopView ,原來的 TopView 被移除或隱藏,成為下一次滑動的 BackView 。互換後完成一次滑動。
反之,手指向下滑動亦然。
同時要考慮手指放手後,滑動距離不夠或者速度不夠時,TopView 會沿著y軸回彈到原來的位置。 BackView 也跟著原路返回,直到被移除或隱藏。
SlidableLayout 還實現了 NestedScrollingChild 介面,使其能夠與自定義的下拉重新整理佈局巢狀滑動。
原始碼和使用例子參照 github.com/YvesCheung/… 。如有不同意的地方,請在 Github 留下 Issue。