Flutter ListView 用法详解

14,911 阅读4分钟

ListView的4种构造方式

默认构造函数(传入 List children)

ListView(
          children: <Widget>[
            ListTile(title: Text("普通ListView")),
            ListTile(
                title: Text("ListView.build"),
                onTap: () {
                  Navigator.pushNamed(context, '/listview_build');
                }),
          ],
        )

适用场景:已知有限个Item的情况下

builder

ListView.builder(
            itemBuilder: (context, index) => Text("Item $index"),
            itemCount: 100)

适用场景:长列表时采用builder模式,能提高性能。不是把所有子控件都构造出来,而是在控件viewport加上头尾的cacheExtent这个范围内的子Item才会被构造。在构造时传递一个builder,按需加载是一个惯用模式,能提高加载性能。

构造多种样式的Item

abstract class ListItem {}

class HeadingItem implements ListItem {
  final String heading;

  HeadingItem(this.heading);
}

class MessageItem implements ListItem {
  final String sender;
  final String body;

  MessageItem(this.sender, this.body);
}

ListView.builder(
            itemBuilder: (context, index) {
              final item = items[index];

              if (item is HeadingItem) {
                return ListTile(
                  title: Text(
                    item.heading,
                    style: Theme.of(context).textTheme.headline,
                  ),
                );
              } else if (item is MessageItem) {
                return ListTile(
                  title: Text(item.sender),
                  subtitle: Text(item.body),
                );
              }
            },
            itemCount: items.length))

separated

ListView.separated(
            itemBuilder: (context, index) {
              return Text("Item $index");
            },
            separatorBuilder: (context, index) {
              return Container(
                color: Colors.grey,
                height: 3,
              );
            },
            itemCount: 100)

适用场景:列表中需要分割线时,可以自定义复杂的分割线

custom

需要自定义SliverChildDelegate,各方法含义如下:

ListView.custom(childrenDelegate: CustomSliverChildDelegate())

class CustomSliverChildDelegate extends SliverChildDelegate {
  /// 根据index构造child
  @override
  Widget build(BuildContext context, int index) {
    // KeepAlive将把所有子控件加入到cache,已输入的TextField文字不会因滚动消失
    // 仅用于演示
    return KeepAlive(
        keepAlive: true,
        child: TextField(decoration: InputDecoration(hintText: '请输入')));
  }

  /// 决定提供新的childDelegate时是否需要重新build。在调用此方法前会做类型检查,不同类型时才会调用此方法,所以一般返回true。
  @override
  bool shouldRebuild(SliverChildDelegate oldDelegate) {
    return true;
  }

  /// 提高children的count,当无法精确知道时返回null。
  /// 当 build 返回 null时,它也将需要返回一个非null值
  @override
  int get estimatedChildCount => 100;

  /// 预计最大可滑动高度,如果设置的过小会导致部分child不可见,设置报错
  @override
  double estimateMaxScrollOffset(int firstIndex, int lastIndex,
      double leadingScrollOffset, double trailingScrollOffset) {
    return 2500;
  }

  /// 完成layout后的回调,可以通过该方法获已完成布局的视图树包括哪些子控件
  @override
  void didFinishLayout(int firstIndex, int lastIndex) {
    print('didFinishLayout firstIndex=$firstIndex firstIndex=$lastIndex');
  }
}

适用场景:上面几种模式基本可以满足业务需求,如果你还想做一些其它设置(如列表的最大滚动范围)或获取滑动时每次布局的子Item范围,可以尝试custom模式

ListView属性含义

ListView的属性

  • itemExtent: 指定Item在滑动方向上的高度,用来提高滑动性能

继承自父控件 BoxScrollView 的属性

  • padding: 用来设置BoxScrollView中子控件与父控件的间距

继承自 ScrollView 的属性

  • ScrollDirection: 滚动方向
  • reverse:决定滚动方向是否与阅读方向一致
  • scrollController:控制滚动的位置
  • primary:当内容不足以滚动时,是否支持滚动;对于iOS系统还有一个效果:当用户点击状态栏时是否滑动到顶部。
  • ScrollPhysics:控制用户滚动视图的交互
    • AlwaysScrollableScrollPhysics:列表总是可滚动的。在iOS上会有回弹效果,在android上不会回弹。那么问题来了,如果primary设置为false(内容不足时不滚动),且 physics设置为AlwaysScrollableScrollPhysics,列表是否可以滑动?答案是可以,感兴趣的可以试一下
    • PageScrollPhysics:一般是给PageView控件用的滑动效果。如果listview设置的话在滑动到末尾时会有个比较大的弹起和回弹
    • ClampingScrollPhysics:滚动时没有回弹效果,同android系统的listview效果
    • NeverScrollableScrollPhysics:就算内容超过列表范围也不会滑动
    • BouncingScrollPhysics:不论什么平台都会有回弹效果
    • FixedExtentScrollPhysics:不适用于ListView,原因:需要指定scroller为FixedExtentScrollController,这个scroller只能用于ListWheelScrollViews
  • shrinkWrap: scroll view在滑动方向上的高度是否由内容高度决定,false:则高度为滑动方向上的最大允许高度;如果在滑动方向上没有设置约束,则这个字段必须设置为true,否则会报错。
  • cacheExtent:可见区域的前后会有一定高度的空间去缓存子控件,当滑动时就可以迅速呈现
  • semanticChildCount:有含义的子控件的数量,如ListView会用children的长度,ListView.separated会用children长度的一半

用于构造SliverChildListDelegate的属性

  • addAutomaticKeepAlives:是否将子控件包裹在AutomaticKeepAlive控件内
  • addRepaintBoundaries:true:是否将子控件包裹在 RepaintBoundary 控件内。用于避免列表滚动时的重绘,如果子控件重绘开销很小时,比如子控件就是个色块或简短的文字,把这个字段设置为false性能会更好
  • addSemanticIndexes:是否把子控件包装在IndexedSemantics里,用来提供无障碍语义

最后

ListView最为app最常见的控件之一,掌握它的用法非常重要。本文主要给Flutter萌新们介绍ListView的几种构造方式和参数含义,视图控件的学习应多动手写写demo,真实体会各个参数的效果。

最后附上demo以供参考。

@akindone, 本文版权属于再惠研发团队,欢迎转载,转载请保留出处。