Flutter key 色块交换 updateChild child.update理解

参考:https://www.jianshu.com/p/6e704112dc67

示例1:StatelessWidget能够交换色块
示例2:StatefulWidget不能交换色块
示例3:StatefulWidget外部传入颜色能够交换色块

首先是基于原生ios的理解当时的想法是 Widget类比UIView 靠 位置都换了颜色为啥不换 ???理解不能

现在想想Widget只是个配置描述 Element才可以类比UIView

本文以下讨论全部是基于runtimetype相同且key相同(null)

1、2的代码可参考https://www.jianshu.com/p/6e704112dc67
3的代码如下 Widget build(BuildContext context) {}进行了简化方便打断点调试
_SwapColorDemo1State

class _SwapColorDemo1State extends State {
  List widgets;
  @override
  void initState() {
  // A.runtimeType == B.runtimeType;
    super.initState();
    widgets = [
      StatefulColorfulTile(defaultColor: Colors.green),
      StatefulColorfulTile(defaultColor: Colors.red,)
    ];
  }

  @override
  Widget build(BuildContext context) {

    return GestureDetector(
      onTap: swapTile,
      child: Row(
        children: widgets,
      ),
    );
  }



  swapTile() {
    print("swap");
    setState(() {
      widgets.insert(1 , widgets.removeAt(0));
    });
  }
}

StatefulColorfulTile

class StatefulColorfulTile extends StatefulWidget {
  final Color defaultColor;
  StatefulColorfulTile({this.defaultColor,Key key}) : super(key: key);

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

class StatefulColorfulTileState extends State {
  // final Color defaultColor = UniqueColorGenerator().getColor();
  @override
  void didUpdateWidget(covariant Widget oldWidget) {
    super.didUpdateWidget(oldWidget);
  }
  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: 100,
      width: 100,
      child: Container(
        color: widget.defaultColor,
      ),
    );
  }
}

对于:https://www.jianshu.com/p/6e704112dc67在学习key的用法时对这个无法交换色块的表现感到很奇怪,但对解释有点一脸懵逼
尤其是对于StatefulWidget的解释一头雾水 猛一看其实差不多 其实还是不一样的

1:所以 Element 树将根据 Widget 树进行对应的更新。
2:Flutter 使用 Element 树和它对应的控件的 State 去确定要在设备上显示的内容, 所以 Element 树没有改变

当我们交换行中的两个色块时,Flutter 遍历 Widget 树,看看骨架结构是否相同。它从 Row Widget 开始,然后移动到它的子 Widget,Element 树检查 Widget 是否与旧 Widget 是相同类型和 Key。 如果都相同的话,它会更新对新 widget 的引用。在我们这里,Widget 没有设置 Key,所以Flutter只是检查类型。它对第二个孩子做同样的事情。所以 Element 树将根据 Widget 树进行对应的更新。

现在,我们点击按钮,交换控件的次序,Flutter 将遍历 Element 树,检查 Widget 树中 Row 控件并且更新 Element 树中的引用,然后第一个 Tile 控件检查它对应的控件是否是相同类型,它发现对方是相同的类型; 然后第二个 Tile 控件做相同的事情,最终就导致 Flutter 认为这两个控件都没有发生改变。Flutter 使用 Element 树和它对应的控件的 State 去确定要在设备上显示的内容, 所以 Element 树没有改变,显示的内容也就不会改变。

不符合直观逻辑尤其是无法解释 示例3中 添加了外部defaultColor时为何色块又能交换了,因为runtimetype和key都是相等的。

为了寻求答案查了两天 先贴下用到的知识储备
·StatelessWidget中
第一次构建StatelessElement createElement() => StatelessElement(this);
StatelessElement中
Widget build() => widget.build(this);
即StatelessElement调用build其实调用的是StatelessWidget的Widget build(BuildContext context)

对于StatefulWidget
StatefulElement createElement() => StatefulElement(this);

第一次创建StatefulElement时将_state和StatefulElement进行了一对一绑定
这个绑定是始终不变的

The State instance associated with this location in the tree.
There is a one-to-one relationship between State objects and the StatefulElement objects that hold them. The State objects are created by StatefulElement in mount.

第一次创建时
StatefulElement的widget是StatefulWidget
StatefulElement的_state是widget.createState()
_state的_element是StatefulElement
_state的_widget是StatefulElement的widget

