阅读 460

Flutter中的Element(下篇)

级别: ★☆☆☆☆

标签:「Flutter」「Element 」「重要方法与调用时机 」
作者: 沐灵洛
审校: QiShare团队


上一篇我们简单介绍了Flutter中Element是什么以及它的生命周期。 本篇我们将与大家一起探讨Element的一些重要的方法的作用以及调用时机。

Element的重要方法

  • void mount(Element parent, dynamic newSlot)

**描述:**在parent element中给定的插槽上,加入当前的 element。 **调用时机:**新创建的element首次加入树中时,

  • Element updateChild(Element child, Widget newWidget, dynamic newSlot)

**描述:**使用新的配置widget,更新树中element。这个方法是widges系统的核心方法。

**调用时机:**每次基于更新后的widget,添加、更新或移除树中的child element时。

方法原理:

newWidget == nullnewWidget != null
child == nullReturns null.Returns new [Element].
child != nullOld child is removed, returns null.Old child updated if possible, returns child or new [Element].
```dart
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
if (newWidget == null) {
if (child != null)     
  ///newWidget== null 弃用element                                                                                     
  deactivateChild(child);                                                                                   
return null;                                                                                                
复制代码

}
Element newChild;
if (child != null) {
///标志位:解决hot reload前后Widget的类型不一致导致的类型错误 。 ///比如:之前是StatefulWidget(StatefulElement) ,现在是StatelessWidget(StatelessElement) 。
bool hasSameSuperclass = true;
assert(() { ///element is StatefulElement ? 1 : element is StatelessElement ? 2 : 0;
final int oldElementClass = Element._debugConcreteSubtype(child);
/// widget is StatefulWidget ? 1 : widget is StatelessWidget ? 2 : 0;
final int newWidgetClass = Widget._debugConcreteSubtype(newWidget);
///确保类型一致
hasSameSuperclass = oldElementClass == newWidgetClass;
return true;
}()); ///widget相等
if (hasSameSuperclass && child.widget == newWidget) { ///只有一个child的element,它的slot为null
if (child.slot != newSlot) ///作用:修改给定的child在其parent中占据的slot。 ///调用时机:MultiChildRenderObjectElement ///和其他拥有多个渲染对象的RenderObjectElement的子类。 ///当它们的childelementchild(renderObject)数组 ///的一个位置移动到其他位置时。
updateSlotForChild(child, newSlot);
newChild = child;
} else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
///询问Widget.canUpdate
if (child.slot != newSlot)
///修改给定的child在其parent中占据的slot。
updateSlotForChild(child, newSlot); ///使用新的配置,更新当前child element
child.update(newWidget);
assert(child.widget == newWidget);
assert(() {
child.owner._debugElementWasRebuilt(child);
return true;
}());
newChild = child;
} else { /// 弃用当前的child element
deactivateChild(child);
assert(child._parent == null); ///注入新的widget
newChild = inflateWidget(newWidget, newSlot);
}
} else { ///child == null 创建新的
newChild = inflateWidget(newWidget, newSlot);
}

assert(() {
if (child != null)
_debugRemoveGlobalKeyReservation(child);
final Key key = newWidget?.key;
if (key is GlobalKey) {
key._debugReserveFor(this, newChild);
}
return true;
}());

return newChild;
}

- ` void update(covariant Widget newWidget)`

**描述:**使用新`widget`修改用于配置此`element`的`widget`。

**调用时机:**当`parent element`希望使用不同的`widget`更新此`element`时,并且新的`widget`必须保证和旧的`widget`拥有相同的`runtimeType`。

- `Element inflateWidget(Widget newWidget, dynamic newSlot)`

**描述:**为给定的`widget`创建一个`element`,并且将其作为`child`添加到当前`element`的给定`slot`中。

**调用时机:**此方法通常由`updateChild`调用,但也可以由需要对创建的`element`进行更细粒度控制的子类直接调用。

**注意:** 如果给定的`widget`有`global key`,并且其对应的`element`已经存在,这个函数将复用此`element`(可能从树中的另一个位置移植它,或从`inactive`元素列表中重新激活它)而不是创建一个新的元素。此方法返回的`element`已经被`mounted`并且处于`active`状态。
```dart
  Element inflateWidget(Widget newWidget, dynamic newSlot) {
    assert(newWidget != null);
    final Key key = newWidget.key;
   ///判断是否是`GlobalKey`
    if (key is GlobalKey) {
      ///从处于`inactive`的列表中,重新获取。
     ///1.final Element element = key._currentElement;
     ///2.element == null或!Widget.canUpdate(element.widget, newWidget)      
     ///reture null
     ///3. final Element parent = element._parent;
     ///4. parent.forgetChild(element);
     ///5. parent.deactivateChild(element);
     ///6.owner._inactiveElements.remove(element);
    ///7.return element 
      final Element newChild = _retakeInactiveElement(key, newWidget);
      if (newChild != null) {
        assert(newChild._parent == null);
        assert(() {
          _debugCheckForCycles(newChild);
          return true;
        }());
        ///重新激活获得的`element`。
        ///1.修改`parent`
        ///2._updateDepth(int parentDepth)
        ///3.递归遍历修改`element`中所有子元素的状态未`active`.
       ///针对拥有`children`的`element`。
       ///4.调用attachRenderObject(dynamic newSlot)
      ///添加渲染对象到树中。
        newChild._activateWithParent(this, newSlot);
       ///使用新的`widget`,更新此`element`,在新的`slot`上
        final Element updatedChild = updateChild(newChild, newWidget, newSlot);
        assert(newChild == updatedChild);
       ///返回updatedChild
        return updatedChild;
      }
    }
    ///如果不含globalKey
   ///创建新的元素
    final Element newChild = newWidget.createElement();
    assert(() {
      _debugCheckForCycles(newChild);
      return true;
    }());
   ///挂载:加入树中。
    newChild.mount(this, newSlot);
    assert(newChild._debugLifecycleState == _ElementLifecycle.active);
    return newChild;
  }
