1. Introduction

In the last article, a Widget is configuration data that describes a UI Element, and an Element really represents a screen display Element, an instance generated by a Widget at a location. This article focuses on the main functions of Element.

Using the Widget Tree described in the previous article, the Flutter Framework generates a series of elements that constitute an Element Tree. The main functions of these elements are as follows:

  • Maintenance of the treeElement Tree, according to theWidget TreeChange to updateElement Tree, including: node insertion, update, delete, move, etc.;
  • willWidgetandRenderObjectAssociated with theElement TreeOn.

2. Element classification

As is shown in the figure above,ElementFunctionally, it can be divided into two categories:

  • ComponentElement

Composition class Element. This type of Element is mainly used to combine other more basic elements to get more complex elements. Development often used StatelessWidget and StatefulWidget corresponding Element: StatelessElement and StatefulElement, that is, belong to ComponentElement.

  • RenderObjectElement

The Render class Element, which corresponds to the Renderer Widget, is the core Element of the framework. RenderObjectElement mainly include LeafRenderObjectElement SingleChildRenderObjectElement, and MultiChildRenderObjectElement. Where, the Widget corresponding to LeafRenderObjectElement is LeafRenderObjectWidget, with no child nodes. SingleChildRenderObjectElement corresponding Widget is SingleChildRenderObjectWidget, have a child node; MultiChildRenderObjectElement corresponding Widget is MultiChildRenderObjecWidget, have more child nodes.

3. Element life cycle

An Element has four states: initial, active, Inactive, and defunct. The corresponding meanings are as follows:

  • Initial: Indicates the initial state in which an Element is created.
  • Active: Indicates the active status. At this timeElementtheParentThe file has been mountedElementinsertElement TreeAt the specified slot (Slot),ElementIt can be displayed on the screen at any time.
  • Inactive: indicates the inactive state. whenWidget TreeChange,ElementThe correspondingWidgetChange, as well as due to the old and newWidgettheKeyOr theRunTimeTypeCauses such as mismatchElementAlso removed, so theElementBecomes inactive and is removed from the screen. And will theElementfromElement TreeIf theElementThere is a correspondingRenderObject, and will correspond toRenderObjectfromRender TreeRemoved. However, thisElementThere are still opportunities for reuse, such as throughGlobalKeyReuse.
  • Defunct: The state of failure. If one is in an inactive stateElementThis is called if the current frame animation is not reused at the end of the current frame animationElementThe unmount function willElementChange the state of to defunct and clean up the resources in it.

ElementThe transition relationship between the four states is shown in the figure below:

4. ComponentElement

4.1 Relationship with core elements

As mentioned above,ComponentElementDivided intoStatelessElementandStatefulElementThe twoElementHomologous core elementWidgetAs well asStateThe following figure shows the relationship between.As shown in figure:

  • ComponentElementholdParent ElementandChild Element, thus constituteElement Tree.
  • ComponentElementHold the correspondingWidgetforStatefulElement, which also holds the correspondingStateIn order to achieveElementandWidgetBetween.
  • StateIs beStatefulElementHold, not beStatefulWidgetHold, facilitateStateReuse. In fact,StateandStatefulElementIs one – to – one correspondence only during initializationStatefulElementIs initializedStateAnd bind it toStatefulElementOn.

4.2 Core Process

The core operation processes of an Element include creating, updating, and destroying, which are described below.

  • create

The creation of ComponentElement starts with the parent Widget calling inflateWidget, then mounts the Element to the Element Tree via mount, and recursively creates child nodes.

  • update

The parent Element performs an update to the child node (updateChild), and since the type and Key of the new and old widgets have not changed, the Element’s update is triggered and passed on via performRebuild. The core function, updateChild, will be covered in more detail later.

  • The destruction

An update of the child node (updateChild) performed by the parent or higher Element causes the Element to become inactive, to be added to the inactive list, and to be inactivated in the next frame because the type or Key of the new or old Widget changes, or the new Widget is removed.

4.3 Core Functions

The core methods in ComponentElement are described below.

  • inflateWidget
Element inflateWidget(Widget newWidget, dynamic newSlot) {
  final Key key = newWidget.key;
// Reuse the Element corresponding to GlobalKey
  if (key is GlobalKey) {
    final Element newChild = _retakeInactiveElement(key, newWidget);
    if(newChild ! =null) {
      newChild._activateWithParent(this, newSlot);
      final Element updatedChild = updateChild(newChild, newWidget, newSlot);
      returnupdatedChild; }}// Create an Element and mount it to the Element Tree
  final Element newChild = newWidget.createElement();
  newChild.mount(this, newSlot);
  return newChild;
}
Copy the code

