Unfinished ~

Lists everywhere are a hassle because there’s so much gameplay being developed right now, like: Basic multi-type items, adapter design, complex item view design, item update granularity hierarchy optimization, list caching design principles and optimization, list display optimization, list nesting, sliding conflict, nested scrolling, linkage with other views, etc., all need to be understood to play on any platform

The list in Flutter I personally think is a little bit better than the Android design. First of all, the name goes back to the ListView. Secondly, Flutter uses a build interface instead of an Adapter, and separator lines and things like that are also treated as items using a build. This is easier to write than Android, much easier to understand, more flexible, and less disruptive to the list itself


There are four construction methods

The ListView of Flutter has 4 construction methods, that is, 4 constructors. The core idea is to pass the component that generates the item into the constructors as an object function (various builds). The Listview of Flutter really likes the build design pattern, not only items, dividers, but many types are packaged as builds

  • listview– This is a build that passes fixed data to Childen as items
  Widget build(BuildContext context) {
    // TODO: implement build
    return ListView(
      padding: EdgeInsets.all(20),
       children: <Widget>[
         Text("AA"),
         Text("BB"),
         Text("CC"),]); }Copy the code
  • ListView.builder– Build a list of items of type item. Of course, if and SLSE can also be used in itemBuilder. ItemCount is the number of items in the list
  Widget build(BuildContext context) {
    // TODO: implement build
    return ListView.builder(
      padding: EdgeInsets.all(20),
      itemCount: 50,
      itemBuilder: (context, index) {
        return Text("item:${index}"); }); }Copy the code
  • ListView.separated– List with secant lines, which the Flutter handles as a widget, added hereseparatorBuilderTo return the divider widget, but the divider features are:It doesn't include the last termThat is, the dividing line does not show the range of the list. This I really like the design, the line or separator as list item, can greatly facilitate our design list, can play temperament activity can be greatly increased, at least we can find out the type of item before and after according to the index, then consider can adopt different types of separate item, the most typical application is to insert ads, There are no changes to the list
  Widget build(BuildContext context) {
    // TODO: implement build
    return ListView.separated(
      padding: EdgeInsets.all(20),
      itemCount: 50,
      itemBuilder: (context, index) {
        return Text("item:${index}");
      },
      separatorBuilder: (context,index){
        return Container(
          height: 1, color: Colors.pink, ); }); }Copy the code
  • ListView.custom()– need to an implementation of a SliverChildDelegate components, such as SliverChildListDelegate and SliverChildBuilderDelegate. I’t here, because there is no need to, SliverChildListDelegate is listview, SliverChildBuilderDelegate is listview. Builder. The listView. custom is also used less often

Commonly used attributes

  • ScrollDirection– Scroll direction: Supports horizontal scroll, default is vertical scroll
scrollDirection: Axis.horizontal,
Copy the code
  • reverse– Determines if the scrolling direction is the same as the reading direction,trueWhen the list is displayed, scroll to the bottom and the last item displays the first data
  • scrollController– The main function is to control the scroll position and listen for scroll events, I will explain in detail in a separate section
  • primary– Whether scrolling is supported when the content is not enough to scroll; Another effect for iOS is whether the user swipes to the top of the status bar when they click. This is important, especially when using lists to build a page with a drop-down refresh, and it works if the amount of data is smaller than the height of the screen. A default scroll controller, PrimaryScrollController, is automatically set for the ListView. The advantage is that the parent can control the scrolling behavior of the scrollable components in the subtree
  • itemExtent– You can directly set the height of list items to improve the performance of the list
  • shrinkWrap– Whether to set the ListView length according to the total length of the child components. The default value is false, so it can scroll. Rolling components are nested, shrinkWrap attribute to set up the true, can solve a rolling conflict with NeverScrollableScrollPhysics
  • addAutomaticKeepAlives– This property indicates whether to wrap a list item (child component) in an AutomaticKeepAlive component. In a lazily loaded list, if the list item is wrapped in an AutomaticKeepAlive list, it will not be retrieved when it slides out of the viewport. It uses KeepAliveNotification to save its state. If the list item maintains its KeepAlive state itself, then this parameter must be set to false
  • addRepaintBoundaries– This property indicates whether to wrap a list item (child component) in a RepaintBoundary component. Wrapping a list item in a RepaintBoundary can avoid redrawing a list item when a scrollable component is rolling, but when the overhead of redrawing a list item is very small (such as a color block, or a short text), it is more efficient not to add a RepaintBoundary. As with addAutomaticKeepAlive, this parameter must be set to false if the list item maintains its KeepAlive state itself
  • cacheExtent– A cacheExtent is the number of items in the list that are displayed, and the package is preloaded. We can calculate the proper configuration based on the list length and item’ height

