Flutter Rolling Container Assembly-ListView

  dart, flutter, Front end, ui

图片描述

1. Preface

FlutterAs one of the most popular technologies at present, it has already attracted the attention of a large number of technology lovers, even some, due to its excellent performance and smooth multi-terminal differences.Idle fish,meituan,TencentSuch large companies have been put into production. Although its ecology is not fully mature at present, it depends on what is behind it.GoogleBlessing, its development speed has been amazing enough to predict the futureFlutterThe demand for developers will also grow.

It has been 9102 years for the sake of the current technology taste and the future trend. As a front-end developer, there seems to be no reason not to try it. It is with this mentality that the author also began to learnFlutterAt the same time, we have built a trainingWarehouse, all subsequent code will be hosted on it, welcome star, learn together. This is my Flutter series:

In the last articleArticleWe have learnedFlutterSome of the most frequently used basic components in. However, in some scenes, when the width or height of the component exceeds the edge of the screen,FlutterIt is often givenoverflowWarning, Remind of Component Overflow Screen. In order to solve this problem, today we will learn the most commonly used one.Rolling container assemblyListView component.

2. ListView usage

From a functional comparison point of view,Flutterhit the targetListViewComponents andRNhit the targetScrollView/FlatListIt is very similar, but there are still some differences in the usage. Next, follow me to have a look.ListViewWhat are the common usage methods of components?

2.1 ListView()

The first use method is to call it directlyDefault constructorTo create a list, the effect is equivalent toRNhit the targetScrollViewComponents. But the list created this way has a problem:For thoseLong listOr need toMore expensive rendering overheadThe subcomponent of the, even if it does not appear on the screen, will still beListViewCreated, this will be a big overhead, improper use may cause performance problems and even Caton.

However, although this method may have performance problems, it still depends on its different application scenarios. Let’s take a look at its constructor (the uncommon attributes have been omitted):

ListView({
  Axis scrollDirection = Axis.vertical,
  ScrollController controller,
  ScrollPhysics physics,
  bool shrinkWrap = false,
  EdgeInsetsGeometry padding,
  this.itemExtent,
  double cacheExtent,
  List<Widget> children = const <Widget>[],
})
  • scrollDirection: the scrolling direction of the list; the optional values areAxisThehorizontalAndvertical, you can see that the default is to scroll in the vertical direction;
  • controller: controller, related to list scrolling, such as monitoring list scrolling events;
  • physics: The physical effect of continuing dragging after the list scrolls to the edge.AndroidAndiOSThe effect is different.AndroidWill present a ripple shape (corresponding toClampingScrollPhysics), andiOSThere is a rebound on the elastic effect (correspondingBouncingScrollPhysics)。 If you want to present different effects on different platforms, you can use them.AlwaysScrollableScrollPhysics, it will automatically choose their own physical effects according to different platforms. If you want to disable dragging at the edge, you can useNeverScrollableScrollPhysics;
  • shrinkWrap: This attribute determines whether the length of the list wraps only the length of its contents. WhenListViewWhen embedded in an infinitely long container assembly,shrinkWrapMust be true, otherwiseFlutterA warning will be given;
  • padding: list inner margin;
  • itemExtent: child element length. This value can be specified when the length of each item in the list is fixed, which helps to improve the performance of the list (because it can helpListViewCalculating the position of each element before actually rendering the sub-elements);
  • cacheExtent: length of pre-rendered area,ListViewOne will be left on both sides of its viewable area.cacheExtentThe length of the area as a pre-rendered area (forListView.buildOrListView.separatedThe list created by the constructor, sub-elements that are not in the visible area and the pre-rendered area will not be created or destroyed);
  • children: An array of components that hold child elements.

The above attributes introduce a lot of things, which are not as real as an actual example. We can use oneListViewComponent to wrap the implementation in the previous articleBank card,Pet card,Circle of friendsThese three examples:

Code (File address)

class NormalList extends StatelessWidget {

  const NormalList({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ListView(
      children: <Widget>[
        CreditCard(data: creditCardData),
        PetCard(data: petCardData),
        FriendCircle(data: friendCircleData),
      ],
    );
  }
}

Preview

图片描述

As you can see,Default constructorThe usage of the is very simple, placing the sub-element components directly in thechildrenIn an array. But the potential problems have also been explained before, forLong listThis application scenario should still be usedListView.buildConstructors perform better.

2.2 ListView.build()

ListViewAlthough the default constructor is simple to use, it is not suitable for long lists. To this end, let’s take a lookListView.buildConstructor:

ListView.builder({
  ...
  int itemCount,
  @required IndexedWidgetBuilder itemBuilder,
})

Unusual and and are omitted hereListViewThe default constructor repeats some parameters, in contrast, we can find thatListView.builderTwo new parameters have been added:

