前言
它可以沿一个方向线性排布所有子组件,并且它也可以支持基于Sliver的延迟构建模型。
接口描述
1ListView({ 2 Key key, 3 // 可滚动widget公共参数 4 Axis scrollDirection = Axis.vertical, 5 bool reverse = false, 6 ScrollController controller, 7 bool primary, 8 ScrollPhysics physics, 9 EdgeInsetsGeometry padding, 10 // ListView各个构造函数的共同参数 11 // 该参数如果不为null,则会强制children的“长度”为itemExtent的值;这里的“长度”是指滚动方向上子组件的长度,也就是说如果滚动方向是垂直方向,则itemExtent代表子组件的高度;如果滚动方向为水平方向,则itemExtent就代表子组件的宽度。在ListView中,指定itemExtent比让子组件自己决定自身长度会更高效,这是因为指定itemExtent后,滚动系统可以提前知道列表的长度,而无需每次构建子组件时都去再计算一下,尤其是在滚动位置频繁变化时(滚动系统需要频繁去计算列表高度)。 12 this.itemExtent, 13 // 该属性表示是否根据子组件的总长度来设置ListView的长度,默认值为false 。默认情况下,ListView的会在滚动方向尽可能多的占用空间。当ListView在一个无边界(滚动方向上)的容器中时,shrinkWrap必须为true。 14 bool shrinkWrap = false, 15 // 该属性表示是否将列表项(子组件)包裹在AutomaticKeepAlive 组件中;典型地,在一个懒加载列表中,如果将列表项包裹在AutomaticKeepAlive中,在该列表项滑出视口时它也不会被GC(垃圾回收),它会使用KeepAliveNotification来保存其状态。如果列表项自己维护其KeepAlive状态,那么此参数必须置为false。 16 bool addAutomaticKeepAlives = true, 17 // 该属性表示是否将列表项(子组件)包裹在RepaintBoundary组件中。当可滚动组件滚动时,将列表项包裹在RepaintBoundary中可以避免列表项重绘,但是当列表项重绘的开销非常小(如一个颜色块,或者一个较短的文本)时,不添加RepaintBoundary反而会更高效。和addAutomaticKeepAlive一样,如果列表项自己维护其KeepAlive状态,那么此参数必须置为false。 18 bool addRepaintBoundaries = true, 19 bool addSemanticIndexes = true, 20 double cacheExtent, 21 // 子widget列表 22 List<Widget> children = const <Widget>[], 23 int semanticChildCount, 24 DragStartBehavior dragStartBehavior = DragStartBehavior.start, 25 }) 26
代码示例
1import 'package:english_words/english_words.dart'; 2import 'package:flutter/material.dart'; 3 4// ListView 默认构造函数 5/* 默认构造函数有一个children参数,它接受一个Widget列表(List)。这种方式适合只有少量的子组件的情况,因为这种方式需要将所有children都提前创建好(这需要做大量工作),而不是等到子widget真正显示的时候再创建,也就是说通过默认构造函数构建的ListView没有应用基于Sliver的懒加载模型。实际上通过此方式创建的ListView和使用SingleChildScrollView+Column的方式没有本质的区别。*/ 6class ListViewTest1 extends StatelessWidget { 7 @override 8 Widget build(BuildContext context) { 9 return Scaffold( 10 appBar: AppBar( 11 title: Text('ListView 默认构造函数'), 12 ), 13 body: ListView( 14 shrinkWrap: true, 15 padding: const EdgeInsets.all(20.0), 16 children: <Widget>[ 17 const Text('I\'m haha'), 18 const Text('biubiubiu`'), 19 const Text('qiuqiuqiu`'), 20 const Text('dindindin`'), 21 const Text('I\'m haha'), 22 const Text('biubiubiu`'), 23 const Text('qiuqiuqiu`'), 24 const Text('dindindin`'), 25 const Text('I\'m haha'), 26 const Text('biubiubiu`'), 27 const Text('qiuqiuqiu`'), 28 const Text('dindindin`'), 29 ], 30 ), 31 ); 32 } 33} 34 35 36// ListView.builder 37/* ListView.builder适合列表项比较多(或者无限)的情况,因为只有当子组件真正显示的时候才会被创建,也就说通过该构造函数创建的ListView是支持基于Sliver的懒加载模型的。*/ 38class ListViewTest2 extends StatelessWidget { 39 @override 40 Widget build(BuildContext context) { 41 return Scaffold( 42 appBar: AppBar( 43 title: Text('ListView 默认构造函数'), 44 ), 45 body: ListView.builder( 46 // 列表项的数量,如果为null,则为无限列表 47 itemCount: 100, 48 itemExtent: 50.0, 49 itemBuilder: (BuildContext context, int index) { 50 return ListTile(title: Text("$index"),); 51 } 52 ) 53 ); 54 } 55} 56 57 58// ListView.separated 59/*ListView.separated可以在生成的列表项之间添加一个分割组件,它比ListView.builder多了一个separatorBuilder参数,该参数是一个分割组件生成器。 */ 60class ListViewTest3 extends StatelessWidget { 61 62 Widget build(BuildContext context) { 63 // 下划线widget预定义以供复用 64 Widget divider1 = Divider(color: Colors.blue,); 65 Widget divider2 = Divider(color: Colors.green,); 66 return Scaffold( 67 appBar: AppBar( 68 title: Text('ListView.separated'), 69 ), 70 body: ListView.separated( 71 // 列表项构造器 72 itemBuilder: (BuildContext context, int index) { 73 return ListTile(title: Text('$index'),); 74 }, 75 // 分割器构造器 76 separatorBuilder: (BuildContext context, int index) { 77 return index%2==0?divider1:divider2; 78 }, 79 itemCount: 100 80 ), 81 ); 82 } 83} 84 85 86// 实例:无限加载列表 87class InfiniteListView extends StatefulWidget { 88 @override 89 _InfiniteListViewState createState() => _InfiniteListViewState(); 90} 91 92class _InfiniteListViewState extends State<InfiniteListView> { 93 // 表尾标记 94 static const loadingTag = "##loadding##"; 95 var _words = <String>[loadingTag]; 96 97 @override 98 void initState() { 99 super.initState(); 100 _retrieveData(); 101 } 102 103 @override 104 Widget build(BuildContext context) { 105 return Scaffold( 106 appBar: AppBar( 107 title: Text('ListView.separated'), 108 ), 109 body: ListView.separated( 110 itemBuilder: (context, index) { 111 // 如果到了表尾 112 if (_words[index] == loadingTag) { 113 // 不足100条,继续获取数据 114 if (_words.length - 1 < 100) { 115 // 获取数据 116 _retrieveData(); 117 // 加载时显示loading 118 return Container( 119 padding: const EdgeInsets.all(16.0), 120 alignment: Alignment.center, 121 child: SizedBox( 122 width: 24.0, 123 height: 24.0, 124 child: CircularProgressIndicator(strokeWidth: 2.0,), 125 ), 126 ); 127 } else { 128 // 已经加载了100条数据,不再获取数据 129 return Container( 130 alignment: Alignment.center, 131 padding: EdgeInsets.all(16.0), 132 child: Text('已经到我的底线了ฅʕ•̫͡•ʔฅ'), 133 ); 134 } 135 } 136 // 显示单词列表项 137 return ListTile(title: Text(_words[index]),); 138 }, 139 separatorBuilder: (context, index) => Divider(height: .0,), 140 itemCount: _words.length, 141 ), 142 ); 143 } 144 145 // 模拟从数据源异步获取数据 146 void _retrieveData() { 147 Future.delayed(Duration(seconds: 2)).then((e) { 148 _words.insertAll( 149 _words.length - 1, 150 // 每次生成20个单词 151 generateWordPairs().take(20).map((e) => e.asPascalCase).toList() 152 ); 153 setState(() { 154 // 重新构建列表 155 }); 156 }); 157 } 158} 159
总结
不同的构造函数对应了不同的列表项生成模型,如果需要自定义列表项生成模型,可以通过ListView.custom来自定义,它需要实现一个SliverChildDelegate用来给ListView生成列表项组件。