1. Introduction

In the previous article, Flutter rendering explained the function of Key by using two small demos. In fact, in the process of writing a small demo, I also encountered a problem. At the beginning, I didn’t think clearly. Later, I communicated with my colleagues and searched the source code to find some reasons.

Phenomenon of 2.

In Demo2 — Swapping Two Widgets, it is mentioned at the beginning that if two widgets are inherited StatelessWidgets, the result is successfully swapped. The complete code is shown below.

class KeyDemo1Page extends StatefulWidget {
  @override
  _KeyDemo1PageState createState() => _KeyDemo1PageState();
}

class _KeyDemo1PageState extends State<KeyDemo1Page> {

  List<Widget> tiles = [
    StatelessColorfulTile(),
    StatelessColorfulTile(),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.blue,
        centerTitle: true,
        title: Text("Key demo"),
      ),
      body: Column(children: tiles,),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.delete),
        onPressed: (){
          setState(() {
            tiles.insert(1, tiles.removeAt(0)); }); },),); }}class StatelessColorfulTile extends StatelessWidget {
  Color myColor = Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256));

  @override
  Widget build(BuildContext context) {
    print("build hahahahahha");
    return Container(
        color: myColor,
        child: Padding(padding: EdgeInsets.all(70.0))); }}Copy the code

This doesn’t work if you change the Column inside the body to the ListView and assign tiles to the Children of the ListView. Clicking the button does not switch. This is very strange, the same container ah, why one can not be one. I tried a variety of things.

If you swap tiles array elements by clicking the button instead of deleting an element, the interface still does not respond. If the swap element is changed to create a new array, insert the tiles element into the new array, and have the tiles point to the new array, the result is good.

Changing the position of an element in the array or adding or subtracting an element does not change the interface until the tiles point to the interface.

It is possible to change the ListView(children: Tiles) form to listView. builder, as shown below.

class _KeyDemo1PageState extends State<KeyDemo1Page> {... Widget _itemForRow(BuildContext context,int index) {
    return tiles[index];
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      ......
      body: ListView.builder(
        itemCount: tiles.length,
        itemBuilder: _itemForRow,
      ),
      ......
    );
  }
}
Copy the code

3. The reason why

There is a difference between using ListView and ListView.builder, so you can compare it to the source code.

The ListView is constructed as follows

istView({
    Key key,
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
    ScrollController controller,
    bool primary,
    ScrollPhysics physics,
    bool shrinkWrap = false,
    EdgeInsetsGeometry padding,
    this.itemExtent,
    bool addAutomaticKeepAlives = true.bool addRepaintBoundaries = true.bool addSemanticIndexes = true.double cacheExtent,
    List<Widget> children = const <Widget>[],
    int semanticChildCount,
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
  }) : childrenDelegate = SliverChildListDelegate(
         children,
         addAutomaticKeepAlives: addAutomaticKeepAlives,
         addRepaintBoundaries: addRepaintBoundaries,
         addSemanticIndexes: addSemanticIndexes,
       ),
       super(
         key: key,
         scrollDirection: scrollDirection,
         reverse: reverse,
         controller: controller,
         primary: primary,
         physics: physics,
         shrinkWrap: shrinkWrap,
         padding: padding,
         cacheExtent: cacheExtent,
         semanticChildCount: semanticChildCount ?? children.length,
         dragStartBehavior: dragStartBehavior,
       );
Copy the code

Children are also assigned to the SliverChildListDelegate, so look inside that, and there’s an important method in the SliverChildListDelegate, as follows

@override
  bool shouldRebuild(covariant SliverChildListDelegate oldDelegate) {
    returnchildren ! = oldDelegate.children; }Copy the code

ShouldRebuild should return false if the ListView’s child array is not changed and the build method shouldn’t be executed, so the interface shouldn’t change. As you can imagine, listView. builder also has this method, but it should return a different result. Check it out.

The constructor of listView. builder looks like this

ListView.builder({
    Key key,
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
    ScrollController controller,
    bool primary,
    ScrollPhysics physics,
    bool shrinkWrap = false,
    EdgeInsetsGeometry padding,
    this.itemExtent,
    @required IndexedWidgetBuilder itemBuilder,
    int itemCount,
    bool addAutomaticKeepAlives = true.bool addRepaintBoundaries = true.bool addSemanticIndexes = true.double cacheExtent,
    int semanticChildCount,
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
  }) : assert(itemCount == null || itemCount >= 0),
       assert(semanticChildCount == null || semanticChildCount <= itemCount),
       childrenDelegate = SliverChildBuilderDelegate(
         itemBuilder,
         childCount: itemCount,
         addAutomaticKeepAlives: addAutomaticKeepAlives,
         addRepaintBoundaries: addRepaintBoundaries,
         addSemanticIndexes: addSemanticIndexes,
       ),
       super(
         key: key,
         scrollDirection: scrollDirection,
         reverse: reverse,
         controller: controller,
         primary: primary,
         physics: physics,
         shrinkWrap: shrinkWrap,
         padding: padding,
         cacheExtent: cacheExtent,
         semanticChildCount: semanticChildCount ?? itemCount,
         dragStartBehavior: dragStartBehavior,
       );
Copy the code

We go SliverChildBuilderDelegate inside look and found there is also a shouldRebuild method, the code is as follows.

@override
  bool shouldRebuild(covariant SliverChildBuilderDelegate oldDelegate) => true;
Copy the code

ShouldRebuild should return true by default, so this method should be executed on all builds, so the interface can be toggled.

4. To summarize

After executing setState, the children element in the ListView will be changed, including moving, adding and deleting. The interface will not change, but the entire children point will be changed. Using ListView.builder changes this.

From the beginning to find the problem to try a variety of phenomena to find some basic rules, and then go to the source code to find the reason, basically can figure out the problem, but there are still some technical details need a deeper understanding.