  • itemCount: Number of elements in the list;
  • itemBuilder: Rendering method for sub-elements, allowing customization of sub-element components (equivalent tornInFlatListComponent ofrenderItemProperty).

other thanListViewThe default constructor passes thechildrenThis way parameters specify child elements,ListView.buildBy exposing the unifieditemBuilderThe method returns control of the rendering child element to the caller. Here we use an example of WeChat public number to illustrateListView.buildThe use of methods (public number card style layout can be seenHere, is also a consolidation and review of basic components):

Code (File address)

class SubscribeAccountList extends StatelessWidget {
  const SubscribeAccountList({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Color(0xFFEFEFEF),
      child: ListView.builder(
        itemCount: subscribeAccountList.length,
        itemBuilder: (context, index) {
          return SubscribeAccountCard(data: subscribeAccountList[index]);
        },
      ),
    );
  }
}

Preview

builder_usage

As can be seen from the above code,ListView.buildThe two most important parameters for creating a list areitemCountAnditemBuilder. For the example of public number list, because the layout of each public number message card is regular, and the number of this list may be very large, so useListView.buildTo create a perfect fit.

2.3 ListView.separated()

We can use the requirements of most list classes.ListView.buildConstructor to solve the problem, but some list items need to becut-off ruleAt this time we can useFlutterAnother constructor provided byListView.separatedTo create a list. Let’s look at the difference between its constructors:

ListView.separated({
  ...
  @required IndexedWidgetBuilder separatorBuilder
})

Compared toListView.buildConstructor, you can seeListView.separatedJust one moreseparatorBuilderRequired argument. As the name implies, this is the callback method exposed to the caller’s custom split line component. Take Alipay’s buddy list for example (the style and layout of buddy cards can be seenHere), let’s take a lookListView.separatedHow to use:

Code (File address)

class FriendList extends StatelessWidget {
  const FriendList({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ListView.separated(
      itemCount: friendListData.length,
      itemBuilder: (context, index) {
        return FriendCard(data: friendListData[index]);
      },
      separatorBuilder: (context, index) {
        return Divider(
          height: .5,
          indent: 75,
          color: Color(0xFFDDDDDD),
        );
      },
    );
  }
}

Preview

separated_usage

Looking at the code, we can see that the difference lies in the implementation.separatorBuilderThis function allows us to customize the dividing line between each child element.

2.4 Summary

So far, we have studied together.ListView,ListView.buildAndListView.separatedEach of the three ways to create a list has its own applicable scenario, so it is still necessary to analyze specific problems when meeting requirements.

However, in factListViewThere is also a constructor:ListView.custom. AndListView.buildAndListView.separatedIn the end, they all passedListView.customIt was achieved. However, this article does not intend to introduce this method, because the three construction methods mentioned above are generally enough to solve the problem (we will study this when we encounter practical problems later).

3. ListView Advanced Method

We introduced aboveListViewHowever, in actual products, we will also encounter lists.pull down to refreshAndPull-up loadingSuch as demand. Next, let’s learnFlutterHow should such interaction be implemented in.

3.1 Pull-down Refresh

To be inFlutterThe drop-down refresh effect of the list is actually very simple becauseFlutterWe have a sealed oneRefreshIndicatorComponents are also very convenient to use. Look at the following sample code:

class PullDownRefreshList extends StatefulWidget {
  const PullDownRefreshList({Key key}) : super(key: key);

  @override
  _PullDownRefreshListState createState() => _PullDownRefreshListState();
}

class _PullDownRefreshListState extends State<PullDownRefreshList> {

  Future onRefresh() {
    return Future.delayed(Duration(seconds: 1), () {
      Toast.show('当前已是最新数据', context);
    });
  }

  @override
  Widget build(BuildContext context) {
    return RefreshIndicator(
      onRefresh: this.onRefresh,
      child: ListView.separated(
        itemCount: friendListData.length,
        itemBuilder: (context, index) {
          return FriendCard(data: friendListData[index]);
        },
        separatorBuilder: (context, index) {
          return Divider(
            height: .5,
            indent: 75,
            color: Color(0xFFDDDDDD),
          );
        },
      ),
    );
  }
}

Since the data source of the list is variable, we choose to inherit from this component.StatefulWidget.

You can see.RefreshIndicatorThe usage of is very simple, as long as our originalListViewAs its child, and implement itonRefreshThe method is good. AndonRefreshThe method is actually to refresh the completion notificationRefreshIndicatorA callback function of. In the above code, we simulated a 1s wait as a network request, and then popped up a Toast prompt “is the latest data” (hereToastIt’s installedtoast: ^0.1.3This bag,FlutterThe original did not provide).

Here is a list UI that mimics today’s headlines as an example (the style and layout of news cards can be seenHere), let’s look at the effect:

pull_down_refresh_usage

It can be seen that everything has been successfully implemented as expected, and the effect is still good, moreoverRefreshIndicatorIt is also very simple to use. However, due toFlutterEncapsulatedRefreshIndicatorComponent customizability is a bit weak and it is not able to meet the requirements of custom styles in most app. But fortunately, I took a look at itRefreshIndicatorThe source code is not very much, after learning animation in the future, we will go back to study how to customize a custom pull-down refresh component.

3.2 Pull-up Loading

In addition to pull-down refresh, pull-up loading is another list operation that is often encountered. However, this timeFlutterHowever, there is no ready-made component that can be directly called like pull-down refresh. The interaction of pull-up and loading needs to be completed by ourselves. To this end, let’s first briefly analyze:

