ListView是最常用的可滾動組件之一,它可以沿一個(gè)方向線性排布所有子組件,并且它也支持列表項(xiàng)懶加載(在需要時(shí)才會創(chuàng)建)
1.默認(rèn)構(gòu)造函數(shù)
我們看看ListView的默認(rèn)構(gòu)造函數(shù)定義:

注意:雖然這種方式將所有children一次性傳遞給 ListView,但子組件)仍然是在需要時(shí)才會加載(build(如有)、布局、繪制),也就是說通過默認(rèn)構(gòu)造函數(shù)構(gòu)建的 ListView 也是基于 Sliver 的列表懶加載模型??梢钥吹剑m然使用默認(rèn)構(gòu)造函數(shù)創(chuàng)建的列表也是懶加載的,但我們還是需要提前將 Widget 創(chuàng)建好,等到真正需要加載的時(shí)候才會對 Widget 進(jìn)行布局和繪制。所以我們使用最多還是ListView.builder和ListView.separated
2.ListView.builder
ListView.builder 適合列表項(xiàng)比較多或者列表項(xiàng)不確定的情況,下面看一下ListView.builder的核心參數(shù)列表:

- itemBuilder:它是列表項(xiàng)的構(gòu)建器,類型為IndexedWidgetBuilder,返回值為一個(gè)widget。當(dāng)列表滾動到具體的index位置時(shí),會調(diào)用該構(gòu)建器構(gòu)建列表項(xiàng)。
- itemCount:列表項(xiàng)的數(shù)量,如果為null,則為無限列表

2. ListView.separated
ListView.separated可以在生成的列表項(xiàng)之間添加一個(gè)分割組件,它比ListView.builder多了一個(gè)separatorBuilder參數(shù),該參數(shù)是一個(gè)分割組件生成器

3.固定高度列表
前面說過,給列表指定 itemExtent 或 prototypeItem 會有更高的性能,所以當(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)行后,控制臺打?。?/p>

可見 ListTile 的高度是 56 ,所以我們指定 itemExtent 為 56也是可以的。但是筆者還是建議優(yōu)先指定原型,這樣的話在列表項(xiàng)布局修改后,仍然可以正常工作(前提是每個(gè)列表項(xiàng)的高度相同)。
如果本例中不指定 itemExtent 或 prototypeItem ,我們看看控制臺日志信息:

可以發(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,對列表項(xiàng)進(jìn)行按需加載的邏輯是 Sliver 中實(shí)現(xiàn)的。
3.ListView 的 Sliver 默認(rèn)是 SliverList,如果指定了 itemExtent ,則會使用 SliverFixedExtentList;如果 prototypeItem 屬性不為空,則會使用 SliverPrototypeExtentList,無論是是哪個(gè),都實(shí)現(xiàn)了子組件的按需加載模型
5.無限加載列表(上拉加載更多)
第一步: 頂一個(gè)一個(gè)滾動控制器
// 定義一個(gè)的滾動控制器,
// ScrollController間接繼承自Listenable,我們可以根據(jù)ScrollController來監(jiān)聽滾動事件
// 可以用ScrollController來控制可滾動組件的滾動位置
ScrollController scrollController = ScrollController();
第二步: 使用ListView.builder并添加控制器
ListView.builder(
controller: scrollController,//注意這里一定要加上滾動的控制器,否則是無法監(jiān)聽滾動元素的
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è)長度一定要記得+1 需要包含那個(gè)上拉加載圖標(biāo)
)
第三步:監(jiān)聽控制器
scrollController.addListener(() {
/**
調(diào)用 scrollController.position.pixels 可以獲取當(dāng)前滾動的像素點(diǎn) ;
調(diào)用 scrollController.position.maxScrollExtent 可以獲取當(dāng)前最大可滾動位置 ;
如果上述兩個(gè)值相等 , 那么說明已經(jī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"));
}),
),
]);
}
