Flutterのウィジェット、状態、要素の関係をソースコードを通して理解する

1. フレームワークのソースコード構成

Flutter のウィジェット、状態、要素のソース コードは Framework.dart にあり、ファイル全体では 6693 行あります (バージョン Flutter 3.12.0-14.0.pre.28)。コード全体は、主にキー、ウィジェット、状態、要素を含むいくつかの部分に分割できます。

1.1キー

キーに関するコードは 65 行目から 272 行目までです。ここでのキーには、ObjectKey、GlobalKey、LabeledGlobalKey、および GlobalObjectKey が含まれます。キー システム全体のコードには、Key、LocalKey、UniqueKey、および ValueKey を含む key.dart ファイルも含まれています。Key は GlobalKey と LocalKey の抽象基本クラスです。LabeledGlobalKey および GlobalObjectKey は、GlobalKey の抽象サブクラスです。ObjectKey、UniqueKey、および ValueKey は、LocalKey の 3 つの特定のサブクラスです。

1.2 ウィジェット

ウィジェットのコードは 274 行目から 1922 行目までです。10 個の抽象クラスが含まれています: Widget、StatelessWidget、StatefulWidget、ProxyWidget、ParentDataWidget、InheritedWidget、RenderObjectWidget、LeafRenderObjectWidget、SingleChildRenderObjectWidget、MultiChildRenderObjectWidget。これらのカテゴリは通常、ウィジェットを結合型、レンダリング型、機能型の 3 つのタイプに分類できます。

1.3 状態

州のコードは約 823 です。状態は抽象クラスです。状態はまずハブの役割を果たし、ウィジェットと要素を保持します。状態からコンテキストを取得すると、保持されている要素が返されるだけです。一方、State はウィジェットのライフサイクルを外部に提供します (initState、didUpdateWidget、reassemble、deactivate、activate、dispose、didChangeDependency)。これらのライフサイクル メソッドは、システムによって提供されるフックです。レンダリング要求を積極的に開始したい場合は、State によって提供される setState メソッドを呼び出す必要があります。ビルドでは、ウィジェットのレンダリング方法をシステムに指示します。前者は機会を提供し、後者はコンテンツを提供します。

1.4 ビルドコンテキスト

BuildContext は抽象クラスであり、コードは 2129 ~ 2485 行目にあります。要素はBuildContextを実装します。

1.5 ビルドオーナー

BuildOwner は 2511 ~ 3168 行目にあります。

1.6要素

要素関連のコードは 3259 行目と 6597 行目の間にあります。コードのほぼ半分を見ると、要素がコア部分であることがわかります。

2 ステートレスウィジェットとステートフルウィジェット

ウィジェットは、次の 3 つの重要な要素を含む抽象クラスです。

final Key? key;

Element createElement();

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

canUpdate メソッドの実装に注意する必要があり、このウィジェットを再利用できるかどうかは、ウィジェットの runtimeType とキーによって決まります。runtimeType はウィジェットの種類を示します。異なる種類のウィジェットを再利用することはできません。キーは手動で指定する値であり、同じタイプのウィジェット間に個々の違いが生じる可能性があるため、私たちまたはレンダリング システムがキーを見つけることができます。設定されていない場合はnullになりますが、この時はruntimeTypeを比較するだけです。

まず、ウィジェットに結合されたサブクラス、StatelessWidget と StatefulWidget を紹介します。StatelessWidget によって作成される要素は StatelessElement です。

@override
  StatelessElement createElement() => StatelessElement(this);

StatefulWidget は StatefulElement を作成し、状態も作成できます。

@override
  StatefulElement createElement() => StatefulElement(this);

@protected
  @factory
  State createState();

StatelessWidget と StatefulWidget は依然として抽象クラスであり、それらをサブクラス化する必要があります。ソース コードによれば、StatefulWidget と StatelessWidget は対応する要素を作成するだけであり、それを保持しないことがわかりました。Statefulwidget は状態の作成のみを担当し、状態を保持しません。

3 レンダーオブジェクトウィジェット

RenderObjectWidget には、LeafRenderObjectWidget、SingleChildRenderObjectWidget、および MultiChildRenderObjectWidget という 3 つの抽象サブクラスがあります。それぞれ子を持たない、子が 1 つだけ、および複数の子を持つ 3 つの RenderObjectWidget を表します。もうソース コードを展開することはありませんが、1 つは何も持たず、1 つは子を持ち、最後の 1 つは Children 属性を持っていることは想像できたはずです。

