Introduction

ListView is a widget that contains multiple child components. All child widgets in ListView are presented in the form of lists. You can customize the direction of List, but unlike GridView, each List in ListView contains only Contains a widget.

Today, let's take a closer look at the underlying implementation and specific applications of ListView.

Detailed ListView

Like GridView, ListView also inherits from ScrollView, which means it is a scrollable View.

Specifically, ListView first inherits from BoxScrollView:

 class ListView extends BoxScrollView

Then BoxScrollView inherits from ScrollView:

 abstract class BoxScrollView extends ScrollView

Unique properties in ListView

First of all, let's take a look at the unique properties in ListView. Compared with its parent class, ListView has three more properties, namely itemExtent, prototypeItem and childrenDelegate.

Where itemExtent is a double type of data, if the given value is a non-null value, it indicates the extent size of the child in the scroll direction. This property is mainly used to control the extend information of children, so that each child does not need to judge its own extend by itself.

The advantage of using itemExtent is that ListView can be uniformly optimized on the scrolling mechanism, thereby improving performance.

PrototypeItem is a widget. It can be seen from the name that this widget of a prototype is a prototype. Other children can refer to the size of the prototype widget to set the extent.

The last custom property in ListView is childrenDelegate, which has the same meaning as in GridView and is used to generate children in ListView.

As we mentioned before when we explained GirdView, there is also a custom property in GirdView called gridDelegate. This gridDelegate is an instance of SliverGridDelegate, which is used to control the layout of child components in GridView.

Because the layout of ListView's subcomponents has been determined, gridDelegate is no longer needed, which is a big difference between ListView and GridView.

As an inherited class, ListView needs to implement a buildChildLayout method:

 @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);
  }

The implementation logic of this method is related to the three properties we mentioned earlier. In buildChildLayout, if itemExtent has a value, because itemExtent itself is a fixed value, SliverFixedExtentList is returned.

If itemExtent is not set and prototypeItem has a value, a SliverPrototypeExtentList is returned.

Finally, if neither itemExtent nor prototypeItem are set, a SliverList object is returned.

ListView constructor

Like GridView, ListView also provides multiple constructors to meet our diverse design needs.

First let's take a look at the most basic constructor of 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,
  })

Here, the two properties itemExtent and prototypeItem are passed in externally, and the childrenDelegate is constructed through other parameters:

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

All the child components in the ListView are in the children of the List Widget.

This default constructor is suitable for cases where there are few children, because all child components need to be passed into the list at one time, so the impact on performance is still quite large, and the passed in children are immutable.

If there are many children, you need to use other constructors, such as ListView.builder.

ListView.builder uses the builder mode to build child components. Specifically, his childrenDelegate is implemented as follows:

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

The childrenDelegate here is a SliverChildBuilderDelegate, and the function of dynamically creating children can be achieved by passing in itemBuilder and the total itemCount.

In the actual use of ListView, in order to make the page look better or more distinguishable, we usually add some separators to the items of the list. In order to automate this function, ListView provides a ListView.separated constructor for Provides a separator between list items.

ListView.separated needs to pass in two IndexedWidgetBuilder, itemBuilder and separatorBuilder respectively.

The following is the specific implementation of 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;
         },
       ),

As you can see, if index is even, itemBuilder will be used to generate a widget, and if index is odd, separatorBuilder will be used to generate a separate widget.

Finally, ListView also has a more open constructor ListView.custom, the difference between custom and other constructors is that he can customize the childrenDelegate, thus providing more room for expansion.

Use of ListView

With the above constructor, we can easily use ListView according to our own needs. Here is a simple example of using pictures as children:

 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')
        );
      },
    );
  }
}

In the above example, we use the ListView.builder constructor. In the returned Widget, the number of widgets is 5, and each item is generated by itemBuilder.

Here we encapsulate the Image in a Container, and set a constraint for the Container to control the size of the image.

The final generated interface is as follows:

In the above example, there is no separator between items. We can modify the above example slightly and use ListView.separated to construct a ListView, as shown below:

 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')
             );
           },
         );
  }
}

Here we need to pass in the separatorBuilder as the separator. For simplicity, we use the Divider widget directly.

The final generated interface is as follows:

Summarize

The above is the introduction and basic use of ListView.

Example from this article: https://github.com/ddean2009/learn-flutter.git


flydean
890 声望437 粉丝

欢迎访问我的个人网站:www.flydean.com