日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Flutter瀑布流及通用列表解决方案

發布時間:2024/9/3 编程问答 66 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Flutter瀑布流及通用列表解决方案 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
簡介:解決flutter復雜布局過程以及對基礎能力進行擴充的列表視圖解決方案

作者:閑魚技術-夜瀾

背景

目前閑魚業務中無論是首頁還是搜索頁都有大量可以落地瀑布流的場景,而在Flutter原生中只提供了ListView, GridView,無法提供自定義布局的能力。

而在社區中,一般瀑布流的解決方案都是基于SliverMultiBoxAdaptor對其performLayout進行定制,主要存在的問題是缺乏復用機制,并且在很多情形下容易出現重復布局,在線上業務的復雜場景下容易出現幀數偏低的問題, 閃屏的問題。同時對于Child生命周期,打點曝光等一系列基礎功能的支持還是一片空白的狀態。

所以,我們迫切需要一個更為通用的可以解決復雜布局過程同時能夠對基礎能力進行擴充的列表視圖解決方案。

Flutter中的列表視圖簡介

1. Scrollable

Scrollable是一個StatefulWidget, 職責是監聽用戶的手勢輸入。其State的build方法會返回一個含有Listener和RawGestureDetector的ViewportScrollPosition用于描述其位置信息,并在其內部定義了 onStart, onUpdate, onEnd等回調。Scrollable中的每一次滑動的開始到結束都對應于一個Darg對象,并且會發送滑動的通知。而Viewport則負責對通知進行監聽。

2. Sliver

Flutter有兩種布局體系 Box, Sliver。在layout的過程中,每個Sliver 都接收 SliverConstraints 計算返回一個 SliverGeometry,可以類比于RenderBox 接收 BoxConstraints 返回一個 Size。Sliver由Viewport統一來負責進行管理。

3. Viewport

A widget that is bigger on the inside.

Viewport持有一個或多個Sliver。Scrollable將offset傳遞給Viewport, 由Viewport決定哪些Sliver應該是Visible。Viewport本質上是一個MultiChildRenderObjectWidget,也就是整個滾動視圖的主要渲染邏輯都在Viewport中完成。

而在performLayout中,_attemptLayout會以center為中心,先布局leading方向的child,再布局trailing方向的child。其中只有dirty的child會被布局。

do {correction = _attemptLayout(mainAxisExtent, crossAxisExtent, offset.pixels + centerOffsetAdjustment);if (correction != 0.0) {offset.correctBy(correction);} else {if (offset.applyContentDimensions(math.min(0.0, _minScrollExtent + mainAxisExtent * anchor),math.max(0.0, _maxScrollExtent - mainAxisExtent * (1.0 - anchor)),))break;}count += 1; } while (count < _maxLayoutCycles);

如果_attemptLayout返回了一個非0的correction, 就會打斷當前布局的過程,需要對offset進行調整后重新開始布局,最多只能連續打斷10次(_maxLayoutCycles)。

correction用于調整,舉個🌰,比如targetScrollOffset很遠,而在scroll的過程中child用完了,就需要讓Sliver通知Viewport, 同時進行修正。但是Flutter并不是通過不斷對child進行layout來改變child位置實現的滑動效果,這樣的重繪過程顯然效率太低,顯然RenderObject不需要被改變,是可以復用的。但是布局一般只發生在添加新child的過程中,而滑動效果則發生在paint過程中。