以前の StatelessWidget と比較すると、RenderObjectWidget は独自の一意の RenderObjectElement を返し、RenderObject が 1 つ増えています。

RenderObject createRenderObject(BuildContext context);

  
  @protected
  void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }

 
  @protected
  void didUnmountRenderObject(covariant RenderObject renderObject) { }

RenderObjectWidgetについては後ほどRenderObjectElementのperformRebuildで説明しますが、updateRenderObjectを呼び出して更新します。ここでは updateRenderObject について拡張することはできません。特定のサブクラスの実装を確認する必要があります。 

3.1 カラーボックス

例として、SingleChildRenderObjectWidget の特定のサブクラスである ColoredBox を見てみましょう。

final Color color;

  @override
  RenderObject createRenderObject(BuildContext context) {
    return _RenderColoredBox(color: color);
  }

@override
  void updateRenderObject(BuildContext context, RenderObject renderObject) {
    (renderObject as _RenderColoredBox).color = color;
  }

作成された RenderObject はプライベート クラス _RenderColoredBox であることがわかりました。先ほど説明した updateRenderObject は、ここで新しい色を設定するだけです。

_RenderColoredBox の実装に移りましょう。

class _RenderColoredBox extends RenderProxyBoxWithHitTestBehavior {
  _RenderColoredBox({ required Color color })
    : _color = color,
      super(behavior: HitTestBehavior.opaque);

  /// The fill color for this render object.
  ///
  /// This parameter must not be null.
  Color get color => _color;
  Color _color;
  set color(Color value) {
    if (value == _color) {
      return;
    }
    _color = value;
    markNeedsPaint();
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    // It's tempting to want to optimize out this `drawRect()` call if the
    // color is transparent (alpha==0), but doing so would be incorrect. See
    // https://github.com/flutter/flutter/pull/72526#issuecomment-749185938 for
    // a good description of why.
    if (size > Size.zero) {
      context.canvas.drawRect(offset & size, Paint()..color = color);
    }
    if (child != null) {
      context.paintChild(child!, offset);
    }
  }
}

主なことは、背景色とその色に基づいてキャンバスに子を描画することです。新しい色を設定すると、markNeedsPaint が発生します。markNeedsPaint に関連するコードは RenderObject にあります。

3.2 マークニーズペイント

void markNeedsPaint() {
   
    if (_needsPaint) {
      return;
    }
    _needsPaint = true;
    // If this was not previously a repaint boundary it will not have
    // a layer we can paint from.
    if (isRepaintBoundary && _wasRepaintBoundary) {
     
      if (owner != null) {
        owner!._nodesNeedingPaint.add(this);
        owner!.requestVisualUpdate();
      }
    } else if (parent is RenderObject) {
      parent!.markNeedsPaint();
    } else {
      
      // If we are the root of the render tree and not a repaint boundary
      // then we have to paint ourselves, since nobody else can paint us.
      // We don't add ourselves to _nodesNeedingPaint in this case,
      // because the root is always told to paint regardless.
      //
      // Trees rooted at a RenderView do not go through this
      // code path because RenderViews are repaint boundaries.
      if (owner != null) {
        owner!.requestVisualUpdate();
      }
    }
  }

新しい所有者がここに表示されます: PipelineOwner。markNeedsPaint は、再描画する必要があることをマークします。境界を描画している場合は、描画する必要があるノードのリストに自身を追加します。境界線が描画されていない場合は、親ノードの markNeedsPaint を呼び出します。これは単純なマークであり、リストに追加されています。描画を実行するリアルタイムは、WidgetsBinding.drawFrame の flashPaint です。

void drawFrame() {
  buildOwner!.buildScope(renderViewElement!); // 1.重新构建widget
  super.drawFrame();
  //下面几个是在super.drawFrame()执行的
  pipelineOwner.flushLayout();          // 2.更新布局
  pipelineOwner.flushCompositingBits();     //3.更新“层合成”信息
  pipelineOwner.flushPaint();               // 4.重绘
  if (sendFramesToEngine) {
    renderView.compositeFrame();            // 5. 上屏,将绘制出的bit数据发送给GPU
  }
}

3.3マークニーズレイアウト

上記のコードでは、flushLayout の前にレイアウトが実行されることもわかります。RenderBox ソース コードでは、PipelineOwner が markNeedsLayout を通じて必要なレイアウト ノードをマークし、収集します。

