<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
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列表 List<Widget> children = const <Widget>[], })
上面引數分為兩組:第一組是可捲動元件的公共引數,前面已經介紹過,不再贅述;第二組是ListView各個建構函式(ListView有多個建構函式)的共同引數,我們重點來看看這些引數,:
注意:上面這些引數並非ListView特有,其它可捲動元件也可能會擁有這些引數,它們的含義是相同的。
預設建構函式
預設建構函式有一個children引數,它接受一個Widget列表(List)。這種方式適合只有少量的子元件的情況,因為這種方式需要將所有children都提前建立好(這需要做大量工作),而不是等到子widget真正顯示的時候再建立,也就是說通過預設建構函式構建的ListView沒有應用基於Sliver的懶載入模型。實際上通過此方式建立的ListView和使用SingleChildScrollView+Column的方式沒有本質的區別。下面是一個例子:
ListView( shrinkWrap: true, padding: const EdgeInsets.all(20.0), children: <Widget>[ 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, ... })
可捲動元件的建構函式如果需要一個列表項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<InfiniteListView> { static const loadingTag = "##loading##"; //表尾標記 var _words = <String>[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: <Widget>[ 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: <Widget>[ ListTile(title:Text("商品列表")), Expanded( child: ListView.builder(itemBuilder: (BuildContext context, int index) { return ListTile(title: Text("$index")); }), ), ]); }
執行後,和上圖一樣,完美實現了!
總結:
本節主要介紹了ListView的一些公共引數以及常用的建構函式。不同的建構函式對應了不同的列表項生成模型,如果需要自定義列表項生成模型,可以通過ListView.custom來自定義,它需要實現一個SliverChildDelegate用來給ListView生成列表項元件,更多詳情請參考API檔案。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援it145.com。
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45