美文网首页
挖掘Flutter渲染几个关键场景的背后逻辑

挖掘Flutter渲染几个关键场景的背后逻辑

作者: twj小鱼儿 | 来源:发表于2020-09-13 18:12 被阅读0次

很多文章都讲过Flutter是通过三棵树的方式构造树并呈同页面的,但在具体使用过程中因为不同的代法方式会出现什么样的结果并没有很好地说明白,这里主要是针对几种关键场景来讲渲染过程背后逻辑。

首先来看基本场景:

    return bDependenciesShouldChange
        ? Scaffold(
            body: Container(
            color: Colors.blue,
            height: 500,
            alignment: Alignment.centerLeft,
            child: C(child: B()),
          ))
        : Scaffold(
            body: Container(
            color: Colors.yellow,
            height: 300,
            alignment: Alignment.centerLeft,
            child: C(child: child: B()),
          ));

这段代码只是改变了Container的属性,对树的结构并没有做调整,B的生命周期的关键表现如下:

B: initState
B: build
B: build

如此可见B重新Build了,这符合我们setState后对树的处理过程。

再来看代码场景1:

    return bDependenciesShouldChange
        ? Scaffold(
            body: Container(
            color: Colors.blue,
            height: 500,
            alignment: Alignment.centerLeft,
            child: C(child: B()),
          ))
        : Scaffold(
            body: Container(
            color: Colors.yellow,
            height: 300,
            alignment: Alignment.centerLeft,
            child: C(child: SizedBox(child: B())),
          ));

这段代码B的父节点在两种情况下是不一样的,也就是树结构改变了,那么B在这两种不同树结构情况切换时的生命周期会是怎么样的呢? 生命周期的关键表现如下:

B: initState
B: build
B: dispose
B: initState
B: build

如上所示,B里State有两次initState,说明它的Element(包括对应State)被重新生成了,这是为什么呢?
回答这个问题之前我们先来看看Widget的原码构成的其中一个核心方法:

  static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }

构造树里某个节点的Element本身是否需要更新前会执行这个方法,
即:当canUpdate返回true时,表示Element本身不需要重建可以被更新;
当canUpdate返回false时,表时Element不能被更新,不能被更新的后果是重新生成。
这里有个关键词“本身”,说明这个条件并不针对Element在父节点关系,如位置信息,后面会阐述。
回到开始的代码,由canUpdate方法可知,B的父节点发生的变化,由C变成了C的子元素SizeBox,这时在C的子节点runtimeType值由B变成了SizedBox,所以canUpdate为false导致这个位置的Element重新生成,从而导致这个节点位置以下的所有子节点如B也被重新生成。

首先来看代码场景2:

    return bDependenciesShouldChange
        ? Scaffold(
            body: Container(
            color: Colors.blue,
            height: 500,
            alignment: Alignment.centerLeft,
            child: C(child: B(key: _globalKey,)),
          ))
        : Scaffold(
            body: Container(
            color: Colors.yellow,
            height: 300,
            alignment: Alignment.centerLeft,
            child: C(child: SizedBox(child: B(key: _globalKey,))),
          ));

这段代码在上个场景代码的基础上线B增加了一个GlobalKey,又会发生什么情况呢?生命周期的关键表现如下:

B: initState
B: build
B: build

这里B只是重新Build,并没有重新生成,这是因为这个B带有一个全局的Key,树在重新Build时会在全局配置没有对应这个全局Key的Element(注:Element本身会持有对应的Widget),如果有则不会重新生成。

再来看代码场景3:

class AState extends State<A> {
  List<Widget> list = [B(), B()];
    return bDependenciesShouldChange
        ? Scaffold(
            body: Container(
            color: Colors.blue,
            height: 500,
            alignment: Alignment.centerLeft,
            child: C(child: Column(children: list)),
            //注释掉 child: C(child: Column(children: [B(), B()])),
          ))
        : Scaffold(
            body: Container(
            color: Colors.yellow,
            height: 300,
            alignment: Alignment.centerLeft,
            child: C(child: Column(children: list)),
            //注释掉 child: C(child: Column(children: [B(), B()])),
          )); 

这段代码是把Column的children用一个State作用域的变量引入;
这里B中State的生命周期各个执行阶段会发生什么?

B: initState
B: build

是的,B在父节点setState后并没有继续build;这是为什么呢?
对比下一行中被 "//注释掉" 的代码,这里每次都会实例一个全新B,这种情况的输出结果是正常的:

B: initState
B: build
B: build

解释这个原因之前要看看Element canUpdate之前做了什么?

  /// Information set by parent to define where this child fits in its parent's
  /// child list.
  ///
  /// Subclasses of Element that only have one child should use null for
  /// the slot for that child.
  dynamic get slot => _slot;
  dynamic _slot;
  @protected
  Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
    assert(() {
      if (newWidget != null && newWidget.key is GlobalKey) {
        final GlobalKey key = newWidget.key;
        key._debugReserveFor(this);
      }
      return true;
    }());
    if (newWidget == null) {
      if (child != null)
        deactivateChild(child);
      return null;
    }
    if (child != null) {
      if (child.widget == newWidget) {
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
        return child;
      }
      if (Widget.canUpdate(child.widget, newWidget)) {
        if (child.slot != newSlot)
          updateSlotForChild(child, newSlot);
        child.update(newWidget);
        assert(child.widget == newWidget);
        assert(() {
          child.owner._debugElementWasRebuilt(child);
          return true;
        }());
        return child;
      }
      deactivateChild(child);
      assert(child._parent == null);
    }
    return inflateWidget(newWidget, newSlot);
  }

还记得前面讲“本身”关键词的时候引出一个问题,就是Element在父节点的位置关系是哪个变量来维系的?看这段代码可知在 canUpdate 之前有个实例的判断child.widget == newWidget,当widget是同一个例时,Elelement不会走canUpdate逻辑而直接返回了。
存个疑:如果这里给把list的两个对象调换位置呢?给B添加非Globalkey又会是什么结果呢?参考:https://juejin.im/post/6844903811870359559 (注:文章中有不合逻辑的地方或作者没解释清楚的地方,还是以现实代码打印结果为主)。

相关文章

网友评论

      本文标题:挖掘Flutter渲染几个关键场景的背后逻辑

      本文链接:https://www.haomeiwen.com/subject/isjiektx.html