复制代码
  • void deactivateChild(Element child)

**描述:**从inactive elements 列表中移除给定的element并且 从渲染树中分离此element所对应的render object

**调用时机:**一般在元素更新(删除)其子元素时调用,但是在global key更换父节点时,新父会主动调用旧父的deactivateChild ,并且在此之前会先调用旧父的forgetChild,对旧父进行更新。

 void deactivateChild(Element child) {
    assert(child != null);
    assert(child._parent == this);
    ///去父
    child._parent = null;
    ///分离其渲染对象
    child.detachRenderObject();
    ///加入不活跃的数组中
    owner._inactiveElements.add(child); // this eventually calls child.deactivate()
    assert(() {
      if (debugPrintGlobalKeyedWidgetLifecycle) {
        if (child.widget.key is GlobalKey)
          debugPrint('Deactivated $child (keyed child of $this)');
      }
      return true;
    }());
  }
复制代码
  • void detachRenderObject()

**描述:**从渲染树中移除渲染对象。

**调用时机:**一般在deactivateChild方法中被调用

**注意:**该函数的默认实现只是对其child递归调用detachRenderObject,但是RenderObjectElement.detachRenderObject重写了此方法后,对其进行了覆盖(未调用super)。

  void detachRenderObject() {
    visitChildren((Element child) {
      child.detachRenderObject();
    });
    _slot = null;
  }
复制代码
  • void forgetChild(Element child)

**描述:**从元素的child列表中删除给定的child,以准备在元素树中其他位置重用该child

**调用时机:**一般在deactivateChild方法调用前。update会负责创建或者更新用于替换此child的新的child。 关于对此方法的详细用法可以查看MultiChildRenderObjectElement类,此类中forgetChild会影响update时对于replaceWithNullIfForgotten方法的调用,也会影响visitChildren时对于子元素的遍历。

  • void updateSlotForChild(Element child, dynamic newSlot)

