Flutter 基础组件之 ListView

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zgcqflqinhao/article/details/85121054

跟 Android 中的 ListView 差不多,就是一个可滚动的列表,这种组件在开发中是很常用的。

1 构造方法

ListView({Key key, Axis scrollDirection: Axis.vertical, bool reverse: false, ScrollController controller, bool primary, ScrollPhysics physics, bool shrinkWrap: false, EdgeInsetsGeometry padding, double itemExtent, bool addAutomaticKeepAlives: true, bool addRepaintBoundaries: true, bool addSemanticIndexes: true, double cacheExtent, List<Widget> children: const [], int semanticChildCount })

根据显式的 Widget 集合来创建一个 ListView。

ListView.builder({Key key, Axis scrollDirection: Axis.vertical, bool reverse: false, ScrollController controller, bool primary, ScrollPhysics physics, bool shrinkWrap: false, EdgeInsetsGeometry padding, double itemExtent, @required IndexedWidgetBuilder itemBuilder, int itemCount, bool addAutomaticKeepAlives: true, bool addRepaintBoundaries: true, bool addSemanticIndexes: true, double cacheExtent, int semanticChildCount })

根据需要来自定义创建 ListView,该构造方法必传的一个参数为 itemBuilder,它是一个 IndexedWidgetBuilder ,它的构造方法中会返回 index,可以根据这个 index 来让 ListView 的子 Item 有不同的展示。

ListView.custom({Key key, Axis scrollDirection: Axis.vertical, bool reverse: false, ScrollController controller, bool primary, ScrollPhysics physics, bool shrinkWrap: false, EdgeInsetsGeometry padding, double itemExtent, @required SliverChildDelegate childrenDelegate, double cacheExtent, int semanticChildCount })

创建一个自定义子模型的 ListView。

ListView.separated({Key key, Axis scrollDirection: Axis.vertical, bool reverse: false, ScrollController controller, bool primary, ScrollPhysics physics, bool shrinkWrap: false, EdgeInsetsGeometry padding, @required IndexedWidgetBuilder itemBuilder, @required IndexedWidgetBuilder separatorBuilder, @required int itemCount, bool addAutomaticKeepAlives: true, bool addRepaintBoundaries: true, bool addSemanticIndexes: true, double cacheExtent })

创建一个带分隔的 ListView,这个分隔可以帮助我们实现分隔线的效果,它除了要传入一个 itemBuilder 之外,还要传入一个 separatorBuilder,这个就是分隔线。

2 常用属性

childrenDelegate:自定义子模型时用到。

itemExtent:Item 的范围,scrollDirection 为 Axis.vertical 时限制高度,scrollDirection 为 Axis.horizontal 限制宽度。

cacheExtent:预加载的区域。

controller:滑动监听,值为一个 ScrollController 对象,这个属性应该可以用来做下拉刷新和上垃加载,后面详细研究。

padding:整个 ListView 的内间距。

physics:设置 ListView 如何响应用户的滑动行为,值为一个 ScrollPhysics 对象,它的实现类常用的有:
    AlwaysScrollableScrollPhysics:总是可以滑动。
    NeverScrollableScrollPhysics:禁止滚动。
    BouncingScrollPhysics:内容超过一屏,上拉有回弹效果。
    ClampingScrollPhysics:包裹内容,不会有回弹,感觉跟 AlwaysScrollableScrollPhysics 差不多。

primary:是否是与 PrimaryScrollController 关联的主滚动视图,若为 true 则 controller 必须为空。  

reverse:Item 的顺序是否反转,若为 true 则反转。

scrollDirection:ListView 的方向,为 Axis.vertical 表示纵向,为 Axis.horizontal 表示横向。

shrinkWrap:不太明白。

itemCount:子 Item 数量,只有使用 new ListView.builder() 和 new ListView.separated() 构造方法的时候才能指定,其中 new ListView.separated() 是必须指定。

下面是一个设置了上述属性的 Demo:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      //是否显示 debug 标签
      debugShowCheckedModeBanner: false,
      title: "ListView",
      home: Scaffold(
        appBar: new AppBar(
          title: new Text("ListView"),
        ),
        body: new Container(
          child: new MyListView1(),
        ),
      ),
    );
  }
}

