網站首頁 編程語言 正文
正文
當內容超過顯示視口(ViewPort)時,如果沒有特殊處理,Flutter則會提示Overflow錯誤。為此,Flutter提供了多種可滾動widget(Scrollable Widget)用于顯示列表和長布局。
Flutter中有兩種布局模型:
- 基于 RenderBox 的盒模型布局。
- 基于 Sliver ( RenderSliver ) 按需加載列表布局。
通常可滾動組件的子組件可能會非常多、占用的總高度也會非常大;如果要一次性將子組件全部構建出將會非常昂貴!為此,Flutter中提出一個Sliver(中文為“薄片”的意思)概念,Sliver 可以包含一個或多個子組件。Sliver 的主要作用是配合:加載子組件并確定每一個子組件的布局和繪制信息,如果 Sliver 可以包含多個子組件時,通常會實現按需加載模型。
只有當Sliver
出現在視口中時才會去構建它,這種模型也稱為“基于Sliver的列表按需加載模型”。可滾動組件中有很多都支持基于Sliver的按需加載模型,如ListView
、GridView
,但是也有不支持該模型的,如SingleChildScrollView
。
Flutter 中的可滾動主要由三個角色組成:Scrollable、Viewport 和 Sliver:
- Scrollable :用于處理滑動手勢,確定滑動偏移,滑動偏移變化時構建 Viewport 。
- Viewport:顯示的視窗,即列表的可視區域;
- Sliver:視窗里顯示的元素。
具體布局過程:
- Scrollable 監聽到用戶滑動行為后,根據最新的滑動偏移構建 Viewport 。
- Viewport 將當前視口信息和配置信息通過 SliverConstraints 傳遞給 Sliver。
- Sliver 中對子組件(RenderBox)按需進行構建和布局,然后確認自身的位置、繪制等信息,保存在 geometry 中(一個 SliverGeometry 類型的對象)
比如有一個 ListView,大小撐滿屏幕,假設它有 100 個列表項(都是RenderBox)且每個列表項高度相同,結構如下:
圖中白色區域為設備屏幕,也是 Scrollable 、 Viewport 和 Sliver 所占用的空間,三者所占用的空間重合,父子關系為:Sliver 父組件為 Viewport,Viewport的 父組件為 Scrollable 。注意ListView 中只有一個 Sliver,在 Sliver 中實現了子組件的按需加載。
其中頂部和底部灰色的區域為 cacheExtent,它表示預渲染的高度,需要注意這是在可視區域之外,如果 RenderBox 進入這個區域內,即使它還未顯示在屏幕上,也是要先進行構建的,預渲染是為了后面進入 Viewport 的時候更絲滑。cacheExtent 的默認值是 250,在構建可滾動列表時我們可以指定這個值,這個值最終會傳給 Viewport。
Scrollable
用于處理滑動手勢,確定滑動偏移,滑動偏移變化時構建 Viewport,我們看一下其關鍵的屬性:
Scrollable({ ... this.axisDirection = AxisDirection.down, this.controller, this.physics, required this.viewportBuilder, //后面介紹 })
- axisDirection 滾動方向。
- physics:此屬性接受一個ScrollPhysics類型的對象,它決定可滾動組件如何響應用戶操作,比如用戶滑動完抬起手指后,繼續執行動畫;或者滑動到邊界時,如何顯示。默認情況下,Flutter會根據具體平臺分別使用不同的ScrollPhysics對象,應用不同的顯示效果,如當滑動到邊界時,繼續拖動的話,在 iOS 上會出現彈性效果,而在 Android 上會出現微光效果。如果你想在所有平臺下使用同一種效果,可以顯式指定一個固定的ScrollPhysics,Flutter SDK中包含了兩個ScrollPhysics的子類,他們可以直接使用:
AlwaysScrollableScrollPhysics:總是可以滑動 NeverScrollableScrollPhysics:禁止滾動 BouncingScrollPhysics :內容超過一屏 上拉有回彈效果 ClampingScrollPhysics :包裹內容 不會有回彈
- controller:此屬性接受一個ScrollController對象。ScrollController的主要作用是控制滾動位置和監聽滾動事件。默認情況下,Widget樹中會有一個默認的PrimaryScrollController,如果子樹中的可滾動組件沒有顯式的指定controller,并且primary屬性值為true時(默認就為true),可滾動組件會使用這個默認的PrimaryScrollController。這種機制帶來的好處是父組件可以控制子樹中可滾動組件的滾動行為,例如,Scaffold正是使用這種機制在iOS中實現了點擊導航欄回到頂部的功能。
- viewportBuilder:構建 Viewport 的回調。當用戶滑動時,Scrollable 會調用此回調構建新的 Viewport,同時傳遞一個 ViewportOffset 類型的 offset 參數,該參數描述 Viewport 應該顯示那一部分內容。注意重新構建 Viewport 并不是一個昂貴的操作,因為 Viewport 本身也是 Widget,只是配置信息,Viewport 變化時對應的 RenderViewport 會更新信息,并不會隨著 Widget 進行重新構建。
主軸和縱軸
在可滾動組件的坐標描述中,通常將滾動方向稱為主軸,非滾動方向稱為縱軸。由于可滾動組件的默認方向一般都是沿垂直方向,所以默認情況下主軸就是指垂直方向,水平方向同理。
Viewport
Viewport 比較簡單,用于渲染當前視口中需要顯示 Sliver。
Viewport({ Key? key, this.axisDirection = AxisDirection.down, this.crossAxisDirection, this.anchor = 0.0, required ViewportOffset offset, // 用戶的滾動偏移 // 類型為Key,表示從什么地方開始繪制,默認是第一個元素 this.center, this.cacheExtent, // 預渲染區域 //該參數用于配合解釋cacheExtent的含義,也可以為主軸長度的乘數 this.cacheExtentStyle = CacheExtentStyle.pixel, this.clipBehavior = Clip.hardEdge, List<Widget> slivers = const <Widget>[], // 需要顯示的 Sliver 列表 })
需要注意的是:
- offset:該參數為Scrollabel 構建 Viewport 時傳入,它描述了 Viewport 應該顯示那一部分內容。
- cacheExtent 和 cacheExtentStyle:CacheExtentStyle 是一個枚舉,有 pixel 和 viewport 兩個取值。當 cacheExtentStyle 值為 pixel 時,cacheExtent 的值為預渲染區域的具體像素長度;當值為 viewport 時,cacheExtent 的值是一個乘數,表示有幾個 viewport 的長度,最終的預渲染區域的像素長度為:cacheExtent * viewport 的積, 這在每一個列表項都占滿整個 Viewport 時比較實用,這時 cacheExtent 的值就表示前后各緩存幾個頁面。
Sliver
Sliver 主要作用是對子組件進行構建和布局,比如 ListView 的 Sliver 需要實現子組件(列表項)按需加載功能,只有當列表項進入預渲染區域時才會去對它進行構建和布局、渲染。
Sliver 對應的渲染對象類型是 RenderSliver,RenderSliver 和 RenderBox 的相同點是都繼承自 RenderObject 類,不同點是在布局的時候約束信息不同。RenderBox 在布局時父組件傳遞給它的約束信息對應的是 BoxConstraints,只包含最大寬高的約束;而 RenderSliver 在布局時父組件(列表)傳遞給它的約束是對應的是 SliverConstraints。
可滾動組件的通用配置
幾乎所有的可滾動組件在構造時都能指定 scrollDirection(滑動的主軸)、reverse(滑動方向是否反向)、controller、physics 、cacheExtent ,這些屬性最終會透傳給對應的 Scrollable 和 Viewport,這些屬性我們可以認為是可滾動組件的通用屬性.
reverse
表示是否按照閱讀方向相反的方向滑動,如:scrollDirection
值為Axis.horizontal
時,即滑動發現為水平,如果閱讀方向是從左到右。
reverse
為true
時,那么滑動方向就是從右往左。
ScrollController
可滾動組件都有一個 controller 屬性,通過該屬性我們可以指定一個 ScrollController 來控制可滾動組件的滾動,比如可以通過ScrollController來同步多個組件的滑動聯動。
子節點緩存
按需加載子組件在大多數場景中都能有正收益,但是有些時候也會有副作用。比如有一個頁面,它由一個ListView 組成,我們希望在頁面頂部顯示一塊內容, 這部分內容的數據需要在每次頁面打開時通過網絡來獲取,為此我們通一個 Header 組件來實現,它是一個 StatefulWidget ,會在initState 中請求網絡數據,然后將它作為 ListView 的第一個孩子。現在問題來了,因為 ListView 是按需加載子節點的,這意味著如果 Header 滑出 Viewport 的預渲染區域之外時就會被銷毀,重新滑入后又會被重新構建,這樣就會發起多次網絡請求,不符合我們期望。
為了解決上述問題,可滾動組件提供了一種緩存子節點的通用解決方案,它允許開發者對特定的子界限進行緩存.
Scrollbar
Scrollbar
是一個Material風格的滾動指示器(滾動條),如果要給可滾動組件添加滾動條,只需將Scrollbar
作為可滾動組件的任意一個父級組件即可,如:
Scrollbar( child: SingleChildScrollView( ... ), );
Scrollbar
和CupertinoScrollbar
都是通過監聽滾動通知來確定滾動條位置的。
CupertinoScrollbar
CupertinoScrollbar
是 iOS 風格的滾動條,如果你使用的是Scrollbar
,那么在iOS平臺它會自動切換為CupertinoScrollbar
。
總結
原文鏈接:https://juejin.cn/post/7159383007936118820
相關推薦
- 2023-02-07 C++11中的引用限定符示例代碼_C 語言
- 2022-05-06 Python學習之循環方法詳解_python
- 2023-03-13 Android?Hilt依賴注入的使用講解_Android
- 2022-04-10 用python實現讀取xlsx表格操作_python
- 2023-07-26 webpack中plugin的工作原理
- 2022-05-27 python繪制棉棒圖的方法詳解_python
- 2022-11-06 Go+Redis實現延遲隊列實操_Golang
- 2023-02-07 C++內存模型與名稱空間概念講解_C 語言
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支