**描述:**修改给定的child在父元素中占据的插槽 。 调用时机:MultiChildRenderObjectElement和其他拥有多个渲染对象的RenderObjectElement的子类。当它们的childelementchild(renderObject)数组的一个位置移动到其他位置时。

 void updateSlotForChild(Element child, dynamic newSlot) {
    assert(_debugLifecycleState == _ElementLifecycle.active);
    assert(child != null);
    assert(child._parent == this);
    void visit(Element element) {
      element._updateSlot(newSlot);
      if (element is! RenderObjectElement)
        element.visitChildren(visit);
    }
    visit(child);
  }
复制代码
  • void activate()

**描述:**将元素的生命周期由inactive变为active。 **调用时机:**当之前停用的元素重新合并到树中时,框架将调用此方法。元素第一次激活时(即,状态从initial 开始时),framework不会调用此方法,而是通过调用mount

void activate() {
    ....
    final bool hadDependencies = (_dependencies != null && _dependencies.isNotEmpty) || _hadUnsatisfiedDependencies;
    _active = true;
    // We unregistered our dependencies in deactivate, but never cleared the list.
    // Since we're going to be reused, let's clear our list now.
    _dependencies?.clear();
    _hadUnsatisfiedDependencies = false;
    _updateInheritance();
    assert(() {
      _debugLifecycleState = _ElementLifecycle.active;
      return true;
    }());
    if (_dirty)
      ///将元素添加到`dirty element`列表中,
      ///以便在`WidgetsBinding.drawFrame`调用`buildScope`
     ///时将对其进行重建。
      owner.scheduleBuildFor(this);
    if (hadDependencies)
      didChangeDependencies();
  }
复制代码
  • void attachRenderObject(dynamic newSlot)

**描述:**将renderObject添加到渲染树中slot指定的位置。 **调用时机:**该函数的默认实现只是对其child递归调用attachRenderObject,但是RenderObjectElement.attachRenderObject重写了此方法后,对其进行了覆盖(未调用super)。

 void attachRenderObject(dynamic newSlot) {
    assert(_slot == null);
    visitChildren((Element child) {
      child.attachRenderObject(newSlot);
    });
    _slot = newSlot;
  }
复制代码
  • void unmount()

描述: 生命周期由inactive 变为 defunct。 **调用时机:**当framework确定处于inactive状态的元素永远不会被激活时调用。在每个动画结束时,framework会对任何保持inactive状态的元素调unmount,以防止inactive元素保持inactive的时间超过单个动画帧。调用此函数后,该元素将不再合并到树中。

  • void reassemble()

描述: 用于快速开发的一种debugging策略。 调用时机: 在调试期间重新组装(reassemble)应用程序时调用,例如:hot reload期间。此函数仅在开发期间被调用,并且调用了reassemblebuild将至少被调用一次。

  • void didChangeDependencies()

调用时机: 当此元素的依赖项更改时调用。比如 此元素依赖的InheritedElement更新了新的InheritedWidget并且InheritedWidget.updateShouldNotify返回true的时候,framework会调用此方法,通知此元素做出相应的改变。

  • void markNeedsBuild()

描述: 将元素标记为dirty ,并将其添加到全局的widget列表中,以在下一帧中进行重建。 调用时机: setStatereassembledidChangeDependencies时。

  • void rebuild()

调用时机:

  1. BuildOwner.scheduleBuildFor已经标记此元素为dirty时,由BuildOwner调用;
  2. 当元素通过mount首次被构建时会调用;
  3. 当元素的widget通过update已经被改变时,会调用此方法。

关注我们的途径有:

QiShare(简书)
QiShare(掘金)
QiShare(知乎)
QiShare(GitHub)
QiShare(CocoaChina)
QiShare(StackOverflow)
QiShare(微信公众号)

Flutter中的Element(上篇)

iOS 解决 [NSURL fileURLWithPath:] 对 # 的编码问题
Xcode 调整导航目录字体大小b
Swift 5.1 (21) - 泛型
Swift 5.1 (20) - 协议
Swift 5.1 (19) - 扩展
Swift 5.1 (18) - 嵌套类型
Swift 5.1 (17) - 类型转换与模式匹配
浅谈编译过程
深入理解HTTPS
奇舞团安卓团队——aTaller
奇舞周刊