美文网首页
Flutter学习之九 ListView

Flutter学习之九 ListView

作者: MQ_Twist | 来源:发表于2022-11-18 11:03 被阅读0次

    极端很容易,平衡才是最难的。

    👈🏻 Flutter学习之八 Container

    前言

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

    默认构造函数

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

    ListView({
      ...  
      //可滚动widget公共参数
      Axis scrollDirection = Axis.vertical,
      bool reverse = false,
      ScrollController? controller,
      bool? primary,
      ScrollPhysics? physics,
      EdgeInsetsGeometry? padding,
      
      //ListView各个构造函数的共同参数  
      double? itemExtent,
      Widget? prototypeItem, //列表项原型,后面解释
      bool shrinkWrap = false,
      bool addAutomaticKeepAlives = true,
      bool addRepaintBoundaries = true,
      double? cacheExtent, // 预渲染区域长度
        
      //子widget列表
      List<Widget> children = const <Widget>[],
    })
    

    上面的可滑动公共参数就不再赘述了,下面是ListView各个构造函数(ListView有多个构造函数)的共同参数,我们重点来看看这些参数:

    • itemExtent:该参数如果不为null,则会强制children的“长度”为itemExtent的值;这里的“长度”是指滚动方向上子组件的长度,也就是说如果滚动方向是垂直方向,则itemExtent代表子组件的高度;如果滚动方向为水平方向,则itemExtent就代表子组件的宽度。在ListView中,指定itemExtent比让子组件自己决定自身长度会有更好的性能,这是因为指定itemExtent后,滚动系统可以提前知道列表的长度,而无需每次构建子组件时都去再计算一下,尤其是在滚动位置频繁变化时(滚动系统需要频繁去计算列表高度),和原生很像。

    • prototypeItem:如果我们知道列表中的所有列表项长度都相同但不知道具体是多少,这时我们可以指定一个列表项,该列表项被称为prototypeItem(列表项原型)。指定 prototypeItem后,可滚动组件会在 layout 时计算一次它延主轴方向的长度,这样也就预先知道了所有列表项的延主轴方向的长度,所以和指定 itemExtent一样,指定prototypeItem会有更好的性能。注意,itemExtentprototypeItem互斥,不能同时指定它们。

    • shrinkWrap:该属性表示是否根据子组件的总长度来设置ListView的长度,默认值为false 。默认情况下,ListView会在滚动方向尽可能多的占用空间。当ListView在一个无边界(滚动方向上)的容器中时,shrinkWrap必须为true

    • addAutomaticKeepAlives:该属性比较复杂,以后再说。

    • addRepaintBoundaries:该属性表示是否将列表项(子组件)包裹在RepaintBoundary组件中。RepaintBoundary 读者可以先简单理解为它是一个”绘制边界“,将列表项包裹在RepaintBoundary中可以避免列表项不必要的重绘,但是当列表项重绘的开销非常小(如一个颜色块,或者一个较短的文本)时,不添加RepaintBoundary反而会更高效(具体原因会在本书后面 Flutter 绘制原理相关章节中介绍)。如果列表项自身来维护是否需要添加绘制边界组件,则此参数应该指定为false

    默认构造函数有一个children参数,它接受一个Widget列表(List<Widget>)。这种方式适合只有少量的子组件数量已知且比较少的情况,反之则应该使用ListView.builder 按需动态构建列表项。

    注意:虽然这种方式将所有children一次性传递给 ListView,但子组件仍然是在需要时才会加载(build(如有)、布局、绘制),也就是说通过默认构造函数构建的ListView 也是基于 Sliver的列表懒加载模型。

    举个栗子:

    _listView() {
      return ListView(
        shrinkWrap: true,
        padding: const EdgeInsets.all(20.0),
        children: const <Widget>[
          Text('张三'),
          Text('李四'),
          Text('王五'),
          Text('赵六'),
        ],
      );
    }
    

    效果如下:


    ListView.png

    可以看到,虽然使用默认构造函数创建的列表也是懒加载的,但我们还是需要提前将Widget创建好,等到真正需要加载的时候才会对 Widget 进行布局和绘制。

    ListView.builder

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

    ListView.builder({
      // ListView公共参数已省略  
      ...
      required IndexedWidgetBuilder itemBuilder,
      int itemCount,
      ...
    })
    

    +itemBuilder:它是列表项的构建器,类型为IndexedWidgetBuilder,返回值为一个widget。当列表滚动到具体的index位置时,会调用该构建器构建列表项。

    • itemCount:列表项的数量,如果为null,则为无限列表

    举个栗子:

    _listViewBuild() {
      return ListView.builder(
          itemCount: 100,
          itemExtent: 50.0, //强制高度为50.0
          itemBuilder: (BuildContext context, int index) {
            return ListTile(title: Text("_listViewBuild $index"));
          });
    }
    

    效果如下:


    ListViewBuilder.gif

    ListView.separated

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

    举个栗子:

    _listViewSeparated() {
      // widget预定义以供复用。
      Widget blue = Container(color: Colors.blue, height: 10);
      Widget red = Container(color: Colors.red, height: 10);
      return ListView.separated(
        itemCount: 100,
        //列表项构造器
        itemBuilder: (BuildContext context, int index) {
          return ListTile(title: Text("$index"));
        },
        //分割器构造器
        separatorBuilder: (BuildContext context, int index) {
          return index % 2 == 0 ? blue : red;
        },
      );
    }
    

    效果如下:


    _listViewSeparated.gif

    当然还有一个场景就是,每个item之间有一定的间距,也可以用这个实现。

    总结

    • 我在开发中一般使用ListView.builder,里面的itemBuilder对应的原生的cell,然后自定义cell就行了,用起来还是比较舒服的。
    • 如果item之间有分割线或是间距的,ListView.separated当首选。

    后记

    常见的列表页面ListView一般都能搞定,其对应iOS 原生的UITableView。那原生的UICollectionView呢,在Flutter中要使用GridView,这个以后再写。

    相关文章

      网友评论

          本文标题:Flutter学习之九 ListView

          本文链接:https://www.haomeiwen.com/subject/scngurtx.html