void _paintWithContext(PaintingContext context, Offset offset) {// 重新布局就不需要調整offset了.if (_needsLayout)return;_needsPaint = false;paint(context, offset); }

Viewport通過PaintingContext間接持有Canvas進行繪制。Offset指笛卡爾坐標系下的坐標,與Axis方向無關。繪制時只需改變對應RenderObject的Offset即可實現滾動的效果, 這樣就不必重新創建RenderObject。所以我們如果想實現性能較高的列表視圖,就要嘗試去減少重新布局Child。在對Flutter的列表布局有了基本了解后,我們再來看瀑布流的實現過程。

瀑布流的實現邏輯

WatetfallFlow的布局過程中需要指定Child的Offset,然后對其進行布局。所以需要繼承SliverMultiBoxAtaptor,依賴于其將SliverConstraints轉換為BoxConstraints的能力。我們也可以使用其SliverBoxChildManager, 方便控制Child的懶加載過程。

核心邏輯

在瀑布流中由于同一行(列)的child(大多)具有先后關系,需要按照順序來進行布局,所以瀑布流相比于GridView更類似于ListView,而瀑布流的布局過程也借鑒了ListView。整個瀑布流的布局邏輯圍繞三個核心展開:

  • 在滑動的過程中找到其邊緣最近的child,在其后(前)進行添加child,并對child進行layout.
  • 在child離開一定距離后進行GC.
  • 保證layout方法被盡可能少的調用. 上文有提過layout會調用performLayout而不能直接進行paint.
  • 其中核心的數據結構是ParentData.

    ParentData位于Child中,Child將其傳遞給Sliver,Sliver又將其傳遞至上層,其中儲存了全部的布局信息(在笛卡爾坐標系下)。在performLayout中,child在調用layout時所使用的布局信息就來自ParentData。在Child的添加過程中,用一個Manager存儲前后邊緣所有Child的ParentData,在添加時尋找邊緣最靠近可見區域的Child,對其ParentData進行設置并替換當前Child.

    布局的核心邏輯是對從最開始的Child(對應firstIndex)到最末的Child(對應targetLastIndex)進行布局。如果_layoutedChilds中已經有記錄,則跳過其布局過程。

    for (int index = firstIndex; index <= targetLastIndex; ++index) {final SliverGeometry gridGeometry = layout.getGeometryForChildIndex(index);final BoxConstraints childConstraints = gridGeometry.getBoxConstraints(constraints);RenderBox child = childAfter(trailingChildWithLayout);if (child == null || indexOf(child) != index) {// 重新獲取Child.child = _createAndLayoutChildIfNeeded(childConstraints, after: trailingChildWithLayout);if (child != null && indexOf(child) == index) {_layoutedChilds.add(index);}else if (child == null) {// Child已經用盡.break;}} else {if (!_layoutedChilds.contains(index)) {_layoutChildIfNeeded(child, parentUsesSize: true);_layoutedChilds.add(index);}}trailingChildWithLayout = child; }

    對離開視圖的child進行GC,同時記得將數組中的child清除.

    if (firstChild != null) {// 上一次的最先最末Child.final int oldFirstIndex = indexOf(firstChild);final int oldLastIndex = indexOf(lastChild);// 前后需要GC的child數量final int leadingGarbage = (firstIndex - oldFirstIndex).clamp(0, childCount);final int trailingGarbage = targetLastIndex == null ? 0 : (oldLastIndex - targetLastIndex).clamp(0, childCount);// GCcollectGarbage(leadingGarbage, trailingGarbage);_layoutedChilds.sort();_layoutedChilds.removeRange(0, leadingGarbage);_layoutedChilds.removeRange(layoutedChilds.length - 1 - trailingGarbage, layoutedChilds.length - 1); } else {collectGarbage(0, 0); }

    在開發過程中出現了幀數偏低的問題,發現是Child在performLayout的過程中會出現重復布局。解決方法是我們不僅記錄leading, trailing邊緣的child。而且用對已經layout過的child進行記錄,粗暴直接但是有效,這樣做也可以提供單獨update單個child的Layout能力。在更新Child的布局時也只需從記錄中將對應child移除。

    相比于原生視圖,我們可以通過獲取所有Child的ParentData信息,可以為上層接口提供實時并且有效的回調.。這樣就可以根據每個Child的實時位置來提供生命周期,曝光打點的能力。所以可以對每個child的坐標進行監聽,從而獲得精準的曝光信息。

    從瀑布流到容器

    在瀑布流的開發過程中也暴露出了一些設計上的問題。

    比如瀑布流的具體渲染邏輯都在RenderObject中進行,太過底層顯然是不利于業務方根據業務進行定制。

    又比如由于沒有復用的機制,在視圖層級較為復雜時幀數會由于重復渲染而不可避免的降低。

    借鑒native思路重新設計后將整體容器分為3個部分進行設計。

  • delegate
  • 主要管理child生命周期并響應手勢,由于我們可以得到每個可見Child的parentData屬性,所以可在滾動時進行實時的通知。從而對每個Child的位置監聽,從開始創建進入緩沖區,到從緩沖區進入可見區域。手勢則來自于頂層的Scrollable。

  • layout
  • 主要負責布局所有的Child。將具體的布局邏輯抽離出,類似于iOS中的UICollectionViewLayout。但是在開發過程中也出現了一些問題,原因主要來自于Flutter特殊的信息傳遞方式,就是我們不能采用native的方式一次性計算出所有child的布局。因為RenderBox需要接收一個BoxConstraints才能返回一個size。

  • reuser
  • reuser則在RenderObject層面,對Child進行基于類型的復用并實現局部更新的操作。需要將SliverMultiBoxAdaptor和其Element拷貝一份進行重寫,改變其mount的邏輯,方案還在探索和調研之中,希望能在后續的文章中和大家見面!

    性能數據

    應用于主搜索頁進行自動化測試,先前在54.7幀左右,換用瀑布流后為56.2,大概提升了1.5幀。

    內存上則有略微的升高情況。

    展望

    目前Flutter的列表視圖中仍然有很多問題需要處理,比如瀑布流中scrollTo(int index)的能力還無法實現,內存的使用情況等和原生相比仍然有不小的差距, 對于Flutter側的復用的穩定性和兼容性上還存在問題,閑魚在Flutter化上還有很多路要走。

    PS0: 文中代碼基于Flutter 1.12.13。

    PS1: 文中譬如Viewport,既代指Widget本身, 又代指其對應的RenderObject。

    PS2: 文中涉及到的代碼經過刪改, 僅供參考。

    原文鏈接:https://developer.aliyun.com/article/767965?

    版權聲明:本文中所有內容均屬于阿里云開發者社區所有,任何媒體、網站或個人未經阿里云開發者社區協議授權不得轉載、鏈接、轉貼或以其他方式復制發布/發表。申請授權請郵件developerteam@list.alibaba-inc.com,已獲得阿里云開發者社區協議授權的媒體、網站,在轉載使用時必須注明"稿件來源:阿里云開發者社區,原文作者姓名",違者本社區將依法追究責任。 如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至:developer2020@service.aliyun.com 進行舉報,并提供相關證據,一經查實,本社區將立刻刪除涉嫌侵權內容。

    總結

    以上是生活随笔為你收集整理的Flutter瀑布流及通用列表解决方案的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。