void markNeedsLayout() {

    if (_needsLayout) {

      return;
    }
    if (_relayoutBoundary == null) {
      _needsLayout = true;
      if (parent != null) {
        // _relayoutBoundary is cleaned by an ancestor in RenderObject.layout.
        // Conservatively mark everything dirty until it reaches the closest
        // known relayout boundary.
        markParentNeedsLayout();
      }
      return;
    }
    if (_relayoutBoundary != this) {
      markParentNeedsLayout();
    } else {
      _needsLayout = true;
      if (owner != null) {
        owner!._nodesNeedingLayout.add(this);
        owner!.requestVisualUpdate();
      }
    }
  }

void markParentNeedsLayout() {
    assert(_debugCanPerformMutations);
    _needsLayout = true;
    assert(this.parent != null);
    final RenderObject parent = this.parent!;
    if (!_doingThisLayoutWithCallback) {
      parent.markNeedsLayout();
    } else {
      assert(parent._debugDoingThisLayout);
    }
    assert(parent == this.parent);
  }

レイアウト マーカーと描画マーカーの実装は似ていることがわかりました。どちらも自分自身をマークする必要があり、どちらもレイアウトまたは描画の境界を上方に調べる必要があります。PipelineOwner は最後に、performLayout と markNeedsPaint を呼び出します。

void flushLayout() {
   
    try {
      while (_nodesNeedingLayout.isNotEmpty) {
        final List<RenderObject> dirtyNodes = _nodesNeedingLayout;
        _nodesNeedingLayout = <RenderObject>[];
        dirtyNodes.sort((RenderObject a, RenderObject b) => a.depth - b.depth);
        for (int i = 0; i < dirtyNodes.length; i++) {
          if (_shouldMergeDirtyNodes) {
            _shouldMergeDirtyNodes = false;
            if (_nodesNeedingLayout.isNotEmpty) {
              _nodesNeedingLayout.addAll(dirtyNodes.getRange(i, dirtyNodes.length));
              break;
            }
          }
          final RenderObject node = dirtyNodes[i];
          if (node._needsLayout && node.owner == this) {
            node._layoutWithoutResize();
          }
        }
        // No need to merge dirty nodes generated from processing the last
        // relayout boundary back.
        _shouldMergeDirtyNodes = false;
      }

      
      for (final PipelineOwner child in _children) {
        child.flushLayout();
      }


    } finally {
      _shouldMergeDirtyNodes = false;
      
    }
  }



  void _layoutWithoutResize() {

    RenderObject? debugPreviousActiveLayout;
    try {
      performLayout();
      markNeedsSemanticsUpdate();
    } catch (e, stack) {
      _reportException('performLayout', e, stack);
    }
    _needsLayout = false;
    markNeedsPaint();
  }

PerformLayout メソッドは RenderBox では空であるため、サブクラスによって実装する必要があります。

前の ColoredBox の例では、updateRenderObject で色を変更してもレイアウトは変更されません。次に、RenderPositionedBox のソース コードを見てみましょう。

3.3 RenderPositionedBox

RenderPositionedBox は、Align によって使用される renderObject です。その updateRenderObject 実装を見てみましょう。

void updateRenderObject(BuildContext context, RenderPositionedBox renderObject) {
    renderObject
      ..alignment = alignment
      ..widthFactor = widthFactor
      ..heightFactor = heightFactor
      ..textDirection = Directionality.maybeOf(context);
  }

次に、RenderPositionedBox のアライメント設定の実装に進みます。

set alignment(AlignmentGeometry value) {
    if (_alignment == value) {
      return;
    }
    _alignment = value;
    _markNeedResolution();
  }

void _markNeedResolution() {
    _resolvedAlignment = null;
    markNeedsLayout();
  }

新しい配置を設定すると、markNeedsLayout が呼び出されることがわかりました。

4 3 つの機能ウィジェット: ProxyWidget、ParentDataWidget、InheritedWidget

まだ拡張されていません。詳細については、ソース コードによる Flutter InheritedWidget_Mamong のブログを参照してください - CSDN ブログ

5 ステートフル要素とステートレス要素

StatelessElement コンストラクターにジャンプしましょう。


class StatelessElement extends ComponentElement {
  /// Creates an element that uses the given widget as its configuration.
  StatelessElement(StatelessWidget super.widget);