InflateWidget’s main responsibilities are as follows:

  1. To judge newWidgetIs there aGlobalKey, if you haveGlobalKey, fromInactive ElementsIn the listElementAnd reuse it.
  2. No reuseElement, according to the newWidgetCreate the correspondingElementAnd mount it toElement Tree.
  • mount
void mount(Element parent, dynamic newSlot) {
// Update the _parent attribute to add the Element to the Element Tree_parent = parent; _slot = newSlot; _depth = _parent ! =null ? _parent.depth + 1 : 1;
  _active = true;
  if(parent ! =null) // Only assign ownership if the parent is non-null
    _owner = parent.owner;
/ / register GlobalKey
  final Key key = widget.key;
  if (key is GlobalKey) {
    key._register(this);
  }
  _updateInheritance();
}
Copy the code

This method is called when an Element is first inserted into the Element Tree. Its main responsibilities are as follows:

  1. Will giveElementjoinElement TreeTo update the attributes related to the tree, such as _parent, _slot.
  2. If the newWidgetThere areGlobalKeytheElementRegistered inGlobalKey, its role will be analyzed in detail below.
  3. ComponentElementThe mount function calls the _firstBuild function, triggering the childWidgetCreate and update.
  • performRebuild
@override
void performRebuild() {
// Call the build function to generate the sub-widgets
  Widget built;
  built = build();
// Update the child Element with the new child Widget
  _child = updateChild(_child, built, slot);
}
Copy the code

The main responsibilities of performRebuild are as follows:

  1. Call the build function to generate the childWidget.
  2. Update the child with the new child WidgetElement.
  • update
@mustCallSuper
void update(covariant Widget newWidget) {
  _widget = newWidget;
}
Copy the code

The main responsibilities of this function are:

  1. The correspondingWidgetUpdate to newWidget.
  2. inComponentElementThe rebuild function is also called to trigger the pair in the various subclasses ofWidgetThe reconstruction.
  • updateChild
 updateChild@protected
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
  if (newWidget == null) {
// If the new Child Widget is null, return null; If old Child Widget, make it inactive
    if(child ! =null)
      deactivateChild(child);
    return null;
  }
  Element newChild;
  if(child ! =null) {
// The new Child Widget is not null, and the old Child Widget is not null either
    bool hasSameSuperclass = true;
    if (hasSameSuperclass && child.widget == newWidget) {
      if(child.slot ! = newSlot) updateSlotForChild(child, newSlot); newChild = child; }else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)){
//Key is the same as RuntimeType. Update is used
      if(child.slot ! = newSlot) updateSlotForChild(child, newSlot); child.update(newWidget); newChild = child; }else {
// If the Key or RuntimeType is different, make the old Child Widget inactive and use inflateWidget for the new Child WidgetdeactivateChild(child); newChild = inflateWidget(newWidget, newSlot); }}else {
// The new Child Widget will not be null, the old Child Widget will be null, and the new Child Widget will be inflateWidget
    newChild = inflateWidget(newWidget, newSlot);
  }

  return newChild;
}
Copy the code

The main responsibility of this method is either to update the old child Element based on the new child Widget or to get the new child Element. The core logic can be tabulated:

newWidget == null newWidget ! = null
Child == null Returns null Returns a new Element
Child ! = null Remove the old child Element, returning NULL If the Widget can be updated, update the old child Element and return it. Otherwise create a new child Element and return.

The logic is summarized as follows:

  • Returns NULL if newWidget is NULL, and if there are old childrenElementIs removed.
  • If newWidget is not null, oldChildIs null, a new child is createdElement, and return to it.
  • If newWidget is not null, oldChildNot null, old and new childrenWidgettheKeyandRuntimeTypeThe update method is called to update the childElementAnd return to it.
  • If newWidget is not null, oldChildNot null, old and new childrenWidgettheKeyandRuntimeTypeAnd so on are not exactly the same, thenWidget TreeIf there is a change, remove the old childElementAnd create a new childElement, and return to it.

5. RenderObjectElement

5.1 Relationship with core elements

The relationship between RenderObjectElement and the core Widget and RenderObject is shown in the figure below:

As shown in figure:

  • RenderObjectElementholdParent Element, but not necessarily holdChild Element, possibly noneChild ElementIt is possible to hold oneChild Element(Child), it is possible to hold multipleChild Element(Children).
  • RenderObjectElementHold the correspondingWidgetandRenderObjectThat will beWidget,RenderObjectIt’s connected, it’s doneWidget,Element,RenderObjectBetween.

5.2 Core Process

As ComponentElement, RenderObjectElement’s core operation process, create, update, destroy three, the next will be detailed about these three processes.

  • create

RenderObjectElement creates widgets with RenderObjectElement and ComponentElement creates widgets with RenderObjectElement and ComponentElement creates widgets with RenderObjectElement and ComponentElement creates widgets with RenderObjectElement. RenderObjectElement creates and attaches its RenderObject.

  • update

The update process of RenderObjectElement is basically the same as that of ComponentElement. The biggest difference is that the update function of ComponentElement calls the build function to trigger the building of sub-widgets again. RenderObjectElement calls updateRenderObject to update the bound RenderObject.

  • The destruction

The destruction process for RenderObjectElement is basically the same as the destruction process for ComponentElement. It is also the parent or higher node that performs the updateChild (updateChild) operation, causing the Element to be deactivated, added to the inactive list, and deactivated on the next frame. The difference is that when unmount an Element, it calls didUnmountRenderObject to disable the corresponding RenderObject.

5.3 Core Functions

The core methods in RenderObjectElement are described below.

  • inflateWidget

This function is identical to the inflateWidget function for ComponentElement and will not be repeated here.

  • mount
void mount(Element parent, dynamic newSlot) {
  super.mount(parent, newSlot);
  _renderObject = widget.createRenderObject(this);
  attachRenderObject(newSlot);
  _dirty = false;
}
Copy the code

This function is called at the same time as ComponentElement, when the Element is first inserted into the Element Tree. Its main responsibilities are also consistent with the ComponentElement. Only different responsibilities are listed here. The responsibilities are as follows:

  1. Call createRenderObject to createRenderObjectAnd use attachRenderObject toRenderObjectAssociated with theElementOn.
  2. SingleChildRenderObjectElementI’m going to call updateChild to update the child node,MultiChildRenderObjectElementThe inflateWidget for each child node is called to rebuild all the childrenWidget.
  • performRebuild
@override
void performRebuild() {
/ / update the renderObject
  widget.updateRenderObject(this, renderObject);
  _dirty = false;
}
Copy the code

The main responsibilities of performRebuild are as follows:

Call updateRenderObject to update the corresponding RenderObject.

  • update
@override
void update(covariant RenderObjectWidget newWidget) {
  super.update(newWidget);
  widget.updateRenderObject(this, renderObject);
  _dirty = false;
}
Copy the code

The main responsibilities of update are as follows:

  1. The correspondingWidgetUpdate to newWidget.
  2. Call updateRenderObject to update the correspondingRenderObject.
  • updateChild

This function is identical to the inflateWidget function for ComponentElement and will not be repeated here.

  • updateChildren
@protected
List<Element> updateChildren(List<Element> oldChildren, List<Widget> newWidgets, { Set<Element> forgottenChildren }) {
  int newChildrenTop = 0;
  int oldChildrenTop = 0;
  int newChildrenBottom = newWidgets.length - 1;
  int oldChildrenBottom = oldChildren.length - 1;

  final List<Element> newChildren = oldChildren.length == newWidgets.length ?
      oldChildren : List<Element>(newWidgets.length);

  Element previousChild;

// Update the child Element from the top down
  // Update the top of the list.
  while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
    final Element oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenTop]);
    final Widget newWidget = newWidgets[newChildrenTop];
    if (oldChild == null| |! Widget.canUpdate(oldChild.widget, newWidget))break;
    final Element newChild = updateChild(oldChild, newWidget, IndexedSlot<Element>(newChildrenTop, previousChild));
    newChildren[newChildrenTop] = newChild;
    previousChild = newChild;
    newChildrenTop += 1;
    oldChildrenTop += 1;
  }

// Scan the child elements from the bottom up
  // Scan the bottom of the list.
  while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
    final Element oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenBottom]);
    final Widget newWidget = newWidgets[newChildrenBottom];
    if (oldChild == null| |! Widget.canUpdate(oldChild.widget, newWidget))break;
    oldChildrenBottom -= 1;
    newChildrenBottom -= 1;
  }

