I will update a series of Flutter text tutorials in the coming days

Update progress: at least two articles per week;

Update location: first in the public account, the next day update in nuggets, Sifu, developer headlines and other places;

More communication: You can add me on wechat at 372623326 and follow me on Weibo at CoderWhy

I hope you can help forward, click to see, give me more creative power.

List is a view display method commonly used on mobile terminals. Flutter provides ListView and GridView.

I’ve provided a bit of Json data to show you how to do it better, so we can start with Json parsing.

JSON reading and parsing

In development, we often use local JSON or request data from the server to return to JSON. After getting THE JSON, we usually convert the JSON into Model objects for subsequent operations, because this operation is more convenient and safer.

Therefore, learning how to manipulate JSON and convert it into a Model object is also very important for Flutter development.

1.1. JSON Resource configuration

JSON is also a resource, so you need to configure it before using it

When we learned to use the Image component, we used local images, which must be configured in pubspec.yaml:

1.2. JSON reading and parsing

JSON resource reading

If we want to read the JSON resources, can be used to package: flutter/services. The dart rootBundle in the package.

There is a loadString method in the rootBundle to load JSON resources

  • Note, however, that if you look at the source code for this method, you will see that this operation is asynchronous.
  • Future and Async will not be explained here, but you can check out the dart syntax.
Future<String> loadString(String key, { bool cache = true }) async{... Omit specific code, you can view the source code}Copy the code

Do not try to copy this code to run, it will not run)

import 'package:flutter/services.dart' show rootBundle;

// Print the result of reading a string
rootBundle.loadString("assets/yz.json").then((value) => print(value));
Copy the code

JSON string conversion

Once we have the JSON string, we need to convert it to the familiar List and Map types.

This can be converted using the json.decode method in the DART: Convert package

The code is as follows:

// 1. Read the JSON file
String jsonString = await rootBundle.loadString("assets/yz.json");

// 2. Convert to List or Map
final jsonResult = json.decode(jsonString);
Copy the code

Object Model definition

After converting JSON to the List and Map types, we can convert each Map in the List into a Model object, so we need to define our own Model

class Anchor {
  String nickname;
  String roomName;
  String imageUrl;

  Anchor({
    this.nickname,
    this.roomName,
    this.imageUrl
  });

  Anchor.withMap(Map<String.dynamic> parsedMap) {
    this.nickname = parsedMap["nickname"];
    this.roomName = parsedMap["roomName"];
    this.imageUrl = parsedMap["roomSrc"]; }}Copy the code

1.3. JSON parsing code

Above we give the steps of parsing, below we give the complete code logic

Here I create a separate anchor.dart file where I define all the relevant code:

  • And then the outside world just calls my insidegetAnchorsYou can get the parsed data
import 'package:flutter/services.dart' show rootBundle;
import 'dart:convert';
import 'dart:async';

class Anchor {
  String nickname;
  String roomName;
  String imageUrl;

  Anchor({
    this.nickname,
    this.roomName,
    this.imageUrl
  });

  Anchor.withMap(Map<String.dynamic> parsedMap) {
    this.nickname = parsedMap["nickname"];
    this.roomName = parsedMap["roomName"];
    this.imageUrl = parsedMap["roomSrc"];
  }
}

Future<List<Anchor>> getAnchors() async {
  // 1. Read the JSON file
  String jsonString = await rootBundle.loadString("assets/yz.json");

  // 2. Convert to List or Map
  final jsonResult = json.decode(jsonString);

  // 3. Iterate through the List and cast the Anchor object into another List
  List<Anchor> anchors = new List(a);for (Map<String.dynamic> map in jsonResult) {
    anchors.add(Anchor.withMap(map));
  }
  return anchors;
} 
Copy the code

ListView component

When there is a large amount of data on mobile terminals, we display it through lists, such as commodity data, chat lists, contacts, moments of friends, etc.

On Android, we can use ListView or RecyclerView, and on iOS, we can use UITableView.

In Flutter, we also have a corresponding list Widget, the ListView.

2.1. ListView

2.1.1 Basic Use of ListView

The ListView can arrange all of its child widgets in one direction (vertical or horizontal, vertical by default).

One of the simplest ways to use this is to simply place all the child widgets that need to be arranged in the Children property of the ListView.

Let’s look at a code walkthrough using ListView directly:

  • To create some spacing between the text, I use the Padding Widget