  @override
  Widget build() => (widget as StatelessWidget).build(this);
}

StatelessElement 自体は、ビルドを通じて子要素に対応するウィジェットを返すことができます。

そして、StatefulElement コンストラクター メソッド:

 /// Creates an element that uses the given widget as its configuration.
  StatefulElement(StatefulWidget widget)
      : _state = widget.createState(),
        super(widget) {
    state._element = this;
    state._widget = widget;
  }

要素を作成するときに、最初にウィジェットの createState を呼び出して状態を作成し、それをポイントすることになることがわかりました。その後、状態は 2 つの手を伸ばし、片方の手でウィジェットを保持し、もう一方の手で要素を保持します。要素には重要なメソッドがあります。

  Widget build() => state.build(this);

ここで、状態ビルドは要素によって保持されるウィジェットの「子」であると考えることができます。実際、StatelessElement にも StatefulWidget にも child という概念はありませんが、対応する要素には child 属性があります。そこで、彼らの関係をこのように見てみましょう。要素ツリーのコンテキスト情報を使用する必要があるため、要素がここに渡されます。

3.1 setState

ここで、私たちの古い友人、状態の setState メソッドの実装を見てください。

  void setState(VoidCallback fn) {
    _element!.markNeedsBuild();
  }


 void markNeedsBuild() {
    if (dirty) {
      return;
    }
    _dirty = true;
    owner!.scheduleBuildFor(this);
  }

  void scheduleBuildFor(Element element) {
    _dirtyElements.add(element);
    element._inDirtyList = true;
  }

void rebuild() {
    performRebuild();
  }

void performRebuild() {
    _dirty = false;
  }

完全なプロセスは次のとおりです。

途中の一部のコードを省略して、performRebuild 実装に直接ジャンプします。基本クラスの Element の実装の場合、単純にダーティとしてマークされます。要素にはレンダリングとコンポーネントの2種類があり、前者はレンダリングに関係し、後者は他の要素を構成するために使用されます。

3.2 リビルドの実行

レンダリングに関連する RenderObjectElement の PerformRebuild では、その renderObject を更新する必要があります。

void _performRebuild() {
    (widget as RenderObjectWidget).updateRenderObject(this, renderObject);
    super.performRebuild(); // clears the "dirty" flag
  }

コンポーネントに関連する ComponentElement の PerformRebuild 実装の場合:

  void performRebuild() {
    Widget? built;
    try {
      built = build();
    } catch (e, stack) {
      
    } finally {
      // We delay marking the element as clean until after calling build() so
      // that attempts to markNeedsBuild() during build() will be ignored.
      super.performRebuild(); // clears the "dirty" flag
    }
    try {
      _child = updateChild(_child, built, slot);
    } catch (e, stack) {
      _child = updateChild(null, built, slot);
    }
  }

ここでの核心は、build メソッドを呼び出して新しいウィジェットを作成し、このウィジェットを使用して子要素を更新することです。前のコードから、statefulElement と statelessElement のビルド実装に違いがあることがわかります。ただし、返されるウィジェットは実際にはその子に対応するウィジェットです。複数のレンダリング サイクル中、子要素は常に存在し、ウィジェットは更新が必要なときに再作成されます。更新された要素は、現在の要素の子として設定されます。アップデート方法については、後ほど説明します。

ComponentElement は、ProxyElement、StatefulElement、および StatelessElement の親クラスです。ただし、PerformRebuild をオーバーライドするのは StatefulElement だけです。さらに、StatefulElement の PerformRebuild 実装について説明します。

void performRebuild() {
    if (_didChangeDependencies) {
      state.didChangeDependencies();
      _didChangeDependencies = false;
    }
    super.performRebuild();
  }

StatefulElement の追加タスクは、依存関係が変更された場合に状態の DidChangeDependency メソッドをトリガーすることです。

3.3 更新子

