Flutter基建 - 离不开的列表组件

3,283 阅读7分钟

本篇基于Flutter 3.13.9,Dart 3.1.5版本

Flutter 3.13.9 • channel stable • github.com/flutter/flu…

Framework • revision d211f42860 (2 weeks ago) • 2023-10-25 13:42:25 -0700

Engine • revision 0545f8705d

Tools • Dart 3.1.5 • DevTools 2.25.0

本篇为Flutter基建的第六篇文章了,主要介绍Flutter中列表,列表在我们日常开发中是接触的比较多的组件之一了,本篇文章会涵盖列表的多种创建方式及其重要的参数介绍,并且也会有GridView内容介绍,下面一起进入文章了解下吧~

image.png

Flutter基建 - Dart基础类型

Flutter基建 - Dart方法和类

Flutter基建 - 文本组件

Flutter基建 - 按钮全解析

Flutter基建 - 布局组件全面解析

Flutter基建 - 离不开的列表组件

ListView组件

Flutter中创建列表的形式有多种,可以直接使用ListView,也可以使用ListView的扩展方法builder()、separated()和custom()方式去创建一个列表组件,下面我们逐一的上手体验下这几种方式的区别。

ListView直接创建

ListView(
  padding: const EdgeInsets.all(10),
  children: [
    for (int i = 0; i < 20; i++) buildItem(i),
  ],
),

Widget buildItem(int index) {
  return ListTile(
    leading: const Icon(Icons.favorite),
    title: Text("List Item $index"),
    subtitle: Text("List Item SubTitle $index"),
    onTap: () {
      print('onTap');
    },
    onLongPress: () {
      print('onLongPress');
    },
  );
}

一个最简单的列表就呈现出来了,这里item组件我们采用的是Flutter自带的ListTile组件,它帮我们封装了一套固定格式的item,可以设置主标题、副标题和icon等属性,也可以直接使用onTap和onLongPress实现单击和长按事件的处理逻辑,如果UI上符合ListTile的样式,我们可以直接采用这种,当然也可以自定义自己的item样式。

相比于Android原生的ListView和RecyclerView来说,Flutter创建列表的方式显示更为简单了,省去了Adapter的写法,可以帮忙我们在开发中节省一些时间。

学习了ListView直接创建的方式之后,下面我们再来看下builder()扩展方法的方式是如何使用的。

ListView.builder()创建

ListView.builder(
  padding: const EdgeInsets.all(10),
  itemCount: 20,
  itemBuilder: (context, index) {
    return buildItem(index);
  },
),

builder方式没有children参数,多了itemCount和itemBuilder两个参数,itemCount用于管理列表的子组件长度,itemBuilder则是用于创建子组件,可以根据index来创建对应下标的子组件。

这里实现的界面是一样的,就不再截图展示了哈~

ListView.separated()创建

ListView.separated(
    padding: const EdgeInsets.all(10),
    itemBuilder: (context, index) {
      return buildItem(index);
    },
    separatorBuilder: (context, index) 
      return const Divider(
        height: 1,
        color: Colors.grey,
      );
    },
    itemCount: 20),

separated方式和builder方式有点类似,它多了一个separatorBuilder参数,此参数用于创建item之间的分割线,并且在最后一个item之后不会创建此分割线,实现的效果如下图所示:

ListView.custom()创建

ListView.custom(childrenDelegate: MyListSliverDelegate()),

class MyListSliverDelegate extends SliverChildDelegate {
  @override
  Widget? build(BuildContext context, int index) {
    return buildItem(index);
  }

  @override
  bool shouldRebuild(covariant SliverChildDelegate oldDelegate) {
    return this != oldDelegate;
  }

  @override
  void didFinishLayout(int firstIndex, int lastIndex) {
    super.didFinishLayout(firstIndex, lastIndex);
    print('doFinishLayout firstIndex: $firstIndex, lastIndex: $lastIndex');
  }
}

custom方式要稍微比builder和separated方式复杂一点,它接受一个必传参数为childrenDelegate,此参数为SliverChildDelegate类型,需要我们自定义一个,MyListSliverDelegate就是继承自SliverChildDelegate,其内部build()和shouldRebuild()方法必须覆写,build()方法用于创建列表的子组件,shouldRebuild()方法用于判断子组件是否需要重建。

注意看下这里我多覆写了一个方法为didFinishLayout(),它可以回调当前已经完成布局的起始子组件和结束子组件,我们可以通过此方法判断出即将渲染的子组件是哪些,是一个非常有用的方法。

到这里为止四种创建列表的方式都介绍完了,一般前三种基本上就满足日常开发使用了,有些定制化的可以考虑采用custom的方式,下面我们再来看看ListView中几个比较重要的参数。

参数介绍

这里我们以ListView.builder()方式为切入点介绍参数,以下介绍的参数在几个创建方式中都有定义,没有区别点。