  1. One is required inside the componentlistVariables store data sources for the current list;
  2. One is required inside the componentboolTypeisLoadingFlag bit to indicate whether the current is inLoadingStatus;
  3. It is necessary to be able to judge whether the current list has scrolled to the bottom, and this requires the help of what we mentioned earlier.controllerProperty (ScrollControllerThe scrolling position of the current list and the maximum scrolling area of the list can be obtained, and the result can be obtained by comparison);
  4. When you start loading data, you need toisLoadingSet totrue; When the data is loaded, the new data needs to be merged intolistVariable, and theisLoadingSet tofalse.

According to the above ideas, we can get the following code:

class PullUpLoadMoreList extends StatefulWidget {
  const PullUpLoadMoreList({Key key}) : super(key: key);

  @override
  _PullUpLoadMoreListState createState() => _PullUpLoadMoreListState();
}

class _PullUpLoadMoreListState extends State<PullUpLoadMoreList> {
  bool isLoading = false;
  ScrollController scrollController = ScrollController();
  List<NewsViewModel> list = List.from(newsList);

  @override
  void initState() {
    super.initState();
    // 给列表滚动添加监听
    this.scrollController.addListener(() {
      // 滑动到底部的关键判断
      if (
        !this.isLoading &&
        this.scrollController.position.pixels >= this.scrollController.position.maxScrollExtent
      ) {
        // 开始加载数据
        setState(() {
          this.isLoading = true;
          this.loadMoreData();
        });
      }
    });
  }

  @override
  void dispose() {
    // 组件销毁时,释放资源(一定不能忘,否则可能会引起内存泄露)
    super.dispose();
    this.scrollController.dispose();
  }

  Future loadMoreData() {
    return Future.delayed(Duration(seconds: 1), () {
      setState(() {
        this.isLoading = false;
        this.list.addAll(newsList);
      });
    });
  }

  Widget renderBottom() {
    // TODO
  }

  @override
  Widget build(BuildContext context) {
    return ListView.separated(
      controller: this.scrollController,
      itemCount: this.list.length + 1,
      separatorBuilder: (context, index) {
        return Divider(height: .5, color: Color(0xFFDDDDDD));
      },
      itemBuilder: (context, index) {
        if (index < this.list.length) {
          return NewsCard(data: this.list[index]);
        } else {
          return this.renderBottom();
        }
      },
    );
  }
}

One thing to note is that theitemCountThe value becomeslist.length + 1, this is because we have rendered one moreBottom assembly. When not loading, we can show onePull up to load moreThe suggestive component of; When loading data, we can show anotherTrying to load ...The placeholder for the.renderBottomThe implementation of is as follows:

Widget renderBottom() {
  if(this.isLoading) {
    return Container(
      padding: EdgeInsets.symmetric(vertical: 15),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text(
            '努力加载中...',
            style: TextStyle(
              fontSize: 15,
              color: Color(0xFF333333),
            ),
          ),
          Padding(padding: EdgeInsets.only(left: 10)),
          SizedBox(
            width: 20,
            height: 20,
            child: CircularProgressIndicator(strokeWidth: 3),
          ),
        ],
      ),
    );
  } else {
    return Container(
      padding: EdgeInsets.symmetric(vertical: 15),
      alignment: Alignment.center,
      child: Text(
        '上拉加载更多',
        style: TextStyle(
          fontSize: 15,
          color: Color(0xFF333333),
        ),
      ),
    );
  }
}

Finally, let’s look at the final effect:

pull_up_load_more_usage

4. Summary

First of all, this paper introduces the commonly usedListView,ListView.buildAndListView.separatedThree construction methods are used to create lists, and their different usage scenarios are illustrated with practical examples. Next, the list component is introducedpull down to refreshAndPull-up loadingThe two more commonly used interactions areFlutterHow should it be realized 3.

Through the five practical examples in this article, I believe you must have been rightFlutterHow to useListViewWith a preliminary understanding, all that remains is to practice more.

All code in this article is managed inHere, can also pay attention to myBlog.