前の記事に戻って、Element の updateChild 実装を見てみましょう。

Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
    // 如果'newWidget'为null,而'child'不为null,那么我们删除'child',返回null。
    if (newWidget == null) {
      if (child != null) {
        deactivateChild(child);
      }
      return null;
    }

    final Element newChild;
    if (child != null) {
      // 两个widget相同,位置不同更新位置。先更新位置,然后返回child。这里比较的是hashCode
      if (child.widget == newWidget) {
        if (child.slot != newSlot) {
          updateSlotForChild(child, newSlot);
        }
        newChild = child;
      } else if (Widget.canUpdate(child.widget, newWidget)) {
        //两个widget不同,但是可以复用。位置不同则先更新位置。然后用新widget更新element
        if (child.slot != newSlot) {
          updateSlotForChild(child, newSlot);
        }
        child.update(newWidget);
        newChild = child;
      } else {
        // 如果无法更新复用,那么删除原来的child,然后创建一个新的Element并返回。
        deactivateChild(child);
        newChild = inflateWidget(newWidget, newSlot);
      }
    } else {
      // 如果是初次创建,那么创建一个新的Element并返回。
      newChild = inflateWidget(newWidget, newSlot);
    }

    return newChild;
  }

ここでの 2 つの主要なメソッドは、update と inflateWidget です。

3.4アップデート

子の種類によって更新方法が異なります。基本クラスの Element は、古いものを新しいものに置き換えるだけです。

void update(covariant Widget newWidget) {
    _widget = newWidget;
  }

StatelessElement を更新するには、ウィジェットを直接置き換えるだけです。

 @override
  void update(StatelessWidget newWidget) {
    //直接更换widget
    super.update(newWidget);
    assert(widget == newWidget);
    rebuild(force: true);
  }

StatefulElement を更新するには、ウィジェットを置き換えるだけでなく、状態が指すウィジェットも置き換える必要があります。

void update(StatefulWidget newWidget) {
    super.update(newWidget);

    final StatefulWidget oldWidget = state._widget!;
    state._widget = widget as StatefulWidget;
    final Object? debugCheckForReturnedFuture = state.didUpdateWidget(oldWidget) as dynamic;
    rebuild(force: true);
  }

最後に、rebuild を呼び出すことで、自分自身をダーティとしてマークします。

SingleChildRenderObjectElement の場合はその子に対して updateChild を呼び出し、MultiChildRenderObjectElement の場合はその子に対して updateChildren を呼び出します。

void update(SingleChildRenderObjectWidget newWidget) {
    super.update(newWidget);
    _child = updateChild(_child, (widget as SingleChildRenderObjectWidget).child, null);
  }

void update(MultiChildRenderObjectWidget newWidget) {
    super.update(newWidget);
    final MultiChildRenderObjectWidget multiChildRenderObjectWidget = widget as MultiChildRenderObjectWidget;
    _children = updateChildren(_children, multiChildRenderObjectWidget.children, forgottenChildren: _forgottenChildren);
    _forgottenChildren.clear();
  }

ProxyElement の場合、主にウィジェットと通知を更新します。

@override
  void update(ProxyWidget newWidget) {
    final ProxyWidget oldWidget = widget as ProxyWidget;
    //使用新的widget更新持有的widget
    super.update(newWidget);
    //通知其他关联widget自己发生了变化
    updated(oldWidget);
    //标记dirty
    rebuild(force: true);
  }

  @protected
  void updated(covariant ProxyWidget oldWidget) {
    notifyClients(oldWidget);
  }

3.5 アップデート子供

updateChild についてはすでに述べましたが、updateChildren の実装については次のようになります。

