yii.
← Writing
Flutter 技術 · · 7 min read

實作 Flutter 多變有趣的滾動效果CustomScrollView!

這篇寫於 2022 年。工具和 API 變化很快,部分步驟可能已經不適用。

與眾不同的ScrollView列表

為什麼需要CustomScrollView?#

通常如果使用ListView放置在ListView裡會出錯,原因是在Layout時父ListView不能判斷子ListView的高度,但可以設置子ListView的 shrinkWrap = true 來解決,但動態計算會導致性能很差,不太推薦。

不過CustomScrollView可以解決此問題,它支援複雜列表的嵌套和滾動組合!

Sliver 佈局流程#

  1. Viewport 將目前的配置資訊通過 SliverConstraints傳遞給 Sliver
  2. Sliver 根據 Viewport 的約束,確定自己的大小、位置、繪製等資訊,保存在 SliverGeometry 中,回傳給 Viewport
  3. Viewport 讀取 SliverGeometry 中的資訊來對 Sliver 子原件進行安排和繪製

Viewport → SliverConstraints → Sliver → SliverGeometry → Viewport → Sliver


以下說明幾個重要角色

ViewPort#

  • 一個顯示範圍、窗口,裡面包含多個Sliver元件
  • 寬高是固定的,它內部元件的的總寬高是可以大於本身的
  • 為了提高性能採用懶加載機制,它只會繪製可視區域的Widget
  • 只繪製、顯示ScrollView中一部分內容來實現滾動效果,大大降低記憶體消耗

Sliver#

  • 自定義滾動效果的滾動View,達到更準確的控制
  • 使用 CustomScrollView 搭配各種Sliver元件,為所有子Sliver提供一個共享的Scrollable,然後統一處理指定滑動方向的滑動事件,統一滾動效果
  • 在 Viewport 中實現滾動效果的 RenderObject,使用 RenderSliver 作為基礎類別
  • 在佈局的過程,Sliver 獲取Viewport給予的 SliverConstraints 並計算對應的 SliverGeometry (描述 Sliver佔得空間大小與相關資訊)
  • CustomScrollView和ListView、GridView、PageView 一樣,都是完整的可滾動元件(同時擁有Viewport、Sliver、Scrollable)

SliverConstraints#

  • 和RenderBox佈局使用BoxConstraints類似,Sliver佈局採用SliverConstraints作為約束
  • 當列表滑動時,如果某個Sliver已經進入Viewport需要創建的區域,則列表會將SliverConstraints傳遞給該Sliver,Sliver就可以根據這些資訊來確定自身的繪製資訊 SliverGeometry

SliverGeometry#

  • Sliver在滾動列表的空間繪製資訊
  • 在佈局的最後Sliver通過回傳SliverGeometry給Viewport,計算出Sliver的位置,更新UI

常見的幾個Sliver元件,在開發時很常使用到,熟悉他們對我們很有幫助。

⚡︎SliverAppBar#

  • 根據滾動偏移量改變高度
  • 基於 SliverPersistentHeader 的實現
  • expandedHeight:設置內容展開顯示的最大高度
  • flexibleSpace:靈活展開的內容,以動畫效果顯示出來。最常見是顯示圖片,例如商品、食物等
  • pinned:如果為true,會將AppBar固定在頂部,始終渲染一個固定在頂部的收起狀態AppBar
  • floating:如果為true,向下滾動時,AppBar會顯示,否則須回到頂端。而只有當AppBar展開時才會繼續滾動ListView
  • snap:如果為true,向下滾動時,直接全展開,效果會比 floating 的速度還快,始終保持展開和收起的狀態。且只有在 floating 為true時才可以設置,才會有效
設定 leading 和 actions
設定 leading 和 actions
pinned = true
pinned = true
floating = true
floating = true
snap = true
snap = true
flexibleSpace & expandedHeight
flexibleSpace & expandedHeight
幫flexibleSpace裡的圖片新增漸層效果
幫flexibleSpace裡的圖片新增漸層效果

FlexibleSpaceBar的stretchModes,設置下拉的伸縮、伸展模式#

  • zoomBackground:縮放內容
  • blurBackground:模糊內容
  • 注意:SliverAppBar的 stretch 設置需為 true,才會有伸縮效果
設置stretchModes,新增縮放和模糊效果
設置stretchModes,新增縮放和模糊效果