ScrollController Scroll control

If you look at the name “Controller” that means it’s a function of controlling lists. ScrollController’s main function is to listen for scroll status and provide methods to scroll to a specified position in the list

1. ScrollController constructor

ScrollController({
  double initialScrollOffset = 0.0.// Initial scroll position
  this.keepScrollOffset = true.// Whether to save the scroll position. })Copy the code

ScrollController requires us to create a new object. With these two parameters in the constructor, we can set where the selection list starts. With itemExtent, a fixed height for each column is a good idea

2. Core parameters and methods:

  • offset– The current scroll position. Note that this data is a cumulative value, not the amount of each scroll
  • jumpTo(double offset)– Scroll to the specified position without drawing
  • animateTo(double offset,...)– Scroll to the specified position to draw, you can specify the time

Use 3.

  • I’m going to create a new scrollController object
  • Add listeners to scrollController in initState
  • Set the scrollController to the ListView in the layout
class TestWidgetState extends State<TestWidget> {
  var scrollController = ScrollController();

  @override
  void initState() {
    super.initState();
    scrollController.addListener(() {
      print("Current scroll position:${scrollController.offset}");
    });
  }

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return ListView.builder(
      padding: EdgeInsets.all(20),
      physics: BouncingScrollPhysics(),
      itemCount: 50,
      controller: scrollController,
      itemBuilder: (context, index) {
        return Container(
          width: 50,
          height: 30,
          alignment: Alignment.center,
          child: Text("item:${index}")); }); }void dispose() {
    // To avoid memory leaks, dispose needs to be called
    scrollController.dispose();
    super.dispose(); }}Copy the code

Characteristics of 4.

  • ScrollController can add more than one Listener, from its methods scrollController. AddListener can see come out, walk into _listeners is a collection types in the source code
ObserverList<VoidCallback> _listeners = ObserverList<VoidCallback>();
Copy the code
  • Scrollcontroller. offset scrollController.offset scrollController.offset scrollController.offset scrollController.offset scrollController.offset scrollController.offset scrollController.offset scrollController.offset scrollController
I/flutter (7435): Current scroll position: 1.16666666666667 Current scroll position: 3.71212121212848 I/flutter (7435) Current scroll position: 5.5303030303041 I/flutter (7435) Current scroll position: 7.3484848484911 I/flutter (7435): Current scroll position: 8.07575757575766Copy the code

5. JumpTo, animateTo

These two methods are used to specify the scroll of the list at the specified position. Currently, we can only scroll to the specified px

  • jumpTo– This method is easy to use, only an offset argument
scrollController.jumpTo(500);
Copy the code
  • animateTo– This is different, it is drawn, you can specify the time and interpolator, but both must be set together, not individually
scrollController.animateTo(500,duration: Duration(milliseconds: 300), curve: Curves.ease);
Copy the code

Here’s a GIF demo:

6. ScrollPosition

ScrollController can be set to multiple scrollable components. The idea is that the ScrollController generates a ScrollPosition for each scrollable component that is set to it. The ScrollController Positions property stores this data

Naturally, all data about scrolling is stored in the ScrollPosition, such as the scrolling value:

double get offset => position.pixels;
Copy the code

The value offset returns the scrolling data of the displayed or topmost ScrollController, which is incorrect if the ScrollController is bound to multiple scrollable components. Note that the jumpTo, animateTo methods will roll all the ScrollController’s bound scrollable components like numbers

Of course, we don’t have nothing to do:

controller.positions.elementAt(0).pixels
controller.positions.elementAt(1).pixels
Copy the code

This will get the scrollvalue of unscrollable components, but we need to know exactly what order scrollable components were originally inserted


NotificationListener Scrolling listener

We used scrollController to listen for list scrolling, but here we have another idea, inheriting the Android idea of nested scrolling

NestedScrollingParent and NestedScrollingChild. NestedScrollingChild sends scroll events. NestedScrollingParent controls the propagation and value of scroll events

Flutter follows this idea. In the Widget tree, a scrollable widget passes a notification of a scroll event up one by one when it is scrollable. We can monitor this notification through NotificationListener. A NotificationListener is also a widget, so long as the NotificationListener is laid out at a higher level than the ListView, it doesn’t matter how many levels it is

All the scrolling widgets in the Flutter, such as ScrollView, ListView, PageView, etc. can use NotificationListener to listen for scrolling events

  • The classic android apps are:Behavior CoordinatorLayout RecycleView
  • In The Flutter:NotificationListener listview

1. Listening method

The onNotification(ScrollNotification Notification) method in NotificationListener gets the scroll event, and the value is wrapped in the ScrollNotification parameter. Requires that we return a Boolean value:

  • trueNotifcation stops here. We intercept scroll events and do not continue to upload scroll events, but do not affect the listView widget’s scrolling
  • false– Then the Notification will continue to be passed to the outer widget

2. Monitored data

The data for a rolling event is of type ScrollNotification, and the parameters are in its metrics parameters:

  • metrics.pixels– The current location, starting with 0. Default is 0
  • metrics.atEdge– Whether it is at the top or the bottom
  • metrics.axis– Roll vertically or horizontally
  • metrics.axisDirection– Scroll in down or up direction, test, both are down
  • metrics.extentAfter– How far the bottom of the widget is from the bottom of the list
  • metrics.extentBefore– How far is the top of the widget from the top of the list
  • metrics.extentInside– Indicates the length of the list within the scope of the widget
  • metrics.maxScrollExtent– Maximum scroll distance, list length – Widget length
  • metrics.minScrollExtent– Minimum rolling distance
  • metrics.viewportDimension– the length of the widget
  • metrics.outOfRange– Whether it crossed the border

3. After the test

The interpretation of the documents is uncertain, and we’ll have to try it ourselves

  • axisDirection– It has always been down. It is more accurate for us to judge by ourselves according to the change of the value
  • pixels– Pull up to load moreIt's getting bigger and bigger, pull down to refreshIt gets smaller and smaller, all the way to 0
  • atEdge– it is possible to determine whether it is at the top or at the bottom. It is true when it is at the top or at the bottom
  • Rolling numbers– extentAfter + extentBefore really = maxScrollExtent
  • The length of the widget– viewportDimension and extentInside have the same value
  • outOfRange– Bottom or top is false by default, and will only become true if we continue scrolling with the physics properties. Note the critical value

4. Sample:

  Widget build(BuildContext context) {
    return NotificationListener(
      onNotification: (ScrollNotification notification) {
        print("pixels:${notification.metrics.pixels}");
        print("atEdge:${notification.metrics.atEdge}");
        print("axis:${notification.metrics.axis}");
        print("axisDirection:${notification.metrics.axisDirection}");
        print("extentAfter:${notification.metrics.extentAfter}");
        print("extentBefore:${notification.metrics.extentBefore}");
        print("extentInside:${notification.metrics.extentInside}");
        print("maxScrollExtent:${notification.metrics.maxScrollExtent}");
        print("minScrollExtent:${notification.metrics.minScrollExtent}");
        print("viewportDimension:${notification.metrics.viewportDimension}");
        print("outOfRange:${notification.metrics.outOfRange}");
        print("____________________________________________.");
        return true;
      },
      child: Container(
        child: Stack(
          children: <Widget>[
            ListView.builder(
              padding: EdgeInsets.all(20),
              physics: BouncingScrollPhysics(),
              itemCount: 50,
              itemExtent: 35,
              controller: scrollController,
              itemBuilder: (context, index) {
                return Container(
                  alignment: Alignment.center,
                  child: Text("item:${index}")); }, ), Positioned( left:20,
              top: 20,
              child: RaisedButton(
                child: Text("Click and scroll"),
                onPressed: () {
                  scrollController.animateTo(500,
                      duration: Duration(milliseconds: 300),
                      curve: Curves.ease);
                  print("AAA"); },),),],),); }Copy the code

physics

This is the Behavior of the Flutter, depending on the nested scrolling mentioned above, it allows us to do additional operations on the entire list when it reaches the top or bottom, typically by dropping to the top and then bouncing back

Code:

physics: BouncingScrollPhysics(),
Copy the code

The system provides several default implementations, and it is not clear if they can be customized:

  • NeverScrollablePhysics– Lists cannot be scrolled
  • BouncingScrollPhysics– Rebound effect, which is the effect of the GIF above
  • ClampingScrollPhysics– System default, at the end of the display water ripple
  • PageScrollPhysics– For PageView, if the listView is set, there will be a big bounce and bounce at the end of the slide
  • AlwaysScrollableScrollPhysics– Lists are always scrollable. There is a rebound effect on iOS, but not on Android. If the primary is set to false, but set AlwaysScrollableScrollPhysics, list at this time can be sliding
  • FixedExtentScrollPhysics– This must be done in conjunction with the responding widget, listView is not available, more details will be written later

: general


Scroll data cache

The ListView inherits from Scrollable and is itself a StatefulWidget, which naturally holds data. The scroll offset is also saved, so the main ListView is not removed from the Widget tree and the scroll state is always there

Sometimes the ListView is removed due to changes in the widget tree. For example, the State of the scrollable component of the TabBarView is destroyed when the Tab is switched. If we want to show the last scrollable position when we switch back and forth between tabs, we must cache the scrollable offset

Here we use PageStorage, which is a component used to save page (routing) related data, PageStorage declaration cycle is the entire app, no matter how many pages can save data without loss, The core is to set the PageStorageKey to the wigdet constructor

We’re going to set the PageStorageKey of each TAB to its own string

new TabBarView(
   children: myTabs.map((Tab tab) {
    new MyScrollableTabView(
      key: new PageStorageKey<String>(tab.text), // like 'Tab 1'
       tab: tab,
     ),
   }),
 )
Copy the code

That’s all we have right now


Fixed head widget

We always need to fix the header in the list, and the header view doesn’t scroll down the list. Here’s the easiest way to do it: Column + expanded. Column inherits from Flex and automatically adjusts the widget length, and Expanded automatically stretches the component size, so it makes sense to use both

  Widget build(BuildContext context) {
    // TODO: implement build
    return Column(
      children: <Widget>[
        Text("I am the head"),
        Expanded(
          child: ListView.builder(
            padding: EdgeInsets.all(20),
            physics: BouncingScrollPhysics(),
            cacheExtent: 10,
            itemCount: 50,
            itemExtent: 35,
            controller: scrollController,
            itemBuilder: (context, index) {
              return Container(
                alignment: Alignment.center,
                child: Text("item:${index}")); },),),],); }}Copy the code


Common design ideas

  • withListView.separatedInsert ads