List<Element> updateChildren(List<Element> oldChildren, List<Widget> newWidgets, { Set<Element>? forgottenChildren, List<Object?>? slots }) {

    Element? replaceWithNullIfForgotten(Element child) {
      return forgottenChildren != null && forgottenChildren.contains(child) ? null : child;
    }

    Object? slotFor(int newChildIndex, Element? previousChild) {
      return slots != null
          ? slots[newChildIndex]
          : IndexedSlot<Element?>(newChildIndex, previousChild);
    }

    // This attempts to diff the new child list (newWidgets) with
    // the old child list (oldChildren), and produce a new list of elements to
    // be the new list of child elements of this element. The called of this
    // method is expected to update this render object accordingly.

    // The cases it tries to optimize for are:
    //  - the old list is empty
    //  - the lists are identical
    //  - there is an insertion or removal of one or more widgets in
    //    only one place in the list
    // If a widget with a key is in both lists, it will be synced.
    // Widgets without keys might be synced but there is no guarantee.

    // The general approach is to sync the entire new list backwards, as follows:
    // 1. Walk the lists from the top, syncing nodes, until you no longer have
    //    matching nodes.
    // 2. Walk the lists from the bottom, without syncing nodes, until you no
    //    longer have matching nodes. We'll sync these nodes at the end. We
    //    don't sync them now because we want to sync all the nodes in order
    //    from beginning to end.
    // At this point we narrowed the old and new lists to the point
    // where the nodes no longer match.
    // 3. Walk the narrowed part of the old list to get the list of
    //    keys and sync null with non-keyed items.
    // 4. Walk the narrowed part of the new list forwards:
    //     * Sync non-keyed items with null
    //     * Sync keyed items with the source if it exists, else with null.
    // 5. Walk the bottom of the list again, syncing the nodes.
    // 6. Sync null with any items in the list of keys that are still
    //    mounted.

    int newChildrenTop = 0;
    int oldChildrenTop = 0;
    int newChildrenBottom = newWidgets.length - 1;
    int oldChildrenBottom = oldChildren.length - 1;

    final List<Element> newChildren = List<Element>.filled(newWidgets.length, _NullElement.instance);

    Element? previousChild;

   // 从前往后依次对比,相同的更新Element,记录位置,直到不相等时跳出循环。.
    while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
      final Element? oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenTop]);
      final Widget newWidget = newWidgets[newChildrenTop];

      // 注意这里的canUpdate,本例中在没有添加key时返回true。
      // 因此直接执行updateChild,本循环结束返回newChildren。后面因条件不满足都在不执行。
      // 一旦添加key,这里返回false,不同之处就此开始。
      if (oldChild == null || !Widget.canUpdate(oldChild.widget, newWidget)) {
        break;
      }
      final Element newChild = updateChild(oldChild, newWidget, slotFor(newChildrenTop, previousChild))!;

      newChildren[newChildrenTop] = newChild;
      previousChild = newChild;
      newChildrenTop += 1;
      oldChildrenTop += 1;
    }

    // 从后往前依次对比,记录位置,直到不相等时跳出循环。
    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;
    }

    // 至此,就可以得到新旧List中不同Weiget的范围。
    final bool haveOldChildren = oldChildrenTop <= oldChildrenBottom;
    Map<Key, Element>? oldKeyedChildren;
// 如果存在中间范围,扫描旧children,获取所有的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;
      }
    }

    // 更新中间不同的部分,如果新旧key相同就更新一下重新利用,否则新的widget就没有旧的对应,是插入行为
    while (newChildrenTop <= newChildrenBottom) {
      Element? oldChild;
      final Widget newWidget = newWidgets[newChildrenTop];
      if (haveOldChildren) {
        final Key? key = newWidget.key;
        if (key != null) {
          // key不为null,通过key获取对应的旧Element
          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, slotFor(newChildrenTop, previousChild))!;
      
      newChildren[newChildrenTop] = newChild;
      previousChild = newChild;
      newChildrenTop += 1;
    }

    // We've scanned the whole list.
    // 重置

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

    // 将后面相同的Element更新后添加到newChildren,至此形成新的完整的children。
    while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
      final Element oldChild = oldChildren[oldChildrenTop];
     

      final Widget newWidget = newWidgets[newChildrenTop];


      final Element newChild = updateChild(oldChild, newWidget, slotFor(newChildrenTop, previousChild))!;
     

      newChildren[newChildrenTop] = newChild;
      previousChild = newChild;
      newChildrenTop += 1;
      oldChildrenTop += 1;
    }

    // 清除旧列表中多余的带key的Element
    if (haveOldChildren && oldKeyedChildren!.isNotEmpty) {
      for (final Element oldChild in oldKeyedChildren.values) {
        if (forgottenChildren == null || !forgottenChildren.contains(oldChild)) {
          deactivateChild(oldChild);
        }
      }
    }

    return newChildren;
  }

dif アルゴリズムは比較的複雑で、理解するのが難しい場合があります。updateChild と updateChildren の両方が基本クラス要素に実装されていることは言及する価値があります。同一レイヤ差分アルゴリズムでのキーの使用は、パフォーマンス上の理由からではなく、キーをローカルで再利用することはできず、キーを使用して再利用オブジェクトを指定することができますインプレース再利用では、場合によっては問題が発生することがあります。たとえば、ウィジェット自体にいくつかの状態がありますが、他のウィジェットをインプレースで再利用すると、これらの状態は失われます。

3.6 ウィジェットのインフレート

inflateWidget の実装を見てみましょう。これは主に新しい要素を作成し、それらをマウントするために使用されます。ウィジェットに GlobalKey がある場合、ウィジェットは対応する要素の取得を試み、更新後に戻ります。

