上下滑切換翻頁(yè)大概是這樣的效果:

目前網(wǎng)上有諸多如 “仿抖音上下滑...” “仿花椒映客直播...” 之類(lèi)的技術(shù)分享,都有講述實(shí)現(xiàn)上下滑切換頁(yè)面的方案,其中以 ViewPager 和 RecyclerView + SnapHelper 兩種方案為多,但是都有明顯的缺點(diǎn)。以下是一些個(gè)人的看法:
為什么ViewPager不合適
ViewPager 自帶的滑動(dòng)效果完全滿(mǎn)足場(chǎng)景,而且支持 Fragment 和 View 等UI綁定,只要對(duì)布局和觸摸事件部分作一些修改,就可以把橫向的 ViewPager 改成豎向。
但是沒(méi)有復(fù)用是個(gè)最致命的問(wèn)題。在 onLayout 方法中,所有子View會(huì)實(shí)例化并一字排開(kāi)在布局上。當(dāng)Item數(shù)量很大時(shí),將會(huì)是很大的性能浪費(fèi)。
其次是可見(jiàn)性判斷的問(wèn)題。很多人會(huì)以為 Fragment 在 onResume 的時(shí)候就是可見(jiàn)的,而 ViewPager 中的 Fragment 就是個(gè)反例,尤其是多個(gè) ViewPager 嵌套時(shí),會(huì)同時(shí)有多個(gè)父 Fragment 多個(gè)子 Fragment 處于 onResume 的狀態(tài),卻只有其中一個(gè)是可見(jiàn)的。除非放棄 ViewPager 的預(yù)加載機(jī)制。在頁(yè)面內(nèi)容曝光等重要的數(shù)據(jù)上報(bào)時(shí),就需要判斷很多條件:onResumed 、 setUserVisibleHint 、 setOnPageChangeListener 等。
最后是嵌套滑動(dòng)的問(wèn)題。同向嵌套滑動(dòng)是很常見(jiàn)的場(chǎng)景,Google 新出的滑動(dòng)布局基本都使用 NestedScrolling 機(jī)制來(lái)解決嵌套滑動(dòng)。但是 ViewPager 依然需要開(kāi)發(fā)者自己來(lái)處理復(fù)雜的滑動(dòng)沖突。
為什么RecyclerView不合適
RecyclerView + SnapHelper 的方案比 ViewPager 好得多,既有對(duì) View 的復(fù)用,滑動(dòng)事件也已經(jīng)處理好。
但是依然無(wú)法雙向無(wú)限滑動(dòng)。我們可以在 getItemCount 方法中返回 Integer.MAX_VALUE 來(lái)假裝無(wú)限個(gè)滑動(dòng)元素。但是為了從頭開(kāi)始就可以下拉滑到上一個(gè),元素列表的索引就不能初始化為0,那初始值為 Integer.MAX_VALUE/2 ?
無(wú)論怎么掩飾,理論上還是有滑動(dòng)到頭的一天。
更優(yōu)的一種解決方案
使用兩個(gè) View 輪流切換就能完成上下滑的場(chǎng)景。這種方案也有APP在用,但是網(wǎng)上幾乎找不到源碼。因此我把它抽成獨(dú)立的庫(kù)放在Github倉(cāng)庫(kù):致力于打造通用、易用和流暢的上下滑動(dòng)翻頁(yè)布局SlidableLayout。
SlidableLayout 本質(zhì)是一個(gè)包含兩個(gè)相同大小子 View 的 FrameLayout 。兩個(gè)子 View 分別作為 TopView 和 BackView 。
靜止?fàn)顟B(tài)下,用戶(hù)只會(huì)看見(jiàn) TopView ,而 BackView 被移除或隱藏。
手指向上拖動(dòng)時(shí), TopView 在y軸上向上偏移, BackView 開(kāi)始出現(xiàn),而且 BackView 的頂部與 TopView 的底部相接。
手指向上拖動(dòng)一定距離后放手,TopView 繼續(xù)在y軸上做動(dòng)畫(huà)直到完全消失, BackView 向上直到完全出現(xiàn)。然后 TopView 和 BackView 互換身份,原來(lái)的 BackView 成為現(xiàn)在的 TopView ,原來(lái)的 TopView 被移除或隱藏,成為下一次滑動(dòng)的 BackView ?;Q后完成一次滑動(dòng)。
反之,手指向下滑動(dòng)亦然。
同時(shí)要考慮手指放手后,滑動(dòng)距離不夠或者速度不夠時(shí),TopView 會(huì)沿著y軸回彈到原來(lái)的位置。 BackView 也跟著原路返回,直到被移除或隱藏。
SlidableLayout 還實(shí)現(xiàn)了 NestedScrollingChild 接口,使其能夠與自定義的下拉刷新布局嵌套滑動(dòng)。
源碼和使用例子參照 https://github.com/YvesCheung/SlidableLayout 。如有不同意的地方,請(qǐng)?jiān)?Github 留下 Issue。