关于flutter:flutter系列之flutter中常用的ListView-layout详解

9次阅读

共计 5229 个字符,预计需要花费 14 分钟才能阅读完成。

简介

ListView 是蕴含多个 child 组件的 widget,在 ListView 中所有的 child widget 都是以 list 的模式来出现的,你能够自定义 List 的方向,然而和 GridView 不同的是 ListView 中的每一个 List 外面都只蕴含一个 widget。

明天咱们来具体理解一下 ListView 的底层实现和具体的利用。

ListView 详解

和 GridView 一样,ListView 也是继承自 ScrollView,示意它是一个能够滚动的 View。

具体而言,ListView 首先继承自 BoxScrollView:

class ListView extends BoxScrollView

而后 BoxScrollView 又继承自 ScrollView:

abstract class BoxScrollView extends ScrollView 

ListView 中的特有属性

首先咱们来看下 ListView 中的特有属性,ListView 和它的父类相比,多了三个属性,别离是 itemExtent,prototypeItem 和 childrenDelegate。

其中 itemExtent 是一个 double 类型的数据,如果给定的是一个非空值,那么示意的是 child 在 scroll 方向的 extent 大小。这个属性次要用来管制 children 的 extend 信息,这样每个 child 就不须要自行来判断本人的 extend。

应用 itemExtent 的益处在于,ListView 能够对立的在滚动机制上进行优化,从而晋升性能体现。

prototypeItem 是一个 widget,从名字就能够看出,这个一个 prototype 的 widget,也就是说是一个原型,其余的 child 能够参照这个原型 widget 的大小进行 extent 的设置。

ListView 中的最初一个自定义属性是 childrenDelegate,这个 childrenDelegate 和 GridView 中的含意是一样的,用来生成 ListView 中 child。

之前咱们在解说 GirdView 的时候有提到过,GirdView 中还有一个自定义的属性叫做 gridDelegate,这个 gridDelegate 是一个 SliverGridDelegate 的实例,用来管制子组件在 GridView 中的布局。

因为 ListView 的子组件的布局是曾经确定的,所以就不再须要 gridDelegate 了,这是 ListView 和 GridView 的一大区别。

ListView 作为一个继承的类,须要实现一个 buildChildLayout 的办法:

  @override
  Widget buildChildLayout(BuildContext context) {if (itemExtent != null) {
      return SliverFixedExtentList(
        delegate: childrenDelegate,
        itemExtent: itemExtent!,
      );
    } else if (prototypeItem != null) {
      return SliverPrototypeExtentList(
        delegate: childrenDelegate,
        prototypeItem: prototypeItem!,
      );
    }
    return SliverList(delegate: childrenDelegate);
  }

这个办法的实现逻辑和咱们之前讲到的三个属性是相关联的,在 buildChildLayout 中,如果 itemExtent 有值的话,因为 itemExtent 自身就是一个固定值,所以返回的是 SliverFixedExtentList。

如果 itemExtent 没有设置,并且 prototypeItem 有值的话,返回的是一个 SliverPrototypeExtentList。

最初,如果 itemExtent 和 prototypeItem 都没有设置的话,返回的是一个 SliverList 对象。

ListView 的构造函数

和 GridView 一样,为了满足咱们的多样性的设计需要,ListView 也提供了多个构造函数。

首先咱们来看下 ListView 的最根本的构造函数:

ListView({
    Key? key,
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
    ScrollController? controller,
    bool? primary,
    ScrollPhysics? physics,
    bool shrinkWrap = false,
    EdgeInsetsGeometry? padding,
    this.itemExtent,
    this.prototypeItem,
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
    bool addSemanticIndexes = true,
    double? cacheExtent,
    List<Widget> children = const <Widget>[],
    int? semanticChildCount,
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
    String? restorationId,
    Clip clipBehavior = Clip.hardEdge,
  })

这里 itemExtent 和 prototypeItem 这两个属性是内部传入的,childrenDelegate 是通过其余的参数结构而来的:

 childrenDelegate = SliverChildListDelegate(
         children,
         addAutomaticKeepAlives: addAutomaticKeepAlives,
         addRepaintBoundaries: addRepaintBoundaries,
         addSemanticIndexes: addSemanticIndexes,
       ),