ListView({
  super.key,
  super.scrollDirection,
  super.reverse,
  super.controller,
  super.primary,
  super.physics,
  super.shrinkWrap,
  super.padding,
  this.itemExtent,
  this.prototypeItem,
  bool addAutomaticKeepAlives = true,
  bool addRepaintBoundaries = true,
  bool addSemanticIndexes = true,
  super.cacheExtent,
  List<Widget> children = const <Widget>[],
  int? semanticChildCount,
  super.dragStartBehavior,
  super.keyboardDismissBehavior,
  super.restorationId,
  super.clipBehavior,
})
  • scrollDirection:为滑动方向,可以设置为Axis.vertical和Axis.horizontal;
  • reverse:可设置滑动方式反转,默认垂直方向是从上往下滑,设置为true之后滑动方向变为从下往上滑;
  • controller:为ScrollController对象,可以理解成列表的控制器,我们可以使用它来直接滑动到某个指定位置的子组件,也可以通过addListener()方法监听滑动的偏移量。注意别忘记调用controller.dispose()方法;
  • padding:用于设置列表内边距值,这个无须多加介绍;
  • itemExtend:这个参数是为了提升滑动的性能,此参数设置的值就是子组件的高度或者宽度,滑动方向垂直的话就是子组件的高度,滑动方向水平的话就是子组件的宽度,这个参数大家可以动手设置下,可以非常直观的感受出来;
  • prototypeItem:此参数是Widget类型,它表示的意思是子组件的原型组件,系统可以通过原型组件的高度得知所有的子组件高度都是一致并且具体值也是知晓,这样在滑动过程中,系统就不需要每次都去计算子组件的高度,可以很好的提高滑动的性能。注意这个参数和itemExtend为互斥关系,择其一即可。
  • cacheExtend:此参数就比较容易理解了,它表示的是预加载的一个高度值,系统会根据这个值去预加载相应高度的子组件。

这里就是ListView比较重要的几个参数,可能仅看解释不能很好的理解,需要大家动手去敲一敲代码,然后赋上不同的值就可以很好的感受每个参数的效果🍗🍗🍗。

GridView组件

GridView为网格布局,一般用于显示网格形式的图片,其用法和ListView有很多相似之处,下面我们逐一看下几种创建网格布局的方式。

GridView直接创建

GridView(
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 3,
    mainAxisSpacing: 10,
    crossAxisSpacing: 10,
  ),
  children: [for (int i = 0; i < 20; i++) buildGridItem()],
)

Widget buildGridItem() {
  return Image.asset("images/img.png");
}

原图:

GridView在用法上比ListView多了个gridDelegate参数,它主要用于管理主轴和纵轴方向子组件之间的边距和一行显示的个数。

gridDelegate是SliverGridDelegate类型,Flutter为我们提供了SliverGridDelegateWithFixedCrossAxisCount和SliverGridDelegateWithMaxCrossAxisExtent两种形式的Delegate,第一种就是上图一行固定个数的样式,而第二种则是一行内每个子组件最大长度的样式,下面我们来看下这种效果:

GridView(
  gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
    maxCrossAxisExtent: 100,
    mainAxisSpacing: 10,
    crossAxisSpacing: 10,
  ),
  children: [for (int i = 0; i < 20; i++) buildGridItem()],
),

这里maxCrossAxisExtent我们设置的为100,每行显示了四张图片,将maxCrossAxisExtent改为200之后,每行只能显示两张图片了,这就是maxCrossAxisExtent限制每个子组件的效果。

GridView.builder()创建

GridView.builder(
  itemCount: 20,
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 3,
    mainAxisSpacing: 10,
    crossAxisSpacing: 10,
  ),
  itemBuilder: (context, index) {
    return buildGridItem();
  },
),

builder()方式去除了children参数,增加了itemCount和itemBuilder参数,itemCount用于设置子组件的个数,而itemBuilder用于创建每个子组件,用法和ListView.builder()类似,这里就不多解释了。

GridView.custom()创建

GridView.custom(
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 3,
    mainAxisSpacing: 10,
    crossAxisSpacing: 10,
  ),
  childrenDelegate: MyGridSliverDelegate(),
),

class MyGridSliverDelegate extends SliverChildDelegate {
  @override
  Widget? build(BuildContext context, int index) {
    return buildGridItem();
  }

  @override
  bool shouldRebuild(covariant SliverChildDelegate oldDelegate) {
    return this != oldDelegate;
  }

  @override
  void didFinishLayout(int firstIndex, int lastIndex) {
    super.didFinishLayout(firstIndex, lastIndex);
    print('doFinishLayout firstIndex: $firstIndex, lastIndex: $lastIndex');
  }
}

GridView.custom()用法基本和ListView是一样的,都需要自定义一个SliverChildDelegate对象,然后在此对象内部创建子组件,同样的didFinishLayout()方法在GridView也适用。

GridView.extend()创建

GridView.extent(
  maxCrossAxisExtent: 100,
  children: [for (int i = 0; i < 20; i++) buildGridItem()],
),

extend()方式其实就是简化了gridDelegate,可以直接设置maxCrossAxisExtend参数,如果需要创建每个子组件固定大小的样式,可以直接采用此扩展方式来创建GridView,实现的效果和第一种直接创建一致。

介绍完这四种方式之后,GridView的参数和ListView的类似,小伙伴们类比下就能熟悉了,文章中就不多加解释。

写在最后

本篇文章全面的介绍了Flutter中ListView和GridView组件的相关知识,希望可以帮助大家了进一步了解和熟悉列表组件的相关知识,后续会循序渐进逐步接触Flutter更多的知识。

我是Taonce,如果觉得本文对你有所帮助,帮忙关注、赞或者收藏三连一下,谢谢😆😆~