Element inflateWidget(Widget newWidget, Object? newSlot) {

    try {
      //如果widget带key,并且是GlobalKey,则尝试获取一下对应的element,并用新的widget更新它然后返回
      final Key? key = newWidget.key;
      if (key is GlobalKey) {
        final Element? newChild = _retakeInactiveElement(key, newWidget);
        if (newChild != null) {
          
          newChild._activateWithParent(this, newSlot);
          final Element? updatedChild = updateChild(newChild, newWidget, newSlot);

          return updatedChild!;
        }
      }

    // 这里就调用到了createElement,重新创建了Element
      final Element newChild = newWidget.createElement();
      
      newChild.mount(this, newSlot);

      return newChild;
    } 
  }

3.7マウント

要素の基本クラスのマウントを見てみましょう。

void mount(Element? parent, Object? newSlot) {
    
    _parent = parent;
    _slot = newSlot;
    _lifecycleState = _ElementLifecycle.active;
    _depth = _parent != null ? _parent!.depth + 1 : 1;
    if (parent != null) {
      // Only assign ownership if the parent is non-null. If parent is null
      // (the root node), the owner should have already been assigned.
      // See RootRenderObjectElement.assignOwner().
      _owner = parent.owner;
    }

    final Key? key = widget.key;
    if (key is GlobalKey) {
      owner!._registerGlobalKey(key, this);
    }
    _updateInheritance();
    attachNotificationTree();
  }

マウントとは、親要素のスロットに自分自身を挿入することです。要素がマウントされると、親要素のパワーがそれ自体に設定されることがわかりました。ウィジェットにキーがある場合、Ower はその要素を独自のマップに登録します。

結合された要素のマウントは異なります。上記の基本クラスの動作に加えて、_firstBuild も呼び出されます。

@override
  void mount(Element? parent, Object? newSlot) {
    super.mount(parent, newSlot);
    _firstBuild();

  }

  void _firstBuild() {
    // StatefulElement overrides this to also call state.didChangeDependencies.
    rebuild(); // This eventually calls performRebuild.
  }

StatelessElement の場合、_firstBuild の実装は単純な再構築にすぎません。StatefulElement の場合:

@override
  void _firstBuild() {

    final Object? debugCheckForReturnedFuture = state.initState() as dynamic;
    state.didChangeDependencies();
    super._firstBuild();
  }

state の initState メソッドが _firstBuild で呼び出されたことがわかりました。これは、state に実装したライフサイクル メソッドが、実際にはさまざまな状態に応じて StatefulElement によって呼び出されることを示しています。したがって、他の方法については詳しく説明しません。

3.8 なぜ?

参考記事に質問があるので、この記事の理解を深めるために分析してみましょう。これで、次のコードが完成しました。

import 'dart:math';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  List<Widget> widgets;

  @override
  void initState() {
    super.initState();
    widgets = [
      StatelessColorfulTile(),
      StatelessColorfulTile()
    ];
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Row(
        children: widgets,
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.refresh),
        onPressed: _swapTile,
      ),
    );
  }

  _swapTile() {
    setState(() {
      widgets.insert(1, widgets.removeAt(0));
    });
  }
}

class StatelessColorfulTile extends StatelessWidget {

  final Color _color = Utils.randomColor();

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 150,
      width: 150,
      color: _color,
    );
  }
}

class Utils {
  static Color randomColor() {
    var red = Random.secure().nextInt(255);
    var greed = Random.secure().nextInt(255);
    var blue = Random.secure().nextInt(255);
    return Color.fromARGB(255, red, greed, blue);
  }
}

コードをDartPadに直接コピーして実行し、効果を確認できます。または、ここをクリックして直接実行します

効果は非常にシンプルで、色の付いた 2 つの四角形があるだけで、右下のボタンをクリックすると 2 つの四角形の位置を入れ替えることができます。上の四角は ですStatelessWidgetが、これを何に置き換えればよいでしょうかStatefulWidget?

class StatefulColorfulTile extends StatefulWidget {
  StatefulColorfulTile({Key key}) : super(key: key);

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

class StatefulColorfulTileState extends State<StatefulColorfulTile> {
  final Color _color = Utils.randomColor();

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 150,
      width: 150,
      color: _color,
    );
  }
}

 コードを再度実行すると、ブロックが「交換」されていないことがわかります。何故ですか?結論としては、ウィジェット レベルでは、2 つのウィジェットは確かに交換されていますが、要素は交換されておらず、元の位置にある要素が保持している状態によって、元の色のコンテナが構築されます。

