ListView是最常用的可滚动组件之一,它可以沿一个方向线性排布所有子组件,并且它也支持基于Sliver的延迟构建模型。Flutter中的ListView与Android中的ListView具有同等功能。

Sliver的延迟构建模型请参见本系列文章《Flutter SingleChildScrollView 滚动控件》中的基本概念处。
本文示例效果图(全)

1、默认构造函数

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

ListView({
  ...  
  //可滚动widget公共参数
  Axis scrollDirection = Axis.vertical,
  bool reverse = false,
  ScrollController controller,
  bool primary,
  ScrollPhysics physics,
  EdgeInsetsGeometry padding,

  //ListView各个构造函数的共同参数  
  double itemExtent,
  bool shrinkWrap = false,
  bool addAutomaticKeepAlives = true,
  bool addRepaintBoundaries = true,
  double cacheExtent,

  //子widget列表
  List<Widget> children = const <Widget>[],
})

上面参数分为两组:第一组是可滚动组件的公共参数,本章第一节中已经介绍过,不再赘述;第二组是ListView各个构造函数(ListView有多个构造函数)的共同参数,我们重点来看看这些参数。

ListView通用属性值含义
itemExtent每个子控件的高度。指定itemExtent的值比不指定(自适应高度)会更高效。
shrinkWrap是否根据子组件的总高度来设置ListView的高度,默认为false 。当ListView在一个无边界(滚动方向上)的容器中时,shrinkWrap必须为true。
addAutomaticKeepAlives是否将列表项(子控件)包裹在AutomaticKeepAlive组件中,包含之后列表项滑出视口时它也不会被GC。如果列表项自己维护其KeepAlive状态,那么此参数必须置为false
addRepaintBoundaries是否将列表项(子控件)包裹在RepaintBoundary组件中,包含之后可以避免列表项重绘。如果列表项自己维护其KeepAlive状态,那么此参数必须置为false
cacheExtent预加载的区域。

不支持Sliver懒加载模型。

默认构造函数有一个children参数,它接受一个Widget列表。这种方式适合只有少量的子组件的情况,因为这种方式需要将所有children都提前创建好(这需要做大量工作),而不是等到子widget真正显示的时候再创建,也就是说通过默认构造函数构建的ListView没有应用基于Sliver的懒加载模型。实际上通过此方式创建的ListView和使用SingleChildScrollView+Column的方式没有本质的区别。

下面是一个例子:

    ListView(
      padding: EdgeInsets.all(20),
      children: <Widget>[
        new Text("one"),
        new Text("two"),
        new Text("three"),
        new Text("four"),
        new Text("five"),
        new Text("six"),
      ],
    ),

2、ListView.builder

支持Sliver懒加载模型。

ListView.builder适合列表项比较多(或者无限)的情况,因为只有当子组件真正显示的时候才会被创建,也就说通过该构造函数创建的ListView是支持基于Sliver的懒加载模型的。

下面看一下ListView.builder的核心参数列表:

ListView.builder({
  // ListView公共参数已省略  
  ...
  @required IndexedWidgetBuilder itemBuilder,
  int itemCount,
  ...
})
ListView.builder属性值含义
itemBuilder它是列表项的构建器,类型为IndexedWidgetBuilder,返回值为一个widget。当列表滚动到具体的index位置时,会调用该构建器构建列表项。
itemCount列表项的数量,如果为null,则为无限列表。

可滚动组件的构造函数如果需要一个列表项Builder,那么通过该构造函数构建的可滚动组件通常就是支持基于Sliver的懒加载模型的,反之则不支持,其他可滚动组件亦是如此。

下面看一个例子:

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

3、ListView.separated

支持Sliver懒加载模型。

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

下面我们看一个例子:奇数行添加一条蓝色下划线,偶数行添加一条绿色下划线。

class ListView3 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //下划线widget预定义以供复用。  
    Widget divider1=Divider(color: Colors.blue,);
    Widget divider2=Divider(color: Colors.green);
    return ListView.separated(
        itemCount: 100,
        //列表项构造器
        itemBuilder: (BuildContext context, int index) {
          return ListTile(title: Text("$index"));
        },
        //分割器构造器
        separatorBuilder: (BuildContext context, int index) {
          return index%2==0?divider1:divider2;
        },
    );
  }
}

4、ListView.custom

支持Sliver懒加载模型。

ListView.custom可以自定义ListView的Item,对于复杂ListView(比如不同index对应不同布局时)需要用到它。ListView.builderListView.separatedListView.custom的简化版。因为 ListView 内部是靠这个 childrenDelegate 属性动态初始化子元素的。

构造方法:

ListView.builder({
  // ListView公共参数已省略  
  ...
    @required this.childrenDelegate,
  ...
})
  • childrenDelegate:

为ListView提供子委托,类型为SliverChildDelegate。可传入SliverChildDelegate的子类SliverChildBuilderDelegate实例。该实例可以获取到ListView的index,并根据index返回对应的Widget。适用于高度自定义ListView的情况下使用。

举个例子:

  String datas =
      "ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ";

            Expanded(
                child: ListView.custom(
              itemExtent: 40.0,
              childrenDelegate: SliverChildBuilderDelegate(
                (BuildContext context, int i) {
                  Widget result;
                  if (i < 5) {
                    result = new Text(
                      "cus${datas.split("")[i]}",
                      style: new TextStyle(fontSize: 18.0, color: Colors.green),
                    );
                  } else if (i < 10) {
                    result = new Text(
                      "cus${datas.split("")[i]}",
                      style: new TextStyle(fontSize: 18.0, color: Colors.red),
                    );
                  } else {
                    result = new Text(
                      "cus${datas.split("")[i]}",
                      style: new TextStyle(fontSize: 18.0, color: Colors.blue),
                    );
                  }

                  return result;
                },
                childCount: datas.length,
              ),
              cacheExtent: 0.0,
            )),

效果图见文章开篇处。