極端很容易鳞贷,平衡才是最難的。
???? 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ū)域長(zhǎng)度
//子widget列表
List<Widget> children = const <Widget>[],
})
上面的可滑動(dòng)公共參數(shù)就不再贅述了翁脆,下面是ListView
各個(gè)構(gòu)造函數(shù)(ListView
有多個(gè)構(gòu)造函數(shù))的共同參數(shù)眷蚓,我們重點(diǎn)來(lái)看看這些參數(shù):
itemExtent
:該參數(shù)如果不為null
,則會(huì)強(qiáng)制children
的“長(zhǎng)度”為itemExtent
的值反番;這里的“長(zhǎng)度”是指滾動(dòng)方向上子組件的長(zhǎng)度沙热,也就是說(shuō)如果滾動(dòng)方向是垂直方向,則itemExtent
代表子組件的高度爵川;如果滾動(dòng)方向?yàn)樗椒较蚯薰保瑒titemExtent
就代表子組件的寬度。在ListView
中颇蜡,指定itemExtent
比讓子組件自己決定自身長(zhǎng)度會(huì)有更好的性能鳖目,這是因?yàn)橹付?code>itemExtent后,滾動(dòng)系統(tǒng)可以提前知道列表的長(zhǎng)度,而無(wú)需每次構(gòu)建子組件時(shí)都去再計(jì)算一下抢韭,尤其是在滾動(dòng)位置頻繁變化時(shí)(滾動(dòng)系統(tǒng)需要頻繁去計(jì)算列表高度),和原生很像鳍贾。prototypeItem
:如果我們知道列表中的所有列表項(xiàng)長(zhǎng)度都相同但不知道具體是多少,這時(shí)我們可以指定一個(gè)列表項(xiàng)咆爽,該列表項(xiàng)被稱為prototypeItem
(列表項(xiàng)原型)。指定prototypeItem
后,可滾動(dòng)組件會(huì)在 layout 時(shí)計(jì)算一次它延主軸方向的長(zhǎng)度漾稀,這樣也就預(yù)先知道了所有列表項(xiàng)的延主軸方向的長(zhǎng)度,所以和指定itemExtent
一樣缕贡,指定prototypeItem
會(huì)有更好的性能收擦。注意,itemExtent
和prototypeItem
互斥宴猾,不能同時(shí)指定它們。shrinkWrap
:該屬性表示是否根據(jù)子組件的總長(zhǎng)度來(lái)設(shè)置ListView
的長(zhǎng)度讹剔,默認(rèn)值為false
。默認(rèn)情況下由捎,ListView
會(huì)在滾動(dòng)方向盡可能多的占用空間。當(dāng)ListView
在一個(gè)無(wú)邊界(滾動(dòng)方向上)的容器中時(shí)为居,shrinkWrap
必須為true
。addAutomaticKeepAlives
:該屬性比較復(fù)雜,以后再說(shuō)蹬音。addRepaintBoundaries
:該屬性表示是否將列表項(xiàng)(子組件)包裹在RepaintBoundary
組件中劫狠。RepaintBoundary
讀者可以先簡(jiǎn)單理解為它是一個(gè)”繪制邊界“,將列表項(xiàng)包裹在RepaintBoundary
中可以避免列表項(xiàng)不必要的重繪懦砂,但是當(dāng)列表項(xiàng)重繪的開(kāi)銷非常小(如一個(gè)顏色塊,或者一個(gè)較短的文本)時(shí),不添加RepaintBoundary
反而會(huì)更高效(具體原因會(huì)在本書(shū)后面 Flutter 繪制原理相關(guān)章節(jié)中介紹)。如果列表項(xiàng)自身來(lái)維護(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(如有)、布局、繪制)暖呕,也就是說(shuō)通過(guò)默認(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
蔓钟,則為無(wú)限列表侣集。
舉個(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è)場(chǎng)景就是屎媳,每個(gè)item
之間有一定的間距丹禀,也可以用這個(gè)實(shí)現(xiàn)。
總結(jié)
- 我在開(kāi)發(fā)中一般使用
ListView.builder
,里面的itemBuilder
對(duì)應(yīng)的原生的cell
,然后自定義cell
就行了,用起來(lái)還是比較舒服的。 - 如果
item
之間有分割線或是間距的面哥,ListView.separated
當(dāng)首選吱涉。
后記
常見(jiàn)的列表頁(yè)面ListView
一般都能搞定鳖链,其對(duì)應(yīng)iOS 原生的UITableView
。那原生的UICollectionView
呢,在Flutter中要使用GridView
,這個(gè)以后再寫(xiě)。