class MyHomeBody extends StatelessWidget {
  final TextStyle textStyle = TextStyle(fontSize: 20, color: Colors.redAccent);

  @override
  Widget build(BuildContext context) {
    return ListView(
      children: <Widget>[
        Padding(
          padding: const EdgeInsets.all(8.0),
          child: Text("All the pain of man is, in essence, an anger at his own incompetence.", style: textStyle),
        ),
        Padding(
          padding: const EdgeInsets.all(8.0),
          child: Text("One must live in this world without deviation; And it takes some effort to keep myself on the path of reason.", style: textStyle),
        ),
        Padding(
          padding: const EdgeInsets.all(8.0),
          child: Text('I'm here to find out something, to meet something interesting.', style: textStyle), ) ], ); }}Copy the code

2.2.2. Use of ListTile

In development, we often see a list that has an Icon or image, a Title, a Subtitle, and an Icon at the end.

At this point, we can use ListTile to implement:

class MyHomeBody extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return ListView(
      children: <Widget>[
        ListTile(
          leading: Icon(Icons.people, size: 36,),
          title: Text("Contact"),
          subtitle: Text("Contact Information"),
          trailing: Icon(Icons.arrow_forward_ios),
        ),
        ListTile(
          leading: Icon(Icons.email, size: 36,),
          title: Text("Email"),
          subtitle: Text("Email Address information"),
          trailing: Icon(Icons.arrow_forward_ios),
        ),
        ListTile(
          leading: Icon(Icons.message, size: 36,),
          title: Text("News"),
          subtitle: Text("Message Details"),
          trailing: Icon(Icons.arrow_forward_ios),
        ),
        ListTile(
          leading: Icon(Icons.map, size: 36,),
          title: Text("Address"),
          subtitle: Text("Address Details"), trailing: Icon(Icons.arrow_forward_ios), ) ], ); }}Copy the code

2.2.3. Vertical scrolling

We can control the scrolling direction of the view by setting the scrollDirection parameter.

We implement a horizontal scroll with the following code:

  • Note that we need to set width to the Container, otherwise it will have no width and will not display properly.
  • Or we can set an itemExtent to the ListView, which sets the width of each item in the scrolling direction.
class MyHomeBody extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return ListView(
      scrollDirection: Axis.horizontal,
      itemExtent: 200,
      children: <Widget>[
        Container(color: Colors.red, width: 200),
        Container(color: Colors.green, width: 200),
        Container(color: Colors.blue, width: 200),
        Container(color: Colors.purple, width: 200),
        Container(color: Colors.orange, width: 200),]); }}Copy the code

2.2. The ListView. Build

There is a problem with passing all child widgets through children in the constructor: all child widgets are created by default.

Building all the widgets at once makes no difference to the user, but it creates performance issues for our application and increases the rendering time of the first screen.

We can build the child widgets by listView.build to provide performance.

2.2.1. Listview. build basic use

Listview. build is suitable for scenarios where there are many child widgets. This constructor hands the creation of the child widgets to an abstract method that the ListView manages, and the ListView creates the child widgets when it really needs to, rather than initially initializing them all.

This method takes two important parameters:

  • ItemBuilder: Method for creating list items. The ListView automatically calls this method to create the corresponding child Widget when the list scrolls to the corresponding location. The type is IndexedWidgetBuilder, which is a function type.
  • ItemCount: Indicates the number of list items. If empty, the ListView is an infinite list.

Let’s look at a simple example:

class MyHomeBody extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: 100,
      itemExtent: 80,
      itemBuilder: (BuildContext context, int index) {
        return ListTile(title: Text("Title$index"), subtitle: Text("Details$index")); }); }}Copy the code

2.2.2. Listview. build dynamic data

Previously, we made a yz.json data, and now we dynamically display a list through json data.

Consider whether the StatelessWidget can still be used at this time:

Answer: No, because currently our data is loaded asynchronously, the screen does not show the data at first (no data), and then shows the loaded data again after it is loaded from JSON (with data).

  • There’s a change of state, from no data to a change of data.
  • At this point, we need to useStatefulWidgetTo manage components.

The code is shown below:

import 'model/anchor.dart'; . Omit intermediate codeclass MyHomeBody extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    returnMyHomeBodyState(); }}class MyHomeBodyState extends State<MyHomeBody> {
  List<Anchor> anchors = [];

  // Load the data in the initialization state method
  @override
  void initState() {
    getAnchors().then((anchors) {
      setState(() {
        this.anchors = anchors;
      });
    });

    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemBuilder: (BuildContext context, int index) {
        return Padding(
          padding: EdgeInsets.all(8),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              Image.network(
                anchors[index].imageUrl,
                fit: BoxFit.fitWidth,
                width: MediaQuery.of(context).size.width,
              ),
              SizedBox(height: 8),
              Text(anchors[index].nickname, style: TextStyle(fontSize: 20),),
              SizedBox(height: 5), Text(anchors[index].roomName) ], ), ); }); }}Copy the code

2.2.3. ListView. Separated

Separated lists generate separators between list items, except for the separatorBuilder parameter, which is a separator generator.

Here’s an example: add a blue underscore to odd lines and a red underscore to even lines:

class MySeparatedDemo extends StatelessWidget {
  Divider blueColor = Divider(color: Colors.blue);
  Divider redColor = Divider(color: Colors.red);

  @override
  Widget build(BuildContext context) {
    return ListView.separated(
      itemBuilder: (BuildContext context, int index) {
        return ListTile(
          leading: Icon(Icons.people),
          title: Text("Contact${index+1}"),
          subtitle: Text("Contact number${index+1}")); }, separatorBuilder: (BuildContext context,int index) {
        return index % 2= =0 ? redColor : blueColor;
      },
      itemCount: 100); }}Copy the code

GridView component

GridView is used to display multi-column displays, which are also common in development, such as host lists in live streaming apps, product lists in e-commerce, and so on.

Flutter can be implemented using a GridView similar to ListView.

3.1. GridView constructor

Let’s start by learning how to use the GridView constructor

One way to use a GridView is to create it using a constructor that has a special parameter to compare to the ListView: a gridDelegate

The gridDelegate is used to control the number or width of items on the cross axis. The type we need to pass is SliverGridDelegate, but it is an abstract class, so we need to pass its subclass:

SliverGridDelegateWithFixedCrossAxisCount

SliverGridDelegateWithFixedCrossAxisCount({
  @required double crossAxisCount, // The number of items in the cross axis
  double mainAxisSpacing = 0.0.// Spindle spacing
  double crossAxisSpacing = 0.0.// Cross the axis spacing
  double childAspectRatio = 1.0.// The aspect ratio of the child Widget
})
Copy the code

Code walkthroughs:

class MyGridCountDemo extends StatelessWidget {

  List<Widget> getGridWidgets() {
    return List.generate(100, (index) {
      return Container(
        color: Colors.purple,
        alignment: Alignment(0.0),
        child: Text("item$index", style: TextStyle(fontSize: 20, color: Colors.white)),
      );
    });
  }

  @override
  Widget build(BuildContext context) {
    return GridView(
      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 3,
        mainAxisSpacing: 10,
        crossAxisSpacing: 10,
        childAspectRatio: 1.0), children: getGridWidgets(), ); }}Copy the code

SliverGridDelegateWithMaxCrossAxisExtent

SliverGridDelegateWithMaxCrossAxisExtent({
  double maxCrossAxisExtent, // Cross the axis item width
  double mainAxisSpacing = 0.0.// Spindle spacing
  double crossAxisSpacing = 0.0.// Cross the axis spacing
  double childAspectRatio = 1.0.// The aspect ratio of the child Widget
})
Copy the code

Code walkthroughs:

class MyGridExtentDemo extends StatelessWidget {

  List<Widget> getGridWidgets() {
    return List.generate(100, (index) {
      return Container(
        color: Colors.purple,
        alignment: Alignment(0.0),
        child: Text("item$index", style: TextStyle(fontSize: 20, color: Colors.white)),
      );
    });
  }

  @override
  Widget build(BuildContext context) {
    return GridView(
      gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
        maxCrossAxisExtent: 150,
        mainAxisSpacing: 10,
        crossAxisSpacing: 10,
        childAspectRatio: 1.0), children: getGridWidgets(), ); }}Copy the code

The previous two methods can also be used without setting the delegate

The gridView.count and gridView.extent constructors can be used to achieve the same effect, but I won’t go into details here.

3.2. The GridView. Build

As with ListView, using constructors creates all the child widgets at once, which can cause performance problems, so we can use GridView.build to hand over to the GridView to manage the child widgets we need to create.

Let’s go straight to the previous data for a code walkthrough:

class _GridViewBuildDemoState extends State<GridViewBuildDemo> {
  List<Anchor> anchors = [];

  @override
  void initState() {
    getAnchors().then((anchors) {
      setState(() {
        this.anchors = anchors;
      });
    });
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: GridView.builder(
        shrinkWrap: true,
        physics: ClampingScrollPhysics(),
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 2,
          mainAxisSpacing: 10,
          crossAxisSpacing: 10,
          childAspectRatio: 1.2
        ),
        itemCount: anchors.length,
        itemBuilder: (BuildContext context, int index) {
          return Container(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: <Widget>[
                Image.network(anchors[index].imageUrl),
                SizedBox(height: 5),
                Text(anchors[index].nickname, style: TextStyle(fontSize: 16),),
                Text(anchors[index].roomName, maxLines: 1, overflow: TextOverflow.ellipsis,) ], ), ); })); }}Copy the code

Four Slivers.

Let’s consider a layout where a sliding view includes a HeaderView, a ListView, and a GridView.

How can we make them slide uniformly? It’s hard to do with the previous scroll.

There is a Widget in Flutter that can achieve this scrolling effect: CustomScrollView, which can manage multiple scrolling views in a unified manner.

In CustomScrollView, each individual, scrollable Widget is called a Sliver.

Add: Sliver can be translated as Sliver, and you can treat each individual scroll view as a Sliver.

4.1. Basic use of Slivers

Because we need to put a lot of slivers in a CustomScrollView, the CustomScrollView has a slivers property that lets us put the corresponding slivers:

  • SliverList: similar to the ListView we used before;
  • SliverFixedExtentList: similar to SliverList, except that the scrolling height can be set;
  • SliverGrid: similar to the GridView we used before;
  • SliverPadding: Sets the inside margin of the Sliver, since it may be necessary to set the inside margin of the Sliver separately;
  • SliverAppBar: Add an AppBar, usually used as a HeaderView for CustomScrollView;
  • SliverSafeArea: Set the content to be displayed in a safe area (e.g. don’t let bangs block our content)

SliverGrid+SliverPadding+SliverSafeArea

class HomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return CustomScrollView(
      slivers: <Widget>[
        SliverSafeArea(
          sliver: SliverPadding(
            padding: EdgeInsets.all(8),
            sliver: SliverGrid(
              gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 2,
                crossAxisSpacing: 8,
                mainAxisSpacing: 8,
              ),
              delegate: SliverChildBuilderDelegate(
                (BuildContext context, int index) {
                  return Container(
                    alignment: Alignment(0.0),
                    color: Colors.orange,
                    child: Text("item$index")); }, childCount:20() (() [() (() [() }}Copy the code

4.2. Combined use of Slivers

Here I use the official sample program to create the following interface for SliverAppBar+SliverGrid+SliverFixedExtentList:

class HomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return showCustomScrollView();
  }

  Widget showCustomScrollView() {
    return new CustomScrollView(
      slivers: <Widget>[
        const SliverAppBar(
          expandedHeight: 250.0,
          flexibleSpace: FlexibleSpaceBar(
            title: Text('Coderwhy Demo'),
            background: Image(
              image: NetworkImage(
                "https://tva1.sinaimg.cn/large/006y8mN6gy1g72j6nk1d4j30u00k0n0j.jpg",
              ),
              fit: BoxFit.cover,
            ),
          ),
        ),
        new SliverGrid(
          gridDelegate: new SliverGridDelegateWithMaxCrossAxisExtent(
            maxCrossAxisExtent: 200.0,
            mainAxisSpacing: 10.0,
            crossAxisSpacing: 10.0,
            childAspectRatio: 4.0,
          ),
          delegate: new SliverChildBuilderDelegate(
                (BuildContext context, int index) {
              return new Container(
                alignment: Alignment.center,
                color: Colors.teal[100 * (index % 9)],
                child: new Text('grid item $index')); }, childCount:10,
          ),
        ),
        SliverFixedExtentList(
          itemExtent: 50.0,
          delegate: SliverChildBuilderDelegate(
                (BuildContext context, int index) {
              return new Container(
                alignment: Alignment.center,
                color: Colors.lightBlue[100 * (index % 9)],
                child: new Text('list item $index')); }, childCount:20),),,); }}Copy the code

Listen for rolling events

For a scrolling view, we often need to listen for some of its scrolling events and do something when we listen.

For example, when the view scrolls to the bottom, we might want to do more pull-up loading;

For example, when scrolling to a certain position, a back to the top button is displayed, click the back to the top button, back to the top;

For example, monitor when scrolling starts and ends;

Listening for scrolling in a Flutter consists of two parts: the ScrollController and the ScrollNotification.

5.1. ScrollController

In a Flutter, the Widget is not the final element rendered to the screen (the RenderObject is actually rendered), so usually the listener event and related information cannot be obtained directly from the Widget, but must be implemented through the Controller of the corresponding Widget.

The component controller of the ListView and GridView is the ScrollController, which can be used to retrieve the view’s scroll information and call methods to update the view’s scroll position.

In addition, it is common to change the state information of some widgets based on the position of the scroll, so ScrollController is usually used in conjunction with the StatefulWidget and controls its initialization, listening, and destruction events.

For example, when scrolling to position 1000, a back to top button is displayed:

  • jumpTo(double offset),animateTo(double offset,...): These two methods are used to jump to a specified location. They differ in that the latter performs an animation while the former does not.
  • ScrollController indirectly inherits from Listenable, and we can listen for scroll events based on ScrollController.
class MyHomePage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => MyHomePageState();
}

class MyHomePageState extends State<MyHomePage> {
  ScrollController _controller;
  bool _isShowTop = false;
  
  @override
  void initState() {
    // Initialize the ScrollController
    _controller = ScrollController();
    
    // Listen for scrolling
    _controller.addListener(() {
      var tempSsShowTop = _controller.offset >= 1000;
      if (tempSsShowTop != _isShowTop) {
        setState(() {
          _isShowTop = tempSsShowTop;
        });
      }
    });
    
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("ListView display"),
      ),
      body: ListView.builder(
        itemCount: 100,
        itemExtent: 60,
        controller: _controller,
        itemBuilder: (BuildContext context, int index) {
          return ListTile(title: Text("item$index")); } ), floatingActionButton: ! _isShowTop ?null : FloatingActionButton(
        child: Icon(Icons.arrow_upward),
        onPressed: () {
          _controller.animateTo(0, duration: Duration(milliseconds: 1000), curve: Curves.ease); },),); }}Copy the code

5.2. NotificationListener

If we want to listen for when scrolling starts and when scrolling ends, we can use NotificationListener.

  • A NotificationListener is a Widget where the template parameter T is the type of notification you want to listen for. If omitted, all types of notifications are listened for. If specified, only notifications of that type are listened for.
  • NotificationListener requires an onNotification callback function to implement the listening logic.
  • The callback can return a Boolean value indicating whether the event is prevented from bubbling upward, iftrue, the bubble terminates and the event stops propagating upward, if no return or the return value isfalse, bubbles continue.

Example: Scroll the list and show the scroll progress in the middle

class MyHomeNotificationDemo extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => MyHomeNotificationDemoState();
}

class MyHomeNotificationDemoState extends State<MyHomeNotificationDemo> {
  int _progress = 0;

  @override
  Widget build(BuildContext context) {
    return NotificationListener(
      onNotification: (ScrollNotification notification) {
        // 1. Determine the type of listening event
        if (notification is ScrollStartNotification) {
          print("Start scrolling.....");
        } else if (notification is ScrollUpdateNotification) {
          // The current scrolling position and total length
          final currentPixel = notification.metrics.pixels;
          final totalPixel = notification.metrics.maxScrollExtent;
          double progress = currentPixel / totalPixel;
          setState(() {
            _progress = (progress * 100).toInt();
          });
          print("Rolling:${notification.metrics.pixels} - ${notification.metrics.maxScrollExtent}");
        } else if (notification is ScrollEndNotification) {
          print("End scrolling....");
        }
        return false;
      },
      child: Stack(
        alignment: Alignment(9..9.),
        children: <Widget>[
          ListView.builder(
            itemCount: 100,
            itemExtent: 60,
            itemBuilder: (BuildContext context, int index) {
              return ListTile(title: Text("item$index"));
            }
          ),
          CircleAvatar(
            radius: 30,
            child: Text("$_progress%"), backgroundColor: Colors.black54, ) ], ), ); }}Copy the code

Note: All content will be published on our official website. Later, Flutter will also update other technical articles, including TypeScript, React, Node, Uniapp, MPvue, data structures and algorithms, etc. We will also update some of our own learning experiences