ListView 中所有的 child 组件都在 List Widget 的 children 中。

这个默认的构造函数,实用于 child 比拟少的状况,因为须要一次传入所有的 child 组件到 list 中,所以对性能的影响还是挺大的,并且传入的 child 是不可变的。

如果 child 比拟多的状况下,就须要应用到其余的构造函数了, 比方 ListView.builder。

ListView.builder 应用的是 builder 模式来构建 child 组件,具体而言他的 childrenDelegate 实现如下:

childrenDelegate = SliverChildBuilderDelegate(
         itemBuilder,
         childCount: itemCount,
         addAutomaticKeepAlives: addAutomaticKeepAlives,
         addRepaintBoundaries: addRepaintBoundaries,
         addSemanticIndexes: addSemanticIndexes,
       ),

这里的 childrenDelegate 是一个 SliverChildBuilderDelegate, 通过传入 itemBuilder 和总的 itemCount 就能够实现动态创建 child 的性能。

在 ListView 的理论应用过程中,为了页面难看或者更有区分度,咱们个别会在 list 的 item 中增加一些分隔符 separator, 为了自动化实现这个性能,ListView 提供了一个 ListView.separated 的构造函数,用来提供 list item 两头的分隔符。

ListView.separated 须要传入两个 IndexedWidgetBuilder, 别离是 itemBuilder 和 separatorBuilder。

上面是 childrenDelegate 的具体实现:

 childrenDelegate = SliverChildBuilderDelegate((BuildContext context, int index) {
           final int itemIndex = index ~/ 2;
           final Widget widget;
           if (index.isEven) {widget = itemBuilder(context, itemIndex);
           } else {widget = separatorBuilder(context, itemIndex);
             assert(() {if (widget == null) {throw FlutterError('separatorBuilder cannot return null.');
               }
               return true;
             }());
           }
           return widget;
         },
         childCount: _computeActualChildCount(itemCount),
         addAutomaticKeepAlives: addAutomaticKeepAlives,
         addRepaintBoundaries: addRepaintBoundaries,
         addSemanticIndexes: addSemanticIndexes,
         semanticIndexCallback: (Widget _, int index) {return index.isEven ? index ~/ 2 : null;},
       ),

能够看到,如果 index 是 even 的话就会应用 itemBuilder 生成一个 widget,如果 index 是 odd 的话,就会应用 separatorBuilder 来生成一个 separator 的 widget。

最初,ListView 还有一个更加凋谢的构造函数 ListView.custom,custom 和其余构造函数不同的中央在于他能够自定义 childrenDelegate,从而提供了更多的扩大空间。

ListView 的应用

有了下面的构造函数,咱们能够很不便的依据本人的须要来应用 ListView,上面是一个简略的应用图片做 child 的例子:

class ListViewApp extends StatelessWidget{const ListViewApp({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: 5,
      itemBuilder: (BuildContext context, int index) {
        return Container(constraints: const BoxConstraints(maxWidth:100,maxHeight: 100),
            child: Image.asset('images/head.jpg')
        );
      },
    );
  }
}

下面的例子中,咱们应用的是 ListView.builder 构造函数,返回的 Widget 中,中的 widget 个数是 5,每个 item 是由 itemBuilder 来生成的。

这里咱们把 Image 封装在一个 Container 中,并且为 Container 设置了一个 constraints 来管制图片的大小。

最终生成的界面如下:

下面的例子中,item 之间是没有分隔符的,咱们能够讲下面的例子进行略微的批改一下,应用 ListView.separated 来结构 ListView,如下所示:

class ListViewSeparatedApp extends StatelessWidget{

  @override
  Widget build(BuildContext context) {
    return ListView.separated(
           itemCount: 10,
           separatorBuilder: (BuildContext context, int index) => const Divider(),
           itemBuilder: (BuildContext context, int index) {
             return Container(constraints: const BoxConstraints(maxWidth:50,maxHeight: 50),
               child: Image.asset('images/head.jpg')
             );
           },
         );
  }
}

这里咱们须要传入 separatorBuilder 来作为分隔符,为了简略起见,咱们间接应用了 Divider 这个 widget。

最初生成的界面如下:

总结

以上就是 ListView 的介绍和根本的应用。

本文的例子:https://github.com/ddean2009/learn-flutter.git

正文完
 0