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