// Scan the old list of child elements in the middle and save the Widget's Element with the Key to oldKeyChildren. The rest fail
  // Scan the old children in the middle of the list.
  final bool haveOldChildren = oldChildrenTop <= oldChildrenBottom;
  Map<Key, Element> oldKeyedChildren;
  if (haveOldChildren) {
    oldKeyedChildren = <Key, Element> {};while (oldChildrenTop <= oldChildrenBottom) {
      final Element oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenTop]);
      if(oldChild ! =null) {
        if(oldChild.widget.key ! =null)
          oldKeyedChildren[oldChild.widget.key] = oldChild;
        else
          deactivateChild(oldChild);
      }
      oldChildrenTop += 1; }}// Update the Element in oldKeyChildren according to the Widget's Key.
  // Update the middle of the list.
  while (newChildrenTop <= newChildrenBottom) {
    Element oldChild;
    final Widget newWidget = newWidgets[newChildrenTop];
    if (haveOldChildren) {
      final Key key = newWidget.key;
      if(key ! =null) {
        oldChild = oldKeyedChildren[key];
        if(oldChild ! =null) {
          if (Widget.canUpdate(oldChild.widget, newWidget)) {
            // we found a match!
            // remove it from oldKeyedChildren so we don't unsync it later
            oldKeyedChildren.remove(key);
          } else {
            // Not a match, let's pretend we didn't see it for now.
            oldChild = null; }}}}final Element newChild = updateChild(oldChild, newWidget, IndexedSlot<Element>(newChildrenTop, previousChild));
    newChildren[newChildrenTop] = newChild;
    previousChild = newChild;
    newChildrenTop += 1;
  }

  newChildrenBottom = newWidgets.length - 1;
  oldChildrenBottom = oldChildren.length - 1;

  // Update the bottom Element from bottom to top. .
  while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
    final Element oldChild = oldChildren[oldChildrenTop];
    final Widget newWidget = newWidgets[newChildrenTop];
    final Element newChild = updateChild(oldChild, newWidget, IndexedSlot<Element>(newChildrenTop, previousChild));
    newChildren[newChildrenTop] = newChild;
    previousChild = newChild;
    newChildrenTop += 1;
    oldChildrenTop += 1;
  }

// Clear all other remaining elements from the old list of child elements
  // Clean up any of the remaining middle nodes from the old list.
  if (haveOldChildren && oldKeyedChildren.isNotEmpty) {
    for (final Element oldChild in oldKeyedChildren.values) {
      if (forgottenChildren == null || !forgottenChildren.contains(oldChild))
        deactivateChild(oldChild);
    }
  }

  return newChildren;
}
Copy the code

The main responsibilities of this function are as follows:

  1. Reuse the child nodes that can be reused and call updateChild to update the child nodes.
  2. For children that cannot be updated, deactivateChild is called to invalidate the child node.

The steps are as follows:

  1. Update children from the top downElement.
  2. Scan the child from the bottom upElement.
  3. Scan the old onesElementThe middle child in the listElement, saveWidgetThere areKeytheElementOldKeyChildren, the others fail.
  4. For the new childElementList, if its correspondingWidgettheKeyAnd in the oldKeyChildrenKeyAgain, update oldKeyChildrenElement.
  5. Update bottom from bottom to topElement.
  6. Remove the old sonElementAll the rest of the listElement.

6. Summary

This article mainly introduces Element, focusing on its classification, lifecycle, and core functions. The highlights are as follows:

  • maintenanceElement Tree, according to theWidget TreeChange to updateElement Tree, including: node insertion, update, delete, move, etc.; And act as a link between WidgetAs well asRenderObjectAssociated with theElement TreeOn.
  • ElementDivided intoComponentElementandRenderObjectElement, the former is responsible for the combinatorElement, which is responsible for rendering.
  • ElementThe main reuse and update logic of updateChild is implemented by its core function, which is described above.

7. Refer to the documents

The elements of the Flutter Framework

8. Related articles

Framework Analysis of Flutter (1) — Architecture Overview Framework Analysis of Flutter (2) — Framework Analysis of Widget Flutter (4) — RenderObject Flutter Framework Analysis (5) — Widget, Element, Element RenderObject Tree Flutter framework analysis (6) -Constraint Flutter framework Analysis (7) -relayoutBoundary Flutter Framework Analysis (8) -Platform Channel Flutter framework Analysis – Parent Data Flutter framework Analysis -InheritedWidget