ListView是最常用的可滚动组件之一,它可以沿一个方向线性排布所有子组件,并且它也支持列表项懒加载(在需要时才会创建)
1.默认构造函数
我们看看ListView的默认构造函数定义:
注意:虽然这种方式将所有children一次性传递给 ListView,但子组件)仍然是在需要时才会加载(build(如有)、布局、绘制),也就是说通过默认构造函数构建的 ListView 也是基于 Sliver 的列表懒加载模型。可以看到,虽然使用默认构造函数创建的列表也是懒加载的,但我们还是需要提前将 Widget 创建好,等到真正需要加载的时候才会对 Widget 进行布局和绘制。所以我们使用最多还是ListView.builder和ListView.separated
2.ListView.builder
ListView.builder 适合列表项比较多或者列表项不确定的情况,下面看一下ListView.builder的核心参数列表:
- itemBuilder:它是列表项的构建器,类型为IndexedWidgetBuilder,返回值为一个widget。当列表滚动到具体的index位置时,会调用该构建器构建列表项。
- itemCount:列表项的数量,如果为null,则为无限列表
2. ListView.separated
ListView.separated可以在生成的列表项之间添加一个分割组件,它比ListView.builder多了一个separatorBuilder参数,该参数是一个分割组件生成器
3.固定高度列表
前面说过,给列表指定 itemExtent 或 prototypeItem 会有更高的性能,所以当我们知道列表项的高度都相同时,强烈建议指定 itemExtent 或 prototypeItem 。下面看一个示例
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是一个自定义组件,在布局时可以打印当前上下文中父组件给子组件的约束信息
return LayoutLogPrint(
tag: index,
child: ListTile(title: Text("$index")),
);
},
);
}
}
因为列表项都是一个 ListTile,高度相同,但是我们不知道 ListTile 的高度是多少,所以指定了prototypeItem ,运行后,控制台打印:
可见 ListTile 的高度是 56 ,所以我们指定 itemExtent 为 56也是可以的。但是笔者还是建议优先指定原型,这样的话在列表项布局修改后,仍然可以正常工作(前提是每个列表项的高度相同)。
如果本例中不指定 itemExtent 或 prototypeItem ,我们看看控制台日志信息:
可以发现,列表不知道列表项的具体高度,高度约束变为 0.0 到 Infinity,这样就大大的降低了性能。
4.ListView原理
ListView 内部组合了 Scrollable、Viewport 和 Sliver,需要注意:
1.ListView 中的列表项组件都是 RenderBox,并不是 Sliver, 这个一定要注意。
2.一个 ListView 中只有一个Sliver,对列表项进行按需加载的逻辑是 Sliver 中实现的。
3.ListView 的 Sliver 默认是 SliverList,如果指定了 itemExtent ,则会使用 SliverFixedExtentList;如果 prototypeItem 属性不为空,则会使用 SliverPrototypeExtentList,无论是是哪个,都实现了子组件的按需加载模型
5.无限加载列表(上拉加载更多)
第一步: 顶一个一个滚动控制器
// 定义一个的滚动控制器,
// ScrollController间接继承自Listenable,我们可以根据ScrollController来监听滚动事件
// 可以用ScrollController来控制可滚动组件的滚动位置
ScrollController scrollController = ScrollController();
第二步: 使用ListView.builder并添加控制器
ListView.builder(
controller: scrollController,//注意这里一定要加上滚动的控制器,否则是无法监听滚动元素的
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),//这个根据自身需求定义组件
);
}
},
itemCount: newList!.length + 1 //这个长度一定要记得+1 需要包含那个上拉加载图标
)
第三步:监听控制器
scrollController.addListener(() {
/**
调用 scrollController.position.pixels 可以获取当前滚动的像素点 ;
调用 scrollController.position.maxScrollExtent 可以获取当前最大可滚动位置 ;
如果上述两个值相等 , 那么说明已经滚动到列表最底部了 , 此时可以执行上拉加载更多
*/
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"));
}),
),
]);
}