class MyListView1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    List<Widget> widgetList = new List();

    for (int i = 0; i < 100; i++) {
      widgetList.add(new MyText("Item " + i.toString()));
    }
    ScrollController scrollController = new ScrollController();
    scrollController.addListener(() {
      print("scrollController--->" + scrollController.offset.toString());
    });
    return new ListView(
      //Item 的范围,scrollDirection 为 Axis.vertical 时限制高度,scrollDirection 为 Axis.horizontal 限制宽度
      itemExtent: 30,
//      shrinkWrap: true,
      //预加载的区域
//      cacheExtent: 0,
      //滑动监听,值为一个 ScrollController 对象
      controller: scrollController,
//      //内边距
      padding: EdgeInsets.all(10),
      //设置 ListView 如何响应用户的滑动行为,值为一个 ScrollPhysics 对象,它的实现类常用的有:
      //AlwaysScrollableScrollPhysics:总是可以滑动
      //NeverScrollableScrollPhysics:禁止滚动
      //BouncingScrollPhysics:内容超过一屏,上拉有回弹效果
      //ClampingScrollPhysics:包裹内容,不会有回弹,感觉跟 AlwaysScrollableScrollPhysics 差不多
      physics: new BouncingScrollPhysics(),
      //是否是与 PrimaryScrollController 关联的主滚动视图,若为 true 则 controller 必须为空
//      primary: true,
      //Item 的顺序是否反转,若为 true 则反转
      reverse: true,
      //ListView 的方向,为 Axis.vertical 表示纵向,为 Axis.horizontal 表示横向
//      scrollDirection: Axis.vertical,
      children: widgetList,
    );
  }
}

运行效果如下:

上面是用第一种构造方法创建的 ListView,接下来使用 Builder 来创建一下 ListView,它与普通的构造方法不同的是,普通构造方法传入的是已经创建好的子组件集合,而 Builder 是先指定子组件的个数,然后在 itemBuilder 中可以知道子组件所属的 position,根据这个 position 可以创建不同的子组件:

class MyListView2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return new ListView.builder(
      itemCount: 100,
      itemBuilder: (BuildContext context, int index) {
        if (index.isOdd) {
          return new Container(
            padding: new EdgeInsets.all(15.0),
            child: new Text(
              "builder 奇数 Item " + index.toString(),
              style:
                  new TextStyle(fontSize: 20.0, color: new Color(0xFFFF0000)),
            ),
          );
        } else {
          return new Container(
            padding: new EdgeInsets.all(15.0),
            child: new Text(
              "builder 偶数 Item " + index.toString(),
              style:
                  new TextStyle(fontSize: 20.0, color: new Color(0xFF0000FF)),
            ),
          );
        }
      },
    );
  }
}

运行效果如下:

其实使用 Builder 来构造 ListView 的话,也可以实现分隔线的效果,只需要根据 position 一个返回子组件,下一个返回分隔线组件即可:

class MyListView2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return ListView.builder(
      itemCount: 100,
      itemBuilder: (BuildContext context, int index) {
        if (index.isOdd) {
          return Container(
            padding: EdgeInsets.all(15.0),
            child: Text(
              "builder 奇数 Item " + index.toString(),
              style:
                  new TextStyle(fontSize: 20.0, color: new Color(0xFFFF0000)),
            ),
          );
        } else {
          return Divider(color: Color(0xFF000000),);
        }
      },
    );
  }
}

这样写出来会是这种效果:

虽然实现了分隔线效果,但是想必从 Item 上的文字就能看出问题,分隔线是占了 Item 的位置的,所以如果要用这种方式来实现分隔线的话,子组件的长度会增大一倍,因为一半都给了分隔线,所以实现分隔线的话一般是使用 ListView.separated() 构造方法:

class MyListView3 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    List<Widget> widgetList = new List();

    for (int i = 0; i < 100; i++) {
      widgetList.add(new MyText("Item " + i.toString()));
    }
    return new ListView.separated(
      itemCount: 100,
      itemBuilder: (BuildContext context, int index) {
        print("itemBuilder--->" + index.toString());
        return new Container(
          padding: new EdgeInsets.all(15.0),
          child: new Text(
            "separated Item " + index.toString(),
            style: new TextStyle(fontSize: 20.0, color: new Color(0xFF000000)),
          ),
        );
      },
      separatorBuilder: (BuildContext context, int index) {
        print("separatorBuilder--->" + index.toString());
        return new Divider(
          color: new Color(0xFF888888),
        );
      },
    );
  }
}

运行效果如下:

而且从打印中也可以看到分隔线并没有占据子组件空间,它的 position 跟子组件的 position 是一样的:

还有一种构造方法 ListView.custom() 这个暂时还没有去研究。水平方向的 ListView 很简单,只需要将 scrollDirection 设置为 Axis.horizontal 就行,简单贴个效果:

3 总结

ListView 在开发中用得非常多,通常的场景是通过网络请求到一堆数据,然后通过 ListView 分页加载,这种下拉刷新和上拉加载等今后学到网络请求时再研究,应该有前辈都造好轮子了,到时候看看自己能不能实现。

猜你喜欢

转载自blog.csdn.net/zgcqflqinhao/article/details/85121054