ListView是最常用的可滾動(dòng)組件之一,它可以沿一個(gè)方向線性排布所有子組件,并且它也支持列表項(xiàng)懶加載(在需要時(shí)才會(huì)創(chuàng)建)
1.默認(rèn)構(gòu)造函數(shù)
我們看看ListView的默認(rèn)構(gòu)造函數(shù)定義:
注意:雖然這種方式將所有children一次性傳遞給 ListView,但子組件)仍然是在需要時(shí)才會(huì)加載(build(如有)忍啸、布局仰坦、繪制),也就是說(shuō)通過(guò)默認(rèn)構(gòu)造函數(shù)構(gòu)建的 ListView 也是基于 Sliver 的列表懶加載模型计雌∏幕危可以看到,雖然使用默認(rèn)構(gòu)造函數(shù)創(chuàng)建的列表也是懶加載的凿滤,但我們還是需要提前將 Widget 創(chuàng)建好妈橄,等到真正需要加載的時(shí)候才會(huì)對(duì) Widget 進(jìn)行布局和繪制。所以我們使用最多還是ListView.builder和ListView.separated
2.ListView.builder
ListView.builder 適合列表項(xiàng)比較多或者列表項(xiàng)不確定的情況翁脆,下面看一下ListView.builder的核心參數(shù)列表:
- itemBuilder:它是列表項(xiàng)的構(gòu)建器眷蚓,類(lèi)型為IndexedWidgetBuilder,返回值為一個(gè)widget反番。當(dāng)列表滾動(dòng)到具體的index位置時(shí)沙热,會(huì)調(diào)用該構(gòu)建器構(gòu)建列表項(xiàng)。
- itemCount:列表項(xiàng)的數(shù)量恬口,如果為null校读,則為無(wú)限列表
2. ListView.separated
ListView.separated可以在生成的列表項(xiàng)之間添加一個(gè)分割組件,它比ListView.builder多了一個(gè)separatorBuilder參數(shù)祖能,該參數(shù)是一個(gè)分割組件生成器
3.固定高度列表
前面說(shuō)過(guò)歉秫,給列表指定 itemExtent 或 prototypeItem 會(huì)有更高的性能,所以當(dāng)我們知道列表項(xiàng)的高度都相同時(shí)养铸,強(qiáng)烈建議指定 itemExtent 或 prototypeItem 雁芙。下面看一個(gè)示例
class FixedExtentList extends StatelessWidget {
const FixedExtentList({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListView.builder(
prototypeItem: ListTile(title: Text("1")),
//itemExtent: 56,
itemBuilder: (context, index) {
//LayoutLogPrint是一個(gè)自定義組件,在布局時(shí)可以打印當(dāng)前上下文中父組件給子組件的約束信息
return LayoutLogPrint(
tag: index,
child: ListTile(title: Text("$index")),
);
},
);
}
}
因?yàn)榱斜眄?xiàng)都是一個(gè) ListTile钞螟,高度相同兔甘,但是我們不知道 ListTile 的高度是多少,所以指定了prototypeItem 鳞滨,運(yùn)行后洞焙,控制臺(tái)打印:
可見(jiàn) ListTile 的高度是 56 ,所以我們指定 itemExtent 為 56也是可以的澡匪。但是筆者還是建議優(yōu)先指定原型熔任,這樣的話在列表項(xiàng)布局修改后,仍然可以正常工作(前提是每個(gè)列表項(xiàng)的高度相同)唁情。
如果本例中不指定 itemExtent 或 prototypeItem 疑苔,我們看看控制臺(tái)日志信息:
可以發(fā)現(xiàn),列表不知道列表項(xiàng)的具體高度甸鸟,高度約束變?yōu)?0.0 到 Infinity惦费,這樣就大大的降低了性能。
4.ListView原理
ListView 內(nèi)部組合了 Scrollable抢韭、Viewport 和 Sliver薪贫,需要注意:
1.ListView 中的列表項(xiàng)組件都是 RenderBox,并不是 Sliver篮绰, 這個(gè)一定要注意后雷。
2.一個(gè) ListView 中只有一個(gè)Sliver,對(duì)列表項(xiàng)進(jìn)行按需加載的邏輯是 Sliver 中實(shí)現(xiàn)的吠各。
3.ListView 的 Sliver 默認(rèn)是 SliverList臀突,如果指定了 itemExtent ,則會(huì)使用 SliverFixedExtentList贾漏;如果 prototypeItem 屬性不為空候学,則會(huì)使用 SliverPrototypeExtentList,無(wú)論是是哪個(gè)纵散,都實(shí)現(xiàn)了子組件的按需加載模型
5.無(wú)限加載列表(上拉加載更多)
第一步: 頂一個(gè)一個(gè)滾動(dòng)控制器
// 定義一個(gè)的滾動(dòng)控制器梳码,
// ScrollController間接繼承自Listenable,我們可以根據(jù)ScrollController來(lái)監(jiān)聽(tīng)滾動(dòng)事件
// 可以用ScrollController來(lái)控制可滾動(dòng)組件的滾動(dòng)位置
ScrollController scrollController = ScrollController();
第二步: 使用ListView.builder并添加控制器
ListView.builder(
controller: scrollController,//注意這里一定要加上滾動(dòng)的控制器,否則是無(wú)法監(jiān)聽(tīng)滾動(dòng)元素的
itemBuilder: (context, index) {
if(newList?.length == index){
return LoadingWidget(loadText:loadingText,loadStatus: loadStatus);//自定義加載更多文案伍掀,組件
}else{
return Container(
padding: const EdgeInsets.all(10.0),
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(
width: 0.5,
color: Color(0xffaaaaaa)
)
)
),
child: buildNewListItem(index),//這個(gè)根據(jù)自身需求定義組件
);
}
},
itemCount: newList!.length + 1 //這個(gè)長(zhǎng)度一定要記得+1 需要包含那個(gè)上拉加載圖標(biāo)
)
第三步:監(jiān)聽(tīng)控制器
scrollController.addListener(() {
/**
調(diào)用 scrollController.position.pixels 可以獲取當(dāng)前滾動(dòng)的像素點(diǎn) ;
調(diào)用 scrollController.position.maxScrollExtent 可以獲取當(dāng)前最大可滾動(dòng)位置 ;
如果上述兩個(gè)值相等 , 那么說(shuō)明已經(jīng)滾動(dòng)到列表最底部了 , 此時(shí)可以執(zhí)行上拉加載更多
*/
dynamic maxScroll = scrollController.position.maxScrollExtent;
dynamic pixels = scrollController.position.pixels;
if(maxScroll == pixels){
//加載更多
//TODO
}
});
6.添加固定列表頭
@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"));
}),
),
]);
}