極端很容易,平衡才是最難的。
???? Flutter學(xué)習(xí)之八 Container
前言
Flutter中的ListView的地位,就好比于iOS中的UITableView,算是最常用的可滾動(dòng)組件之一,它可以沿一個(gè)方向線性排布所有子組件,并且它也支持列表項(xiàng)懶加載(在需要時(shí)才會(huì)創(chuàng)建)。
默認(rèn)構(gòu)造函數(shù)
我們看看ListView的默認(rèn)構(gòu)造函數(shù)定義:
ListView({
...
//可滾動(dòng)widget公共參數(shù)
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController? controller,
bool? primary,
ScrollPhysics? physics,
EdgeInsetsGeometry? padding,
//ListView各個(gè)構(gòu)造函數(shù)的共同參數(shù)
double? itemExtent,
Widget? prototypeItem, //列表項(xiàng)原型,后面解釋
bool shrinkWrap = false,
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
double? cacheExtent, // 預(yù)渲染區(qū)域長度
//子widget列表
List<Widget> children = const <Widget>[],
})
上面的可滑動(dòng)公共參數(shù)就不再贅述了,下面是ListView各個(gè)構(gòu)造函數(shù)(ListView有多個(gè)構(gòu)造函數(shù))的共同參數(shù),我們重點(diǎn)來看看這些參數(shù):
itemExtent:該參數(shù)如果不為null,則會(huì)強(qiáng)制children的“長度”為itemExtent的值;這里的“長度”是指滾動(dòng)方向上子組件的長度,也就是說如果滾動(dòng)方向是垂直方向,則itemExtent代表子組件的高度;如果滾動(dòng)方向?yàn)樗椒较?,則itemExtent就代表子組件的寬度。在ListView中,指定itemExtent比讓子組件自己決定自身長度會(huì)有更好的性能,這是因?yàn)橹付?code>itemExtent后,滾動(dòng)系統(tǒng)可以提前知道列表的長度,而無需每次構(gòu)建子組件時(shí)都去再計(jì)算一下,尤其是在滾動(dòng)位置頻繁變化時(shí)(滾動(dòng)系統(tǒng)需要頻繁去計(jì)算列表高度),和原生很像。prototypeItem:如果我們知道列表中的所有列表項(xiàng)長度都相同但不知道具體是多少,這時(shí)我們可以指定一個(gè)列表項(xiàng),該列表項(xiàng)被稱為prototypeItem(列表項(xiàng)原型)。指定prototypeItem后,可滾動(dòng)組件會(huì)在 layout 時(shí)計(jì)算一次它延主軸方向的長度,這樣也就預(yù)先知道了所有列表項(xiàng)的延主軸方向的長度,所以和指定itemExtent一樣,指定prototypeItem會(huì)有更好的性能。注意,itemExtent和prototypeItem互斥,不能同時(shí)指定它們。shrinkWrap:該屬性表示是否根據(jù)子組件的總長度來設(shè)置ListView的長度,默認(rèn)值為false。默認(rèn)情況下,ListView會(huì)在滾動(dòng)方向盡可能多的占用空間。當(dāng)ListView在一個(gè)無邊界(滾動(dòng)方向上)的容器中時(shí),shrinkWrap必須為true。addAutomaticKeepAlives:該屬性比較復(fù)雜,以后再說。addRepaintBoundaries:該屬性表示是否將列表項(xiàng)(子組件)包裹在RepaintBoundary組件中。RepaintBoundary讀者可以先簡單理解為它是一個(gè)”繪制邊界“,將列表項(xiàng)包裹在RepaintBoundary中可以避免列表項(xiàng)不必要的重繪,但是當(dāng)列表項(xiàng)重繪的開銷非常?。ㄈ缫粋€(gè)顏色塊,或者一個(gè)較短的文本)時(shí),不添加RepaintBoundary反而會(huì)更高效(具體原因會(huì)在本書后面 Flutter 繪制原理相關(guān)章節(jié)中介紹)。如果列表項(xiàng)自身來維護(hù)是否需要添加繪制邊界組件,則此參數(shù)應(yīng)該指定為false。
默認(rèn)構(gòu)造函數(shù)有一個(gè)children參數(shù),它接受一個(gè)Widget列表(List<Widget>)。這種方式適合只有少量的子組件數(shù)量已知且比較少的情況,反之則應(yīng)該使用ListView.builder 按需動(dòng)態(tài)構(gòu)建列表項(xiàng)。
注意:雖然這種方式將所有
children一次性傳遞給ListView,但子組件仍然是在需要時(shí)才會(huì)加載(build(如有)、布局、繪制),也就是說通過默認(rèn)構(gòu)造函數(shù)構(gòu)建的ListView也是基于Sliver的列表懶加載模型。
舉個(gè)栗子:
_listView() {
return ListView(
shrinkWrap: true,
padding: const EdgeInsets.all(20.0),
children: const <Widget>[
Text('張三'),
Text('李四'),
Text('王五'),
Text('趙六'),
],
);
}
效果如下:

可以看到,雖然使用默認(rèn)構(gòu)造函數(shù)創(chuàng)建的列表也是懶加載的,但我們還是需要提前將Widget創(chuàng)建好,等到真正需要加載的時(shí)候才會(huì)對(duì) Widget 進(jìn)行布局和繪制。
ListView.builder
ListView.builder適合列表項(xiàng)比較多或者列表項(xiàng)不確定的情況,下面看一下ListView.builder的核心參數(shù)列表:
ListView.builder({
// ListView公共參數(shù)已省略
...
required IndexedWidgetBuilder itemBuilder,
int itemCount,
...
})
+itemBuilder:它是列表項(xiàng)的構(gòu)建器,類型為IndexedWidgetBuilder,返回值為一個(gè)widget。當(dāng)列表滾動(dòng)到具體的index位置時(shí),會(huì)調(diào)用該構(gòu)建器構(gòu)建列表項(xiàng)。
-
itemCount:列表項(xiàng)的數(shù)量,如果為null,則為無限列表。
舉個(gè)栗子:
_listViewBuild() {
return ListView.builder(
itemCount: 100,
itemExtent: 50.0, //強(qiáng)制高度為50.0
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text("_listViewBuild $index"));
});
}
效果如下:

ListView.separated
ListView.separated可以在生成的列表項(xiàng)之間添加一個(gè)分割組件,它比ListView.builder多了一個(gè)separatorBuilder參數(shù),該參數(shù)是一個(gè)分割組件生成器。
舉個(gè)栗子:
_listViewSeparated() {
// widget預(yù)定義以供復(fù)用。
Widget blue = Container(color: Colors.blue, height: 10);
Widget red = Container(color: Colors.red, height: 10);
return ListView.separated(
itemCount: 100,
//列表項(xiàng)構(gòu)造器
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text("$index"));
},
//分割器構(gòu)造器
separatorBuilder: (BuildContext context, int index) {
return index % 2 == 0 ? blue : red;
},
);
}
效果如下:

當(dāng)然還有一個(gè)場景就是,每個(gè)item之間有一定的間距,也可以用這個(gè)實(shí)現(xiàn)。
總結(jié)
- 我在開發(fā)中一般使用
ListView.builder,里面的itemBuilder對(duì)應(yīng)的原生的cell,然后自定義cell就行了,用起來還是比較舒服的。 - 如果
item之間有分割線或是間距的,ListView.separated當(dāng)首選。
后記
常見的列表頁面ListView一般都能搞定,其對(duì)應(yīng)iOS 原生的UITableView。那原生的UICollectionView呢,在Flutter中要使用GridView,這個(gè)以后再寫。