Flutter之可滾動組件實例詳解
正文
當(dāng)內(nèi)容超過顯示視口(ViewPort)時,如果沒有特殊處理,F(xiàn)lutter則會提示Overflow錯誤。為此,F(xiàn)lutter提供了多種可滾動widget(Scrollable Widget)用于顯示列表和長布局。
Flutter中有兩種布局模型:
- 基于 RenderBox 的盒模型布局。
- 基于 Sliver ( RenderSliver ) 按需加載列表布局。
通??蓾L動組件的子組件可能會非常多、占用的總高度也會非常大;如果要一次性將子組件全部構(gòu)建出將會非常昂貴!為此,F(xiàn)lutter中提出一個Sliver(中文為“薄片”的意思)概念,Sliver 可以包含一個或多個子組件。Sliver 的主要作用是配合:加載子組件并確定每一個子組件的布局和繪制信息,如果 Sliver 可以包含多個子組件時,通常會實現(xiàn)按需加載模型。
只有當(dāng)Sliver
出現(xiàn)在視口中時才會去構(gòu)建它,這種模型也稱為“基于Sliver的列表按需加載模型”。可滾動組件中有很多都支持基于Sliver的按需加載模型,如ListView
、GridView
,但是也有不支持該模型的,如SingleChildScrollView
。
Flutter 中的可滾動主要由三個角色組成:Scrollable、Viewport 和 Sliver:
- Scrollable :用于處理滑動手勢,確定滑動偏移,滑動偏移變化時構(gòu)建 Viewport 。
- Viewport:顯示的視窗,即列表的可視區(qū)域;
- Sliver:視窗里顯示的元素。
具體布局過程:
- Scrollable 監(jiān)聽到用戶滑動行為后,根據(jù)最新的滑動偏移構(gòu)建 Viewport 。
- Viewport 將當(dāng)前視口信息和配置信息通過 SliverConstraints 傳遞給 Sliver。
- Sliver 中對子組件(RenderBox)按需進(jìn)行構(gòu)建和布局,然后確認(rèn)自身的位置、繪制等信息,保存在 geometry 中(一個 SliverGeometry 類型的對象)
比如有一個 ListView,大小撐滿屏幕,假設(shè)它有 100 個列表項(都是RenderBox)且每個列表項高度相同,結(jié)構(gòu)如下:
圖中白色區(qū)域為設(shè)備屏幕,也是 Scrollable 、 Viewport 和 Sliver 所占用的空間,三者所占用的空間重合,父子關(guān)系為:Sliver 父組件為 Viewport,Viewport的 父組件為 Scrollable 。注意ListView 中只有一個 Sliver,在 Sliver 中實現(xiàn)了子組件的按需加載。
其中頂部和底部灰色的區(qū)域為 cacheExtent,它表示預(yù)渲染的高度,需要注意這是在可視區(qū)域之外,如果 RenderBox 進(jìn)入這個區(qū)域內(nèi),即使它還未顯示在屏幕上,也是要先進(jìn)行構(gòu)建的,預(yù)渲染是為了后面進(jìn)入 Viewport 的時候更絲滑。cacheExtent 的默認(rèn)值是 250,在構(gòu)建可滾動列表時我們可以指定這個值,這個值最終會傳給 Viewport。
Scrollable
用于處理滑動手勢,確定滑動偏移,滑動偏移變化時構(gòu)建 Viewport,我們看一下其關(guān)鍵的屬性:
Scrollable({ ... this.axisDirection = AxisDirection.down, this.controller, this.physics, required this.viewportBuilder, //后面介紹 })
- axisDirection 滾動方向。
- physics:此屬性接受一個ScrollPhysics類型的對象,它決定可滾動組件如何響應(yīng)用戶操作,比如用戶滑動完抬起手指后,繼續(xù)執(zhí)行動畫;或者滑動到邊界時,如何顯示。默認(rèn)情況下,F(xiàn)lutter會根據(jù)具體平臺分別使用不同的ScrollPhysics對象,應(yīng)用不同的顯示效果,如當(dāng)滑動到邊界時,繼續(xù)拖動的話,在 iOS 上會出現(xiàn)彈性效果,而在 Android 上會出現(xiàn)微光效果。如果你想在所有平臺下使用同一種效果,可以顯式指定一個固定的ScrollPhysics,F(xiàn)lutter SDK中包含了兩個ScrollPhysics的子類,他們可以直接使用:
AlwaysScrollableScrollPhysics:總是可以滑動 NeverScrollableScrollPhysics:禁止?jié)L動 BouncingScrollPhysics :內(nèi)容超過一屏 上拉有回彈效果 ClampingScrollPhysics :包裹內(nèi)容 不會有回彈
- controller:此屬性接受一個ScrollController對象。ScrollController的主要作用是控制滾動位置和監(jiān)聽滾動事件。默認(rèn)情況下,Widget樹中會有一個默認(rèn)的PrimaryScrollController,如果子樹中的可滾動組件沒有顯式的指定controller,并且primary屬性值為true時(默認(rèn)就為true),可滾動組件會使用這個默認(rèn)的PrimaryScrollController。這種機制帶來的好處是父組件可以控制子樹中可滾動組件的滾動行為,例如,Scaffold正是使用這種機制在iOS中實現(xiàn)了點擊導(dǎo)航欄回到頂部的功能。
- viewportBuilder:構(gòu)建 Viewport 的回調(diào)。當(dāng)用戶滑動時,Scrollable 會調(diào)用此回調(diào)構(gòu)建新的 Viewport,同時傳遞一個 ViewportOffset 類型的 offset 參數(shù),該參數(shù)描述 Viewport 應(yīng)該顯示那一部分內(nèi)容。注意重新構(gòu)建 Viewport 并不是一個昂貴的操作,因為 Viewport 本身也是 Widget,只是配置信息,Viewport 變化時對應(yīng)的 RenderViewport 會更新信息,并不會隨著 Widget 進(jìn)行重新構(gòu)建。
主軸和縱軸
在可滾動組件的坐標(biāo)描述中,通常將滾動方向稱為主軸,非滾動方向稱為縱軸。由于可滾動組件的默認(rèn)方向一般都是沿垂直方向,所以默認(rèn)情況下主軸就是指垂直方向,水平方向同理。
Viewport
Viewport 比較簡單,用于渲染當(dāng)前視口中需要顯示 Sliver。
Viewport({ Key? key, this.axisDirection = AxisDirection.down, this.crossAxisDirection, this.anchor = 0.0, required ViewportOffset offset, // 用戶的滾動偏移 // 類型為Key,表示從什么地方開始繪制,默認(rèn)是第一個元素 this.center, this.cacheExtent, // 預(yù)渲染區(qū)域 //該參數(shù)用于配合解釋cacheExtent的含義,也可以為主軸長度的乘數(shù) this.cacheExtentStyle = CacheExtentStyle.pixel, this.clipBehavior = Clip.hardEdge, List<Widget> slivers = const <Widget>[], // 需要顯示的 Sliver 列表 })
需要注意的是:
- offset:該參數(shù)為Scrollabel 構(gòu)建 Viewport 時傳入,它描述了 Viewport 應(yīng)該顯示那一部分內(nèi)容。
- cacheExtent 和 cacheExtentStyle:CacheExtentStyle 是一個枚舉,有 pixel 和 viewport 兩個取值。當(dāng) cacheExtentStyle 值為 pixel 時,cacheExtent 的值為預(yù)渲染區(qū)域的具體像素長度;當(dāng)值為 viewport 時,cacheExtent 的值是一個乘數(shù),表示有幾個 viewport 的長度,最終的預(yù)渲染區(qū)域的像素長度為:cacheExtent * viewport 的積, 這在每一個列表項都占滿整個 Viewport 時比較實用,這時 cacheExtent 的值就表示前后各緩存幾個頁面。
Sliver
Sliver 主要作用是對子組件進(jìn)行構(gòu)建和布局,比如 ListView 的 Sliver 需要實現(xiàn)子組件(列表項)按需加載功能,只有當(dāng)列表項進(jìn)入預(yù)渲染區(qū)域時才會去對它進(jìn)行構(gòu)建和布局、渲染。
Sliver 對應(yīng)的渲染對象類型是 RenderSliver,RenderSliver 和 RenderBox 的相同點是都繼承自 RenderObject 類,不同點是在布局的時候約束信息不同。RenderBox 在布局時父組件傳遞給它的約束信息對應(yīng)的是 BoxConstraints,只包含最大寬高的約束;而 RenderSliver 在布局時父組件(列表)傳遞給它的約束是對應(yīng)的是 SliverConstraints。
可滾動組件的通用配置
幾乎所有的可滾動組件在構(gòu)造時都能指定 scrollDirection(滑動的主軸)、reverse(滑動方向是否反向)、controller、physics 、cacheExtent ,這些屬性最終會透傳給對應(yīng)的 Scrollable 和 Viewport,這些屬性我們可以認(rèn)為是可滾動組件的通用屬性.
reverse
表示是否按照閱讀方向相反的方向滑動,如:scrollDirection
值為Axis.horizontal
時,即滑動發(fā)現(xiàn)為水平,如果閱讀方向是從左到右。
reverse
為true
時,那么滑動方向就是從右往左。
ScrollController
可滾動組件都有一個 controller 屬性,通過該屬性我們可以指定一個 ScrollController 來控制可滾動組件的滾動,比如可以通過ScrollController來同步多個組件的滑動聯(lián)動。
子節(jié)點緩存
按需加載子組件在大多數(shù)場景中都能有正收益,但是有些時候也會有副作用。比如有一個頁面,它由一個ListView 組成,我們希望在頁面頂部顯示一塊內(nèi)容, 這部分內(nèi)容的數(shù)據(jù)需要在每次頁面打開時通過網(wǎng)絡(luò)來獲取,為此我們通一個 Header 組件來實現(xiàn),它是一個 StatefulWidget ,會在initState 中請求網(wǎng)絡(luò)數(shù)據(jù),然后將它作為 ListView 的第一個孩子?,F(xiàn)在問題來了,因為 ListView 是按需加載子節(jié)點的,這意味著如果 Header 滑出 Viewport 的預(yù)渲染區(qū)域之外時就會被銷毀,重新滑入后又會被重新構(gòu)建,這樣就會發(fā)起多次網(wǎng)絡(luò)請求,不符合我們期望。
為了解決上述問題,可滾動組件提供了一種緩存子節(jié)點的通用解決方案,它允許開發(fā)者對特定的子界限進(jìn)行緩存.
Scrollbar
Scrollbar
是一個Material風(fēng)格的滾動指示器(滾動條),如果要給可滾動組件添加滾動條,只需將Scrollbar
作為可滾動組件的任意一個父級組件即可,如:
Scrollbar( child: SingleChildScrollView( ... ), );
Scrollbar
和CupertinoScrollbar
都是通過監(jiān)聽滾動通知來確定滾動條位置的。
CupertinoScrollbar
CupertinoScrollbar
是 iOS 風(fēng)格的滾動條,如果你使用的是Scrollbar
,那么在iOS平臺它會自動切換為CupertinoScrollbar
。
總結(jié)
本篇介紹了可滾動組件的概念和具體的組成,構(gòu)造。后續(xù)會具體介紹一些可滾動組件的使用詳解。如ListView,GridView等,更多關(guān)于Flutter 可滾動組件的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
上傳IPA出現(xiàn)的錯誤提示“application loader“上傳出錯解決方法
這篇文章主要介紹了上傳IPA出現(xiàn)的錯誤提示“application loader“上傳出錯解決方法的相關(guān)資料,需要的朋友可以參考下2017-06-06ios基于UITableViewController實現(xiàn)列表
這篇文章主要介紹了ios基于UITableViewController實現(xiàn)列表的相關(guān)資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-01-01IOS 屏幕適配方案實現(xiàn)縮放window的示例代碼
這篇文章主要介紹了IOS 屏幕適配方案實現(xiàn)縮放window的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04