Rax 系列教程(長列表)
Rax 提供的長列表標籤有很多,在什麼場景下使用什麼列表元件,怎樣選擇列表元件效能會更好,這些問題可能會給剛接觸 Rax 的同學帶來困擾。本文結合 Rax 0.5 釋出版本對列表能力進行一次詳細的梳理。
如何讓頁面滾動
在開始正題之前先說說為什麼要有長列表的概念,以及如何讓頁面可以滾動。
傳統的 Web 頁面天生在瀏覽器裡就是可以滾動的,我們額外引入一個滾動容器的概念好像比較多餘。但當我們做跨容器開發時,這一層概念就變的有意義。native 的頁面天生不可滾動,需要藉助滾動容器的滾動能力,比如 iOS 中的 UITableView、Android 中的 RecyclerView,通過元件的方式讓頁面的部分內容可以滾動。
寫好了一個頁面發現在 Weex 上是白屏,很可能就是滾動容器沒有撐開。真實需求中我們往往想要整個頁面滾動,首先要解決的就是螢幕高度問題。下面這段是比較常用的頁面佔滿全屏的手段。
<View style={{ position: 'position', top: 0, bottom: 0, width: 750 }}> <RecyclerView /> </View>
對於動態設定高度的場景,我們可以通過 dom.getComponentRect 方法得到頁面可是區域的高度。
let dom = require('@weex-module/dom'); dom.getComponentRect('viewport', (e) => { console.log(e.result, e.size); });
如此以來我們的頁面就可以自由滾動,通過下拉重新整理、載入更多能力的組合讓我們的滾動容器更貼近 Web 體驗。
現有列表與能力範圍
Rax 目前提供了很多列表元件,相關基礎組建以及主要特點如下:
-
rax-scrollview (水平滾動推薦方案)
-
Weex 上實現是 slider,支援垂直和水平的滾動
-
無法做 cell 回收,內容過多時會有效能問題
-
-
rax-recyclerview (最常用高效能推薦方案)
-
Weex 實現是 list,可回收的長列表,不可水平滾動
-
效能上有很大優化,滾動體驗流暢
-
-
rax-listview (RN 習慣)
-
RecyclerView 的上層包裝,對標 RN 的能力
-
對效能和列表多樣化展示有更高要求的推薦使用 RecyclerView
-
-
rax-waterfall (瀑布圖場景推薦)
-
底層實現上也是 list 的一個擴充套件,在 API 能力上向 ListView 靠攏
-
長列表基礎能力
作為最基礎的推薦實現方案,以 rax-recyclerview 為例,介紹幾個列表的重要功能
onEndReached
當頁面滾動到底部時,往往我們會有繼續載入的操作,Weex 上 loadmore 事件。對應到 rax-recyclerview 就是 onEndReached 屬性。
在 Weex 中 onEndReached 出發後如果 cell 個數沒有發生變化,文件的高度沒有繼續撐開,則不會重複載入 onEndReached,這種保護措施讓我們避免了重複載入,但同時也引入了另外一個問題。
上面這個例子展示的邏輯是切換 tab 改變同一 list 的功能,當我切換 tab 後更新列表的資料條數與上一個 tab 觸發 onEndReached 的位置相同時,會發現 onEndReached 失效了。原因就是不會重複觸發導致的,解決方案就是使用 列表的 resetScroll 方法重置列表的滾動情況。下面是示例程式碼:
this.refs.list.resetScroll();
refresh
下拉重新整理是 web 瀏覽器的原生體驗,Weex 上的模擬是通過列表標籤內的 RefreshControl 元件實現,注意的是 RefreshControl 需要放在列表的第一個元素,如果有標籤在 refresh 之前會導致 RefreshControl 無法正常展示。
appear
在上手教程中介紹過這個事件,onAppear 事件可以讓我們在元素出現的時候做一些事情,在 Web 上 Rax 的 framework 同樣提供了 Appear 事件用來抹平與 Weex 的差異。appear 的一些注意點如下
-
appear 需要繫結在 滾動容器內不,不然 Weex 上無法生效
-
appear 的能力實際上是基於 onScroll,過多的 appear 對於滾動效能會稍有影響
-
appear 是一個滑動過程中可能頻繁觸發的事件,在這裡的 setState 邏輯需要自己把控好
onScroll
滾動過程中我們需要實時的做一些操作時會用到 onScroll,onScroll 時計 setState 更新內容是一個成本很大的事情,需要注意是否過頻繁的操作會引起頁面的卡頓,另外在滾動過程中的動畫操作我們推薦使用 BindingX ,這個實現方案可以減小通訊成本達到效能的提升,如下示例:
完整 demo 在這裡,下面程式碼展示滾動過程中一個元素的動畫
binding.bind({ eventType: 'scroll', anchor: list, props: [ { element: image, property: 'transform.translateY', expression: image_origin }, ] }, function(e) { });
頁面的組織
簡單可滾動頁面
撐滿裝置螢幕的 View 內部的滾動容器預設就是高度撐開的,此種場景是我們業務中用到最多也是最基礎的滾動場景。
<View style={{ position: 'position', top: 0, bottom: 0, width: 750 }}> <RecyclerView /> </View>
頁面部分固定
如果頁面中有部分是固定的其餘部分可以滾動我們可以採用如下方式,這種場景通常用來作為頂部導航或者底部 bar。
<View style={{ position: 'position', top: 0, bottom: 0, width: 750 }}> <View style={{ height: 80 }} /> <RecyclerView /> </View>
模組吸頂
樓層吸頂是一個較為常見的會場類頁面需求,通常的實現方案是 RecyclerView.Header 標籤,需要注意的是 Rax 0.5 版本中還未對 RecyclerView.Header 做 web 上的實現,需要業務上處理,可以將樣式設定為 fixed,或者將要吸頂元素拷貝到列表外部。上面的演示圖效果更為複雜,使用到了 binding。
<View style={{ position: 'position', top: 0, bottom: 0, width: 750 }}> <RecyclerView> <RecyclerView.Header /> </RecyclerView> </View>
橫滑切換多頁面
效能的優化帶來的是體驗的提升,我們可以不再拘泥於重新整理頁面來切換頁面。這就有了橫滑翻頁的嘗試。其主要思路就是通過手勢來進行橫滑拖拽。
<View style={{width: 750, position: 'absolute', top: 0, bottom: 0}}> <Tab .../> <TabController ... > <TabPanel><SamplePage index="0" /></TabPanel> <TabPanel><SamplePage index="1" /></TabPanel> <TabPanel><SamplePage index="2" /></TabPanel> </TabController> </View>
此處我們還將引申出另外一個文章,《Rax 系列教程(單頁)》敬請期待
模擬滾動巢狀
隨著頁面互動形式的越來越複雜,更豐富的體驗效果不斷的出現。如上圖橫滑頁面的部分上方出現一個公共區域。目前業務中較的實現方案是滾動下方的容器過程中去動態改變一個靜態的 header,頁面組織形式如下:
<View style={{width: 750, position: 'absolute', top: 0, bottom: 0}}> <Parallax> header 部分 </Parallax> <Tab .../> <TabController ... /> </View>
其中 Parallax 的部分也可以用 View 加動畫的方式實現,不過這種效果畢竟是模擬一個滾動巢狀,還不完美。
長列表使用技巧
水平與垂直滾動巢狀
垂直滾動容器中往往會有水平橫滑的場景,實現的方案有很多種,比如 Slider 元件可以完成水平的滾動輪播,Tabheader 可以作為可橫滑的 tab,如果想要更佳令我我們還可以用 ScrollView 自己實現一個水平的滾動。
在垂直與水平巢狀的場景中需要注意一點,就是水平滾動容器並不能盡興節點的回收,所以橫滑內容過長可能會引發效能問題,需要合理規劃橫滑內容。
手勢衝突
手勢動畫我們可以用與 RN 能力對其的 PanResponder,當然我們更推薦效能更加優秀的 BindingX 手勢,在 Rax 的標籤元素上繫結 onTouchStart 這樣的事件也是支援的。在這些能力支援的基礎上有一些坑也是需要我們注意的,比如手勢與滾動行為相互吃掉。垂直長列表預設就有上下滾動的行為,此時我們想要做一些手勢處理的需求時可能要先考慮一下手勢的方向會不會被滾動容器的滾動所影響。
一種情況是垂直滑動手勢,頁面垂直滾動時儘量避免垂直的手勢行為。雖然我們可以通過禁用滾動等方式模擬手勢滑動,但目前 iOS 和安卓仍然有支援程度不同的相容問題。所以如果垂直滑動時想要做一些事情,推薦使用 onScroll 事件 或者 BindingX 的 scroll 方法來解決。
另一種情況是水平橫畫手勢,如上圖。頁面由 4 個 tab 組成,橫畫頁面可以切換 tab,此事如果我們對容器再繫結水平的華東手勢,就會對橫畫切 tab 的行為造成影響。為了避免這種衝突,我們推薦使用原生 slider 進行頁面內的水平滾動操作,省區我們自己處理這一層衝突。
電梯跳轉
每年大促的頁面中我們幾乎都能看到電梯的身影,實現的基本思路是 Weex 下利用 Weex dom 模組的 scrollToElement 方法跳轉到頁面的制定元素,h5 下用錨點進行條轉。此時需要頁面樓層之間斤兩撐開,避免頁面抖動的情況。
還有一種方式是利用滾動容器的 scrollTo 方法跳轉指定舉例,此方式需要在跳轉前嚴格計算每個樓層的高度。
模組的順序保證
在長列表資料更新或者模組更新的過程中,如果沒有指定每個 cell 之間的順序就可能出現樓層錯位問題,指定的方式就是每個 cell 指定唯一的 key。如果摸個模組返回的是多個 cell 的暑促,那除了每個 cell 指定 key 這個模組也需要指定一個唯一的 key。
視差滾動
視差滾動需求目前提供了兩種解決方案,一種是 Weex 的 parallax 標籤,另一種是使用 BindingX 進行視差滾動的模擬。
Web 與 Weex 列表上的不同
此處說明配合 Rax 0.5 版本。
-
下拉重新整理: web 有原生的下拉重新整理體驗,RefreshControl 僅有 weex 的實現
-
吸頂:web 上沒有實現 RecyclerView.Header 的吸頂效果
-
電梯:樓層跳轉方式不同,web 上採用錨點或距離的方式,Weex 採用 scrollToElement 方法
-
回收機制:web 上沒有節點的回收
-
appear:weex 原生支援 appear,並且只能在滾動容器內部才能生效,web 是根據元素是否在可視區域進行模擬的
長列表的效能注意點
-
當列表資料過長時,不推薦用 ScrollView 作為頁面級別的滾動容器,RecyclerView 有更好的滾動效能(非可視區域 cell 的回收機制)
-
RecyclerView 的 cell 拆分粒度越細越好
-
同一 cell 內部不要放置太多圖片,保持儘量簡潔一致的 cell 結構利於原生 tableview cell 檢視複用
-
cell recycle = false 屬性會破壞 cell 的記憶體回收機制
-
WaterFall 的 header 沒有回收機制,不建議瀑布圖頭部 header 過長
-
巢狀太深不利於回收,建議最大深度不超過 15
-
列表內如果有大量視訊需要控制視訊標籤數量,建議非可視區域的視訊區塊用圖片代替
-
更新列表資料時,如果 cell 內部有類似事件繫結 onClick={()=>{}} 每次渲染會例項化新的 function 導致列表內容 diff 前後對比不一致,會觸發 cell 的重新 render
-
為避免長列表內元素的重複渲染,可在元件實現上 shouldComponentUpdate 時機可以將其 return 掉
-
列表記憶體暴漲、滑動卡頓 優先排查頁面是否有頻繁的 setState
題圖: https://unsplash.com/photos/V7gVxlUE5aY By @Toa Heftiba