6キー

参考として参照することはできますが、ここでは詳しく説明しません。

7 ビルドオーナー

buildOwner は、フレームワークのコードの背後にある大きなボスです。それが何をするのか見てみましょう。各要素は、そのライフ サイクルを維持するために Owner を指します。

BuildOwner? get owner => _owner;
BuildOwner? _owner;

buildOwner には、globalKey と要素の間の対応関係を維持するマップがあるため、globalKey を使用して対応する要素を見つけることができるのは不思議なことではありません。

  final Map<GlobalKey, Element> _globalKeyRegistry = <GlobalKey, Element>{};

void _registerGlobalKey(GlobalKey key, Element element)
void _unregisterGlobalKey(GlobalKey key, Element element)

buildOwner のもう 1 つの機能は、要素のビルド リストを維持することです。

  final List<Element> _dirtyElements = <Element>[];

void scheduleBuildFor(Element element) {
    if (element._inDirtyList) {
      _dirtyElementsNeedsResorting = true;
      return;
    }
    if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
      _scheduledFlushDirtyElements = true;
      onBuildScheduled!();
    }
    _dirtyElements.add(element);
    element._inDirtyList = true;
    
  }

WidgetsBinding は、WidgetsBinding.drawFrame を通じて buildOwner の buildScope を呼び出します。

void drawFrame() {
  buildOwner!.buildScope(renderViewElement!); // 1.重新构建widget
  super.drawFrame();
  //下面几个是在super.drawFrame()执行的
  pipelineOwner.flushLayout();          // 2.更新布局
  pipelineOwner.flushCompositingBits();     //3.更新“层合成”信息
  pipelineOwner.flushPaint();               // 4.重绘
  if (sendFramesToEngine) {
    renderView.compositeFrame();            // 5. 上屏,将绘制出的bit数据发送给GPU
  }
}

buildScope は、_dirtyElements 内の要素に対して再構築を呼び出します。

void buildScope(Element context, [ VoidCallback? callback ]) {
    if (callback == null && _dirtyElements.isEmpty) {
      return;
    }
    try {
      _scheduledFlushDirtyElements = true;
      if (callback != null) {
        _dirtyElementsNeedsResorting = false;
        try {
          callback();
        }
        
      }
      _dirtyElements.sort(Element._sort);
      _dirtyElementsNeedsResorting = false;
      int dirtyCount = _dirtyElements.length;
      int index = 0;
      while (index < dirtyCount) {
        final Element element = _dirtyElements[index];
        try {
          element.rebuild();
        } 

        index += 1;
        if (dirtyCount < _dirtyElements.length || _dirtyElementsNeedsResorting!) {
          _dirtyElements.sort(Element._sort);
          _dirtyElementsNeedsResorting = false;
          dirtyCount = _dirtyElements.length;
          while (index > 0 && _dirtyElements[index - 1].dirty) {
            // It is possible for previously dirty but inactive widgets to move right in the list.
            // We therefore have to move the index left in the list to account for this.
            // We don't know how many could have moved. However, we do know that the only possible
            // change to the list is that nodes that were previously to the left of the index have
            // now moved to be to the right of the right-most cleaned node, and we do know that
            // all the clean nodes were to the left of the index. So we move the index left
            // until just after the right-most clean node.
            index -= 1;
          }
        }
      }
     
    } finally {
      for (final Element element in _dirtyElements) {
        assert(element._inDirtyList);
        element._inDirtyList = false;
      }
      _dirtyElements.clear();
      _scheduledFlushDirtyElements = false;
      _dirtyElementsNeedsResorting = null;
    }
  }

後続のプロセスは、前の PerformRebuild メソッドに戻ります。

8 対象外のコンテンツ

この記事では、特定のレイアウト ロジックについては触れませんが、これについては後の記事で説明します。

9図

この記事に登場するいくつかの主要なクラスの継承関係は次のとおりです。

参考

1. Flutter で最もよく知られている見知らぬ人について話しましょう—Key_flutter globalkey ソース コード_Weilu のブログ - CSDN ブログ

おすすめ

転載: blog.csdn.net/Mamong/article/details/132307450