⚡︎SliverPersistentHeader#

  • 自定義效果的 header bar,實現sticky吸頂的效果
  • 可以製作一個複雜的子標題,當向下滾動時它會展開,但在向上滾動時會保持最小高度,顯示基本內容
  • pinned:為 true 時,header會固定在可視區域的頂部。為 false 時,header可以滑出可視區域,當用戶再次向下滑動時,此時不管header 已經被滑出了多遠,它都會立即出現在可視區域頂部並固定住,直到繼續下滑到原來的位置時,header才會重新回到原來的位置(不再固定在頂部)
  • floating:為 true 時,只要往下滑header就會根據偏移量延展,不需要回到頂部

SliverPersistentHeaderDelegate#

自定義的header元件需要實作 ,包含4個地方

  • minExtent:收起狀態下元件的高度
  • maxExtent:展開狀態下元件的高度。如果我們想讓header高度固定,則將 maxExtent 和 minExtent 指定為同樣的值即可
  • shouldRebuild():根據某些資料或條件改變來決定是否要更新
  • build():創建渲染內容

build()

  • shrinkOffset:偏移量,目前位置到最大的距離,展開和收縮都會變動
  • overlapsContent:是否在下拉、延伸時有覆蓋和重疊到其他Sliver
pinned = false & floating = false
pinned = false & floating = false
pinned = false & floating = true
pinned = false & floating = true
pinned = true & floating = false
pinned = true & floating = false
pinned = true & floating = true
pinned = true & floating = true
shrinkOffset & overlapsContent
shrinkOffset & overlapsContent

⚡︎SliverList#

  • 清單列表

delegate#

  • SliverChildListDelegate:已知的多個元件,全部一起渲染
  • SliverChildBuilderDelegate:按照資料來源來產生和渲染對應數量的子元件,提高性能(建議使用)
SliverList & SliverGrid
SliverList & SliverGrid

⚡︎SliverFixedExtentList#

  • 每個item在主軸方向上具有固定的高度/寬度,通過 itemExtent 設定
  • 效率比SliverList高,因為不用通過item的layout過程就可以知道每個item的範圍

⚡︎SliverGrid#

  • 網格清單
  • 僅實例化那些通過ViewPort(滾動視圖)的可見child widgets,提升性能

gridDelegate#

網格參數,以任意二維排列對元件排版

  • SliverGridDelegateWithFixedCrossAxisCount:根據次軸元素的數量
  • SliverGridDelegateWithMaxCrossAxisExtent:根據次軸元素的固定大小

delegate#

  • SliverChildBuilderDelegate:按照資料來源來產生和渲染對應數量的子元件,提高性能(建議使用)

⚡︎SliverFillRemaining#

  • 填滿視圖上的剩餘空間,通常用於slivers的最後一個

hasScrollBody#

  • 默認為true,決定child是否可滾動,根據拖拉的範圍來佔滿空間
  • 設置為false,根據child來決定填滿空間的大小

⚡︎SliverFillViewport#

  • 每一個Sliver元件都佔滿可視區域
  • 對應的可滾動元件為 PageView

⚡︎SliverVisibility#

  • 設置Sliver元件是否顯示

⚡︎SliverOpacity#

  • 設置Sliver元件的透明度

⚡︎SliverPadding#

  • 設置Sliver元件的Padding
  • 注意:不要用它來包裹 SliverPersistentHeader,因為它會使pinned失效,可以在自定義的delegate內部使用Padding

⚡︎SliverToBoxAdapter#

  • 包裹一般元件,並非所有元件都有Sliver版本
  • 可以將RenderBox適配為RenderSliver
  • 方便與CustomScrollView結合,產生精美的滾動體驗

範例#

GitHub - chyiiiiiiiiiiii/sliver_example: Sliver and CustomScrollView in FlutterYou can’t perform that action at this time. You signed in with another tab or window. You signed out in another tab or…github.com

其他文章#

關於我#

贊助#

謝謝你!花費時間看完,非常感謝。

如果覺得文章不錯的話可以贊助我,讓我有更多動力和熱情分享學習紀錄和生活!請我喝一杯咖啡吧~

https://www.buymeacoffee.com/yiichenhi

最後#

希望有幫助到你/妳,歡迎追蹤我,方便瀏覽最新的文章,也請別忘記給我拍手哦 (最多可以達到50次)!

本文原刊登於 Medium。