網站首頁 編程語言 正文
ListView
ListView是最常用的可滾動組件之一,它可以沿一個方向線性排布所有子組件,并且它也支持基于Sliver的延遲構建模型。我們看看ListView的默認構造函數定義:
ListView({ ? ... ? ? //可滾動widget公共參數 ? Axis scrollDirection = Axis.vertical, ? bool reverse = false, ? ScrollController controller, ? bool primary, ? ScrollPhysics physics, ? EdgeInsetsGeometry padding, ? //ListView各個構造函數的共同參數 ? ? double itemExtent, ? bool shrinkWrap = false, ? bool addAutomaticKeepAlives = true, ? bool addRepaintBoundaries = true, ? double cacheExtent, ? //子widget列表 ? Listchildren = const [], })
上面參數分為兩組:第一組是可滾動組件的公共參數,前面已經介紹過,不再贅述;第二組是ListView各個構造函數(ListView有多個構造函數)的共同參數,我們重點來看看這些參數,:
- itemExtent:該參數如果不為null,則會強制children的“長度”為itemExtent的值;這里的“長度”是指滾動方向上子組件的長度,也就是說如果滾動方向是垂直方向,則itemExtent代表子組件的高度;如果滾動方向為水平方向,則itemExtent就代表子組件的寬度。在ListView中,指定itemExtent比讓子組件自己決定自身長度會更高效,這是因為指定itemExtent后,滾動系統可以提前知道列表的長度,而無需每次構建子組件時都去再計算一下,尤其是在滾動位置頻繁變化時(滾動系統需要頻繁去計算列表高度)。
- shrinkWrap:該屬性表示是否根據子組件的總長度來設置ListView的長度,默認值為false。默認情況下,ListView的會在滾動方向盡可能多的占用空間。當ListView在一個無邊界(滾動方向上)的容器中時,shrinkWrap必須為true。
- addAutomaticKeepAlives:該屬性表示是否將列表項(子組件)包裹在AutomaticKeepAlive組件中;典型地,在一個懶加載列表中,如果將列表項包裹在AutomaticKeepAlive中,在該列表項滑出視口時它也不會被GC(垃圾回收),它會使用KeepAliveNotification來保存其狀態。如果列表項自己維護其KeepAlive狀態,那么此參數必須置為false。
- addRepaintBoundaries:該屬性表示是否將列表項(子組件)包裹在RepaintBoundary組件中。當可滾動組件滾動時,將列表項包裹在RepaintBoundary中可以避免列表項重繪,但是當列表項重繪的開銷非常小(如一個顏色塊,或者一個較短的文本)時,不添加RepaintBoundary反而會更高效。和addAutomaticKeepAlive一樣,如果列表項自己維護其KeepAlive狀態,那么此參數必須置為false。
注意:上面這些參數并非ListView特有,其它可滾動組件也可能會擁有這些參數,它們的含義是相同的。
默認構造函數
默認構造函數有一個children參數,它接受一個Widget列表(List)。這種方式適合只有少量的子組件的情況,因為這種方式需要將所有children都提前創建好(這需要做大量工作),而不是等到子widget真正顯示的時候再創建,也就是說通過默認構造函數構建的ListView沒有應用基于Sliver的懶加載模型。實際上通過此方式創建的ListView和使用SingleChildScrollView+Column的方式沒有本質的區別。下面是一個例子:
ListView( ? shrinkWrap: true,? ? padding: const EdgeInsets.all(20.0), ? children:[ ? ? const Text('I\'m dedicating every day to you'), ? ? const Text('Domestic life was never quite my style'), ? ? const Text('When you smile, you knock me out, I fall apart'), ? ? const Text('And I thought I was so smart'), ? ], );
再次強調,可滾動組件通過一個List來作為其children屬性時,只適用于子組件較少的情況,這是一個通用規律,并非ListView自己的特性,像GridView也是如此。
ListView.builder
ListView.builder適合列表項比較多(或者無限)的情況,因為只有當子組件真正顯示的時候才會被創建,也就說通過該構造函數創建的ListView是支持基于Sliver的懶加載模型的。下面看一下ListView.builder的核心參數列表:
ListView.builder({ ? // ListView公共參數已省略 ? ? ... ? @required IndexedWidgetBuilder itemBuilder, ? int itemCount, ? ... })
- itemBuilder:它是列表項的構建器,類型為IndexedWidgetBuilder,返回值為一個widget。當列表滾動到具體的index位置時,會調用該構建器構建列表項。
- itemCount:列表項的數量,如果為null,則為無限列表。
可滾動組件的構造函數如果需要一個列表項Builder,那么通過該構造函數構建的可滾動組件通常就是支持基于Sliver的懶加載模型的,反之則不支持,這是個一般規律。我們在后面在介紹可滾動組件的構造函數時將不再專門說明其是否支持基于Sliver的懶加載模型了。
下面看一個例子:
ListView.builder( ? ? itemCount: 100, ? ? itemExtent: 50.0, //強制高度為50.0 ? ? itemBuilder: (BuildContext context, int index) { ? ? ? return ListTile(title: Text("$index")); ? ? } );
運行效果如圖所示:
ListView.separated
ListView.separated可以在生成的列表項之間添加一個分割組件,它比ListView.builder多了一個separatorBuilder參數,該參數是一個分割組件生成器。
下面我們看一個例子:奇數行添加一條藍色下劃線,偶數行添加一條綠色下劃線。
class ListView3 extends StatelessWidget { ? @override ? Widget build(BuildContext context) { ? ? //下劃線widget預定義以供復用。 ? ? ? Widget divider1=Divider(color: Colors.blue,); ? ? Widget divider2=Divider(color: Colors.green); ? ? return ListView.separated( ? ? ? ? itemCount: 100, ? ? ? ? //列表項構造器 ? ? ? ? itemBuilder: (BuildContext context, int index) { ? ? ? ? ? return ListTile(title: Text("$index")); ? ? ? ? }, ? ? ? ? //分割器構造器 ? ? ? ? separatorBuilder: (BuildContext context, int index) { ? ? ? ? ? return index%2==0?divider1:divider2; ? ? ? ? }, ? ? ); ? } }
運行效果如圖:
實例:無限加載列表
假設我們要從數據源異步分批拉取一些數據,然后用ListView展示,當我們滑動到列表末尾時,判斷是否需要再去拉取數據,如果是,則去拉取,拉取過程中在表尾顯示一個loading,拉取成功后將數據插入列表;如果不需要再去拉取,則在表尾提示"沒有更多"。代碼如下:
class InfiniteListView extends StatefulWidget { ? @override ? _InfiniteListViewState createState() => new _InfiniteListViewState(); } class _InfiniteListViewState extends State{ ? static const loadingTag = "##loading##"; //表尾標記 ? var _words = [loadingTag]; ? @override ? void initState() { ? ? super.initState(); ? ? _retrieveData(); ? } ? @override ? Widget build(BuildContext context) { ? ? return ListView.separated( ? ? ? itemCount: _words.length, ? ? ? itemBuilder: (context, index) { ? ? ? ? //如果到了表尾 ? ? ? ? if (_words[index] == loadingTag) { ? ? ? ? ? //不足100條,繼續獲取數據 ? ? ? ? ? if (_words.length - 1 < 100) { ? ? ? ? ? ? //獲取數據 ? ? ? ? ? ? _retrieveData(); ? ? ? ? ? ? //加載時顯示loading ? ? ? ? ? ? return Container( ? ? ? ? ? ? ? padding: const EdgeInsets.all(16.0), ? ? ? ? ? ? ? alignment: Alignment.center, ? ? ? ? ? ? ? child: SizedBox( ? ? ? ? ? ? ? ? ? width: 24.0, ? ? ? ? ? ? ? ? ? height: 24.0, ? ? ? ? ? ? ? ? ? child: CircularProgressIndicator(strokeWidth: 2.0) ? ? ? ? ? ? ? ), ? ? ? ? ? ? ); ? ? ? ? ? } else { ? ? ? ? ? ? //已經加載了100條數據,不再獲取數據。 ? ? ? ? ? ? return Container( ? ? ? ? ? ? ? ? alignment: Alignment.center, ? ? ? ? ? ? ? ? padding: EdgeInsets.all(16.0), ? ? ? ? ? ? ? ? child: Text("沒有更多了", style: TextStyle(color: Colors.grey),) ? ? ? ? ? ? ); ? ? ? ? ? } ? ? ? ? } ? ? ? ? //顯示單詞列表項 ? ? ? ? return ListTile(title: Text(_words[index])); ? ? ? }, ? ? ? separatorBuilder: (context, index) => Divider(height: .0), ? ? ); ? } ? void _retrieveData() { ? ? Future.delayed(Duration(seconds: 2)).then((e) { ? ? ? _words.insertAll(_words.length - 1, ? ? ? ? ? //每次生成20個單詞 ? ? ? ? ? generateWordPairs().take(20).map((e) => e.asPascalCase).toList() ? ? ? ); ? ? ? setState(() { ? ? ? ? //重新構建列表 ? ? ? }); ? ? }); ? } }
運行后效果如圖所示:
代碼比較簡單,可以參照代碼中的注釋理解。需要說明的是,_retrieveData()的功能是模擬從數據源異步獲取數據,我們使用english_words包的generateWordPairs()方法每次生成20個單詞。
添加固定列表頭
很多時候我們需要給列表添加一個固定表頭,比如我們想實現一個商品列表,需要在列表頂部添加一個“商品列表”標題,期望的效果如圖所示:
我們按照之前經驗,寫出如下代碼:
@override Widget build(BuildContext context) { ? return Column(children:[ ? ? ListTile(title:Text("商品列表")), ? ? ListView.builder(itemBuilder: (BuildContext context, int index) { ? ? ? ? return ListTile(title: Text("$index")); ? ? }), ? ]); }
然后運行,發現并沒有出現我們期望的效果,相反觸發了一個異常;
Error caught by rendering library, thrown during performResize()。
Vertical viewport was given unbounded height ...
從異常信息中我們可以看到是因為ListView高度邊界無法確定引起,所以解決的辦法也很明顯,我們需要給ListView指定邊界,我們通過SizedBox指定一個列表高度看看是否生效:
... //省略無關代碼 SizedBox( ? ? height: 400, //指定列表高度為400 ? ? child: ListView.builder(itemBuilder: (BuildContext context, int index) { ? ? ? ? return ListTile(title: Text("$index")); ? ? }), ), ...
運行效果如圖所示:
可以看到,現在沒有觸發異常并且列表已經顯示出來了,但是我們的手機屏幕高度要大于400,所以底部會有一些空白。那如果我們要實現列表鋪滿除表頭以外的屏幕空間應該怎么做?直觀的方法是我們去動態計算,用屏幕高度減去狀態欄、導航欄、表頭的高度即為剩余屏幕高度,代碼如下:
... //省略無關代碼 SizedBox( ? //Material設計規范中狀態欄、導航欄、ListTile高度分別為24、56、56? ? height: MediaQuery.of(context).size.height-24-56-56, ? child: ListView.builder(itemBuilder: (BuildContext context, int index) { ? ? return ListTile(title: Text("$index")); ? }), ) ...
運行效果如下圖所示:
可以看到,我們期望的效果實現了,但是這種方法并不優雅,如果頁面布局發生變化,比如表頭布局調整導致表頭高度改變,那么剩余空間的高度就得重新計算。那么有什么方法可以自動拉伸ListView以填充屏幕剩余空間的方法嗎?當然有!答案就是Flex。前面已經介紹過在彈性布局中,可以使用Expanded自動拉伸組件大小,并且我們也說過Column是繼承自Flex的,所以我們可以直接使用Column+Expanded來實現,代碼如下:
@override Widget build(BuildContext context) { ? return Column(children:[ ? ? ListTile(title:Text("商品列表")), ? ? Expanded( ? ? ? child: ListView.builder(itemBuilder: (BuildContext context, int index) { ? ? ? ? return ListTile(title: Text("$index")); ? ? ? }), ? ? ), ? ]); }
運行后,和上圖一樣,完美實現了!
總結:
本節主要介紹了ListView的一些公共參數以及常用的構造函數。不同的構造函數對應了不同的列表項生成模型,如果需要自定義列表項生成模型,可以通過ListView.custom來自定義,它需要實現一個SliverChildDelegate用來給ListView生成列表項組件,更多詳情請參考API文檔。
原文鏈接:https://blog.csdn.net/m0_46369686/article/details/104917417
相關推薦
- 2022-04-10 Android中Protobuf的基本使用介紹_Android
- 2022-12-29 react中將html字符串渲染到頁面的實現方式_React
- 2022-06-08 如何在springboot中使用Thymeleaf
- 2022-07-04 基于ASP.NET實現單點登錄(SSO)的示例代碼_實用技巧
- 2023-03-11 Golang的Fork/Join實現代碼_Golang
- 2022-03-30 Android實現屏幕保持常亮功能_Android
- 2022-12-05 關于adfuller函數返回值的參數說明與記錄_python
- 2022-09-06 .net6?使用Senparc開發小程序配置過程_實用技巧
- 最近更新
-
- 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同步修改后的遠程分支