StatefulElement(StatefulWidget widget)
      : _state = widget.createState(),
        super(widget) {
    _state._element = this;
    _state._widget = widget;
  }

//此处其实是调用的_state的build方法
Widget build() => _state.build(this);

stateful和stateless执行逻辑是相同的

首先父Widget发起

updateChild=>child.update=>rebuild=>performRebuild=>build()

方法实现中唯一的不同在child.update

updateChild

Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
  if (newWidget == null) {
    if (child != null)
      deactivateChild(child);
    return null;
  }
  Element newChild;
  if (child != null) {
    bool hasSameSuperclass = true;
      final int oldElementClass = Element._debugConcreteSubtype(child);
      final int newWidgetClass = Widget._debugConcreteSubtype(newWidget);
      hasSameSuperclass = oldElementClass == newWidgetClass;
    if (hasSameSuperclass && child.widget == newWidget) {
      if (child.slot != newSlot)
        updateSlotForChild(child, newSlot);
      newChild = child;
    } else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
      if (child.slot != newSlot)
        updateSlotForChild(child, newSlot);
      child.update(newWidget);
      newChild = child;
    } else {
      deactivateChild(child);
      newChild = inflateWidget(newWidget, newSlot);
    }
  } else {
    newChild = inflateWidget(newWidget, newSlot);
  }
  return newChild;
}

stateful的update

//stateful的update
void update(StatefulWidget newWidget) {
  super.update(newWidget);
  final StatefulWidget oldWidget = _state._widget;
  // Notice that we mark ourselves as dirty before calling didUpdateWidget to
  // let authors call setState from within didUpdateWidget without triggering
  // asserts.
  _dirty = true;
  _state._widget = widget as StatefulWidget;
  rebuild();
}

stateless的update

//stateless的update
void update(StatelessWidget newWidget) {
    super.update(newWidget);
    _dirty = true;
    rebuild();
  }

rebuild=> performRebuild

performRebuild()

盯住这儿

void performRebuild() {
   Widget built;
#盯住这儿
     built = build();

    _dirty = false;
    
    _child = updateChild(_child, built, slot);
    assert(_child != null);
    if (!kReleaseMode && debugProfileBuildsEnabled)
    Timeline.finishSync();
}

1:当交换StatelessWidget色块时 函数调用流程如下

第一步:Element updateChild(Element child, Widget newWidget, dynamic newSlot) {}
第二步:child.update(newWidget);

class StatelessElement extends ComponentElement 
//StatelessElement的update
void update(StatelessWidget newWidget) {
    super.update(newWidget);
    _dirty = true;
    rebuild();
  }
rebuild=>performRebuild()

第三步:调用了build(),build是在Widget中的Widget build(BuildContext context)函数 使用了newWidget的build()所以色块能够发生交换

void performRebuild() {
   Widget built;
     built = build();
    _dirty = false;
    
    _child = updateChild(_child, built, slot);
    assert(_child != null);
    if (!kReleaseMode && debugProfileBuildsEnabled)
    Timeline.finishSync();
}

从StatelessColorfulTile对应的Element的父Element开始调用 父Element开始更新子Element

Element updateChild(Element child, Widget newWidget, dynamic newSlot) {}
此时this是child的父Element child是StatelessColorfulTile对应的Element
因为runtimetype和key相等调用到child.update(newWidget);

在child.update(newWidget)中 newWidget是交换后的色块
child.update=>rebuild()=>performRebuild()=>build()
对于StatelessElement是newWidget的build()所以色块发生了交换

总结如下:

1:element在elementtree中没有交换位置
2:element对应的widget发生了交换
3:调用了newWidget的build()导致颜色发生了改变

2:当交换StatefulWidget时

update变成了如下方法

//stateful的update
void update(StatefulWidget newWidget) {
  super.update(newWidget);
  final StatefulWidget oldWidget = _state._widget;
  // Notice that we mark ourselves as dirty before calling didUpdateWidget to
  // let authors call setState from within didUpdateWidget without triggering
  // asserts.
  _dirty = true;
  _state._widget = widget as StatefulWidget;
  rebuild();
}

1:super.update(newWidget);将当前element的widget更新为newWidget
2:_state._widget = widget 将(当前element对应的)_state的_widget更新为
newWidget

后续和StatelessElement一样去调用

performRebuild()
built = build();

