3.ListView

ListView是最常用的可滚动组件之一,它可以沿一个方向线性排布所有子组件,并且它也支持列表项懒加载(在需要时才会创建)

1.默认构造函数

我们看看ListView的默认构造函数定义:


ListView默认构造函数

注意:虽然这种方式将所有children一次性传递给 ListView,但子组件)仍然是在需要时才会加载(build(如有)、布局、绘制),也就是说通过默认构造函数构建的 ListView 也是基于 Sliver 的列表懒加载模型。可以看到,虽然使用默认构造函数创建的列表也是懒加载的,但我们还是需要提前将 Widget 创建好,等到真正需要加载的时候才会对 Widget 进行布局和绘制。所以我们使用最多还是ListView.builder和ListView.separated

2.ListView.builder

ListView.builder 适合列表项比较多或者列表项不确定的情况,下面看一下ListView.builder的核心参数列表:

ListView.builder

  • itemBuilder:它是列表项的构建器,类型为IndexedWidgetBuilder,返回值为一个widget。当列表滚动到具体的index位置时,会调用该构建器构建列表项。
  • itemCount:列表项的数量,如果为null,则为无限列表
ListView.builder实例

2. ListView.separated

ListView.separated可以在生成的列表项之间添加一个分割组件,它比ListView.builder多了一个separatorBuilder参数,该参数是一个分割组件生成器


ListView.separated

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 ,运行后,控制台打印:


指定itemExtent 或 prototypeItem高度的打印结果

可见 ListTile 的高度是 56 ,所以我们指定 itemExtent 为 56也是可以的。但是笔者还是建议优先指定原型,这样的话在列表项布局修改后,仍然可以正常工作(前提是每个列表项的高度相同)。

如果本例中不指定 itemExtent 或 prototypeItem ,我们看看控制台日志信息:


没有指定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"));
      }),
    ),
  ]);
}
固定列表头
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
禁止转载,如需转载请通过简信或评论联系作者。

推荐阅读更多精彩内容