三者统一了 走的方法也要和StateLess一样了 皆大欢喜啊
但是 和谐么?不和谐! 色块没有发生交换当然不和谐了!
为啥没换??!!!

解释如下:

前文说过
//此处其实是调用的_state的build方法
Widget build() => _state.build(this);

而_state和Statefulelement是一一对应的

此处ElementTree中的Statefulelement位置没有发生改变 那Statefulelement对应的_state还是老的 老的_state颜色是state内部持有的没变 即描述当前Statefulelement的state没变 还是执行老的state的build方法,
你外部交换Widget位置和我Statefulelement有啥关系。。我还执行老的build

总结如下:

1:element在elementtree中没有交换位置
2:element对应的widget发生了交换
3:(element对应的)state的_widget发生了交换
4:执行build执行的是state的build() 但是颜色是跟随state的 所以色块没有发生变化

3:那么问题来了当用示例3给StatefulWidget外部传入color时 为啥色块又能交换了呢?

头疼!!!
明明说StatefulWidget不会变的?!!

哈哈解释如下:
顺着2的解释来:
我还执行老的state中的build(),

Widget build(BuildContext context) {
    return SizedBox(
      height: 100,
      width: 100,
      child: Container(
        color: widget.defaultColor,
      ),
    );
  }

里面有个widget.defaultColor ,widget是state的_widget

//Stateful的update方法
void update(StatefulWidget newWidget) {
  super.update(newWidget);
  final StatefulWidget oldWidget = _state._widget;
  _dirty = true;
  _state._widget = widget as StatefulWidget;
  rebuild();
}

此处_state的_widget变成了widget widget其实是newWidget;
widget.defaultColor其实是newWidget.defaultColor
噢噢!! 执行build()时都是newWidget的配置了呀 !
所以色块发生了交换

总结如下:

1:element在elementtree中没有交换位置
2:element对应的widget发生了交换
3:(element对应的)state的_widget发生了交换
4:执行build执行的是state的build() 但是颜色是跟随state的widget的 _widget发生了交换 所以颜色发生了交换

额外问题:为啥说element在elementtree中没有交换位置?

官方解释如下:

Update the given child with the given new configuration.
This method is the core of the widgets system. It is called each time we are to add, update, or remove a child based on an updated configuration.
The newSlot argument specifies the new value for this element's slot.
If the child is null, and the newWidget is not null, then we have a new child for which we need to create an Element, configured with newWidget.
If the newWidget is null, and the child is not null, then we need to remove it because it no longer has a configuration.
If neither are null, then we need to update the child's configuration to be the new configuration given by newWidget. If newWidget can be given to the existing child (as determined by Widget.canUpdate), then it is so given. Otherwise, the old child needs to be disposed and a new child created for the new configuration.
If both are null, then we don't have a child and won't have a child, so we do nothing.
The updateChild method returns the new child, if it had to create one, or the child that was passed in, if it just had to update the child, or null, if it removed the child and did not replace it.
The following table summarizes the above:
| | newWidget == null | newWidget != null | | :-----------------: | :--------------------- | :---------------------- | | child == null | Returns null. | Returns new Element. | | child != null | Old child is removed, returns null. | Old child updated if possible, returns child or new Element. |

  Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
    if (newWidget == null) {
      if (child != null)
        deactivateChild(child);
      return null;
    }
    Element newChild;
    if (child != null) {
      bool hasSameSuperclass = true;
    final int oldElementClass = Element._debugConcreteSubtype(child);
        final int newWidgetClass = Widget._debugConcreteSubtype(newWidget);
        hasSameSuperclass = oldElementClass == newWidgetClass;
      if (hasSameSuperclass && child.widget == newWidget) {
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
        newChild = child;
      } else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
        child.update(newWidget);
        newChild = child;
      } else {
        deactivateChild(child);
        newChild = inflateWidget(newWidget, newSlot);
      }
    } else {
      newChild = inflateWidget(newWidget, newSlot);
    }
    return newChild;
  }

当进行交换时打断点可以发现
1:没有创建新的Element
2:逻辑判断都是进更新当前child(Element)而非创建
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
child.update(newWidget);
newChild = child;

ElementTree的初始化创建和ElementTree某个Element的更新

flutter底层内核逻辑依然是函数式的

你可能感兴趣的:(Flutter key 色块交换 updateChild child.update理解)