1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Flutter 3 つの tree

Last updated at Posted at 2025-12-21

はじめに

Flutter の 3 つの tree を理解したい。

概要

Screenshot 2025-11-23 at 7.29.02.png
https://www.youtube.com/watch?v=zmbmrw07qBc

  • Widget
    • 軽量(※)
    • immutable → 再利用されない。build() の度に生成される。
    • ライフサイクルが短く、短命
  • Element
    • WidgetRenderObject を同期して紐付ける(attach)仲介役
    • ライフサイクルが長く、長寿命
    • BuildContext の実装
  • RenderObject
    • mutable → 再利用される
    • ライフサイクルが長く、長寿命

(※)「軽量」とはソースコードを見るとわかるが、ElementRenderObject と比較すると難解な処理が無く、コード量も少ないため簡単に読むことができ、文字通り軽いクラス、オブジェクトを意味する。

  • Widget tree
    • UI の設計書
    • Element tree / Render tree の構成を提供
  • Element tree
    • ライフサイクルの管理
  • Render tree
    • レイアウト、描画の計算処理

Build パイプライン

State.setState()

Element.markNeedsBuild()

BuildOwner.scheduleBuildFor(element)

BuildScope._scheduleBuildFor()

WidgetsBinding.drawFrame()

BuildScope.buildScope(rootElement)

BuildScope._flushDirtyElements()

BuildScope._tryRebuild(element)

Element.rebuild()

Element.performRebuild()

ComponentElement.build()

StatelessWidget.build() / StatefulWidget.build()

Widget tree

Screenshot 2025-11-20 at 7.07.07.png
https://www.youtube.com/watch?v=0Xn1QhNtPkQ

MyApp をルートとするツリー構造。

Widget tree のルート
void main() => runApp(const MyApp());
  • 子ウィジェット(child)を一つ持つ
    • 例:Container
  • 子ウィジェット(children)を複数持つ
    • 例:ColumnRow

Widgetabstract class

image.png

Widget
@immutable
abstract class Widget extends DiagnosticableTree {
  const Widget({this.key});

  final Key? key;

  Element createElement();

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

canUpdate()

ElementRenderObject が再利用可能かどうか を返す関数。

Widgetruntymekey を比較することによって判断する。

canUpdate()false を返す場合、Element / RenderObject は破棄され、再生成が行われる。

runType と key を比較している
static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType &&
      oldWidget.key == newWidget.key;
}

StatelessWidgetabstract class

image.png

状態を保持しない Widget

StatelessWidget
abstract class StatelessWidget extends Widget {
  const StatelessWidget({super.key});

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

  Widget build(BuildContext context);
}

build()

build()Element によって、かなり 頻繁に 呼び出される。

Element はこの時「自分自身」をパラメータに渡す。つまり BuildContext contextElement である

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

StatelessWidgetbuild() は以下の 3 つのタイミングで呼ばれる。

  1. Widget tree に挿入される時
  2. Element.rebuild() によって、親が子の構成を変更した時
  3. 依存する InheritedWidget が変更された時

アニメーションなどで Widget の構成を頻繁に変更する場合、処理を最適化するためには以下に気を配る必要がある。

  • よりシンプルな Widget を選択する
    • 子要素が一つしかないのに RowColumn などを使用しない。AlignCustomSingleChildLayout が使用できないかを検討する
    • 多数の ContainerDecoration ではなく CustomPaint が使用できないかを検討する
  • コンストラクタを const で定義する
    • 実行時ではなく、コンパイル時にインスタンスが作られ(new される)る
    • 実行時にインスタンスの生成コストが発生しない
    • 同一インスタンスの使い回しが可能となる
  • StatelessWidgetStatefulWidget へのリファクタを検討する
    • Widget ツリー内の共通部分を StatefulWidgetState に保持させることで、ElementRenderObject が再利用されるようにする
    • tree 構造の変更時は GlobalKey を使用する
  • InheritedWidget に依存する部分のみを、別の StatelessWidget として分解する
    • 頻繁に実行される build() によって更新されるサブツリーの範囲を最小限に抑えることができる
  • 再利用する Widget は、関数で生成するのではなく class で宣言する

StatefulWidgetabstract class

image.png

mutableState を保持する Widget

StatefulWidget 自身は immutable

createState() によって State を生成することができる。

StateStatefulWidget のライフサイクル中に変更する可能性がある情報を指し、開発者は setState() によって State の変更を通知する必要がある。

StatefulWidget
abstract class StatefulWidget extends Widget {
  const StatefulWidget({super.key});

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

  @factory
  State createState();
}

createState()

createState()StatefulWidget が Widget tree に挿入されるタイミングで Flutter フレームワークによって呼ばれる。

アプリケーション上で、同じ StatefulWidget のサブクラスを複数で使用した場合でも、それぞれで createState() が実行され、それぞれ固有の State が作り出される。

StatefulWidget が一度 Widget tree から削除され、再び tree に挿入されるような場合、フレームワークは再挿入時に createState() を新たに呼び出し、新しい State を生成する。

つまり、StateStatefulWidget とライフサイクルを共にする

ただし、GlobalKey を使用した場合、StatefulWidget は 同一の State を保持したまま(dispose() されないまま) Widget tree 内を移動することができる(同じ frame 内で移動した場合に限る)。

Flutter ではこれを grafting(接ぎ木)と表現している。

grafting(接ぎ木)
GlobalKey key = GlobalKey();

Widget build(BuildContext context) {
    return Column(
      children: [
        if (flag) MyWidget(key: key),
        if (!flag) Padding(child: MyWidget(key: key)),
      ],
    );
  }
}

State<T extends StatefulWidget>

image.png

StatefulWidget の存続期間中に変化しうる状態を表現するクラス。

StatefulWidget.createState() によって生成される。

ライフサイクル

Screenshot 2025-11-24 at 11.40.42.png
https://www.youtube.com/watch?v=FP737UMx7ss&t=53s

  1. StatefulWidget.createState()
  2. StatefulElement.mount()
  3. initState()
  4. didChangeDependencies()
  5. didUpdateWidget()
  6. build()
  7. dispose()

setState()

StatefulWidget.createState() / StatefulElement.mount()

生成された State は、Widget.createState() によって生成された後、すぐさま StatefulElement と紐づけられる。

この 「紐付け」とは State が保持する Element への参照 を意味する。

Element のコンストラクタ内で State との紐付けが行われる
abstract class StatefulWidget extends Widget {
  /// Creates a [StatefulElement] to manage this widget's location in the tree.
  ///
  /// It is uncommon for subclasses to override this method.
  @override
  StatefulElement createElement() => StatefulElement(this);
}

class StatefulElement extends ComponentElement {
  StatefulElement(StatefulWidget widget)
  : _state = widget.createState(), super(widget) {
    state._element = this; // 👈 mount
  }

  /// 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].
  State<StatefulWidget> get state => _state!;
  State<StatefulWidget>? _state;
}

特定の Element が Element tree 上に組み込まれることは mount と表現され、Element.mount() で行われる。

Statebool get mountedElement と紐づいたタイミングで true になり、Element.unmount() 時に false になる。

個人的に mount()mounted = true になり、unmount()mounted = false になるのかと思ったが、厳密には Element.mount() が実行されるタイミングと State.mounted は完全にイコールではなかった。

mounted
abstract class State<T extends StatefulWidget> {
  StatefulElement? _element;
 
  bool get mounted => _element != null;
}

class StatefulElement extends ComponentElement {
  StatefulElement(StatefulWidget widget)
  : _state = widget.createState(), super(widget) {
    state._element = this; // 👈 State と Element が紐づけられる
  }
  
  @override
  void unmount() {
    super.unmount();
    state.dispose();
    state._element = null; // 👈 State と Element の紐付けが解除される
    // Release resources to reduce the severity of memory leaks caused by
    // defunct, but accidentally retained Elements.
    _state = null;
  }

State.mouted

mountedState が紐づく Element は、Element tree 上に存在することが保証される ため、BuildConext を使うことに対する安全性、setState() に対する安全性も保証される。

mounted
abstract class State<T extends StatefulWidget> {
  StatefulElement? _element;
 
  bool get mounted => _element != null;
  
  BuildContext get context => _element!;
}

StateElement との紐付けは永続的であり、同じ State インスタンスの生存中に他の BuildConext と紐付けを切り替えることはない(ただし StatefulElementgrafting によって Element tree 内を移動することはある。)

また Element.unmount()State.dispose() 後に実行されるので、dispose() 後の context への参照や、setState() は行ってはならない

unmount()
class StatefulElement extends ComponentElement {
  @override
  void unmount() {
    super.unmount();
    state.dispose();
    state._element = null;
    _state = null;
  }
}

State.setState() は「UI に対する更新要求」だが、Element.unmount() 後の setState() は Element tree にすでに存在しない Element に対する UI 更新要求となってしまう。

特に時間のかかる非同期処理などを実行し、処理結果を元に setState() する場合などは、正しく mounted を利用するなどの注意が必要となる。

アンチパターン
Future<void> fetch() async {
  final data = await api.getData();  // 時間のかかる非同期処理(この処理中に別契機で dispose() される可能性あり)
  // setState() 時に mounted をチェックしていない
  setState(() {
    value = data;
  });
}
推奨パターン
Future<void> fetch() async {
  final data = await api.getData();
  
  if (!mounted) return; // 👈

  setState(() {
    value = data;
  });
}

Screenshot 2025-11-30 at 18.45.11.png
https://www.youtube.com/watch?v=bzWaMpD1LHY

initState()

mount() 後、フレームワークによって呼ばれる。

AnimationControllerStreamSubscription などのライフサイクルを持つリソースの生成や、Stream<T> のサブスクライブ開始処理などに利用される。

@override
void initState() {
  super.initState();
  controller = AnimationController(vsync: this);
}

didChangeDependencies()

自分よりも上層の tree にある InheritedWidget が変化したとき、実行されるコールバック。

initState() の直後にも必ず呼ばれる。

  1. createState()
  2. initState()
  3. didChangeDependencies()
  4. didUpdateWidget()
  5. build()
didChangeDependencies()
abstract class State<T extends StatefulWidget> {
  @mustCallSuper
  void didChangeDependencies() {}
}
class StatefulElement extends ComponentElement {
  @override
  void _firstBuild() {
    state.didChangeDependencies();
    super._firstBuild();
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    _didChangeDependencies = true;
  }

  @override
  void performRebuild() {
    if (_didChangeDependencies) {
      state.didChangeDependencies(); // 👈 ここで呼ばれる
      _didChangeDependencies = false;
    }
    super.performRebuild();
  }

特定のオブジェクトに UI が依存する場合、Statebuild() もしくは didChangeDependencies() で依存登録を行うことがある(実際には of() を通じて依存登録を行う。of() を使う時は、取得対象のオブジェクトが、取得処理を実行する階層よりも上層で 注入(DI) されている必要がある。詳細は こちら)。

依存性の登録
context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>();

依存性の登録によって BuildContext.dependOnInheritedWidgetOfExactType() を使うと、依存先オブジェクトに変更があったときに didChangeDependencies() が呼ばれるようになる。

前述の通り、initState() 内であれば context へは安全にアクセスすることはできるが、InheritedWidgetProvider / Theme / MediaQuery 等)の動きはまだ不安定とされているため of() によって依存オブジェクトを取得できない可能性があることに注意する。

そのため、依存性の登録はここで行う(ただし build()didUpdateWidget() 内でも良い。initState() 内は非推奨)。

build() で実行すれば良いのでは?」「build()didChangeDependencies() の違いは?」と思うが、build() は依存オブジェクトの更新だけでなく別のトリガーでも実行されるのに対して、didChangeDependencies()依存先の更新時にのみ呼ばれる ため、そういった意味では build() よりも 限定された役割を持つ 存在であると言える。

Screenshot 2025-11-29 at 21.09.48.png
https://www.youtube.com/watch?v=og-vJqLzg2c

また didChangeDependencies()dependency とは、Widget tree の祖先(tree 上の親子関係:ancestors)のことを指しているのはなく、dependOnInheritedWidgetOfExactType() で依存性を登録したオブジェクト(Theme 等)のことを指している。

Screenshot 2025-11-29 at 21.13.22.png
https://www.youtube.com/watch?v=og-vJqLzg2c

また、依存オブジェクトを用いたコストの低い(inexpensive)処理は build() 内で実行しても良いが、

Screenshot 2025-11-29 at 21.20.17.png
https://www.youtube.com/watch?v=og-vJqLzg2c

高コストな処理は didChangeDependencies() 内で行い、計算結果をキャッシュしておくことと良い(of() 自体は低コストである)。

Screenshot 2025-11-29 at 21.17.42.png
https://www.youtube.com/watch?v=og-vJqLzg2c

didUpdateWidget()

  1. createState()
  2. initState()
  3. didChangeDependencies()
  4. didUpdateWidget()
  5. build()

State が 「_widget の変更」に対して反応するためのコールバック関数。

State が持っている Widget への参照(_widget)が更新された場合、didUpdateWidget() が実行される。

Widgetbuild() によって、「同じ runtimeType かつ同じ keycanUpdate() = true)」の新しい子 WidgetStatefulWidget) が作成されると、子 State_widget プロパティが更新され、その直後に didUpdateWidget() が呼ばれる。

Element.update() は Widget.update() チェックが通った場合のみ実行される
abstract class Widget {
  static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType && oldWidget.key == newWidget.key;
  }
}

abstract class Element implements BuildContext {
  void update(covariant Widget newWidget) {
    // This code is hot when hot reloading, so we try to
    // only call _AssertionError._evaluateAssertion once.
    assert(
      _lifecycleState == _ElementLifecycle.active &&
          newWidget != widget &&
          Widget.canUpdate(widget, newWidget), // 👈
    );
}
  1. 親の build() が実行される
  2. 子の Widget が新しくなる(トリガー
  3. 子の Elementupdate() を実行する
  4. 子の StatedidUpdateWidget() が呼ばれる
  5. 子の Statebuild() が呼ばれる
Element._widget の更新
class StatefulElement extends ComponentElement {
  StatefulElement(StatefulWidget widget)
    : _state = widget.createState(),
      super(widget) {
    state._widget = widget; // 👈 State の _widget が初期化される
  }

  @override
  void update(StatefulWidget newWidget) {
    super.update(newWidget);
    
    final StatefulWidget oldWidget = state._widget!; // 旧 widget
    
    state._widget = widget as StatefulWidget; // 👈 State の _widget が更新される
    
    final Object? debugCheckForReturnedFuture = state.didUpdateWidget(oldWidget) as dynamic;

    rebuild(force: true);
}

didUpdateWidget() では、旧 widget と新 widget を比較して差分に応じた処理を行うことができる(例:アニメーションを開始する、AnimationControllerduration を変えるなど)。

didUpdateWidget() の利用例
@override
void didUpdateWidget(MyWidget oldWidget) {
  super.didUpdateWidget(oldWidget); // 👈 忘れないよう注意

  if (widget.speed != oldWidget.speed) {
    controller.duration = Duration(milliseconds: widget.speed);
  }
}

didUpdateWidget()build() の前に必ず実行されるため、didUpdateWidget() 内で再 build() 要求をするための setState() を呼ぶのは冗長的である。

didUpdateWidget() のトリガー

StatefulWidgetState が持っている Widget への参照(_widget)が更新される」の具体例について。

immutable で短命な Widget と違い、Statemutable で長寿命なオブジェクトである。

Flutter は、Widget インスタンスについては build() の度に新たに生成して「再利用しない」一方で、StateElement も)はツリー内に残して「再利用しよう」とする。

State 側は、この Widget の変化を検知しなければならない。

==== Parent ===================
class Parent extends StatefulWidget {
  @override
  _ParentState createState() => _ParentState();
}

class _ParentState extends State<Parent> {
  int counter = 0;

  @override
  Widget build(BuildContext context) {
    return Child(value: counter); // 👈 setState() で counter を更新すると Child の didUpdateWidget が発火する
  }
}

==== Child ===================
class Child extends StatefulWidget {
  final int value;
  Child({required this.value});

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

class ChildState extends State<Child> {
  late AnimationController _controller;

  @override
  void initState() {
    _controller = AnimationController(vsync: this);
    super.initState();
  }
  
  @override
  void didUpdateWidget(Child oldWidget) {
    super.didUpdateWidget(oldWidget);

    if (widget.value != oldWidget.value) {
      _controller.forward(from: 0); // 👈 値が変わったときにアニメーションさせる、など
    }
  }
  
  @override
  Widget build(BuildContext context) {
    return Text('${widget.value}');
  }
}

build()

UI の「設計図」である Widget を返す関数。

副作用(自身以外に影響を与える処理)を起こすべきではないとされる。可能な限り副作用は initState()didUpdateWidget() で行う。

State 内で setState() を呼ぶことによって自分で再描画を要求できる

setState()
void increment() {
  setState(() { _counter++; });
}

build() で実行される処理は Futureawait が存在しない、完全に 同期的な処理 である(Synchronous BuildContexts | Decoding Flutter)。

build() は同期処理。

dispose()

Stream<T> を閉じたり、AnimationController を破棄するために利用する。

initState() と対(つい)になる処理を行うことが多い。

dispose()Element によって呼ばれ、実行後は mountedfalse になるため、ここで setState() を実行すると例外が発生する。

unmount()
abstract class State<T extends StatefulWidget> {
  StatefulElement? _element;
  bool get mounted => _element != null;
}
  
class StatefulElement extends ComponentElement {
  @override
  void unmount() {
    super.unmount();
    state.dispose();
    state._element = null; // 👈 unmount
    _state = null;
  }
}

diactivate()

Widget によって別の runtimeType や別の keyWidget に置き換えられるなどして、State(と Element) が tree から外れると deactivate() が呼ばれる。

親 Widget が子の型を変更するパターン
@override
Widget build(BuildContext context) {
  if (flag) {
    return WidgetA();
  } else {
    return WidgetB();
  }
}

ただし「ツリーから外れたが、まだ dispose() されるかどうかは未確定」の段階であるため、diactivate() では軽いクリーンアップを行うに留めることが推奨される。

deactivate() の後に、フレームワークが同一 frame 内で別の場所にそのサブツリーを再挿入(grafting)することがあるためである。(activate()diactivate() は grafting を監視できるとも言えそう。)

再挿入されれば build() が呼ばれて再び同じ State が使われる。この時 activate() が呼ばれるため、diactivate で何らかのリソース解放の実施をしている場合、activate() 側で再度必要な処理を行うことができる。

関数の最後で super.deactivate() を実行するのを忘れないようにする。

diactivate()
@override
void deactivate() {
  // リソースの解放など
  super.deactivate() // 👈 忘れないよう注意
}

activate()

一旦 tree から外れた StateElement)が、同じフレーム内で別の場所に再挿入された(grafting)とき呼ばれるコールバック関数。

setState()

UI からの再描画要求

Build pipeline のトリガ。

中で実行しているのは 2 つだけ。

State.setState()
void setState(VoidCallback fn) {
  final Object? result = fn() as dynamic;
  _element!.markNeedsBuild();
}

reassemble()

開発時 のホットリロードに対応するためのコールバック関数。

initState() で準備したデータを再初期化したいときに使うことができる。

通常のリリースビルド版では呼ばれない、開発用のフック。

RenderObjectWidgetabstract class

image.png

StatelessWidgetState のように build() を持たない Widget

Screenshot 2025-11-30 at 18.30.11.png
https://www.youtube.com/watch?v=zcJlHVVM84I

build() が無い代わりに、RenderObjectWidget の持つ責務と関わりの深い、以下のメソッドが定義されている。

  • createRenderObject()
    • RenderObject を生成する
  • updateRenderObject()
    • RenderObject を更新する
RenderObjectWidget
abstract class RenderObjectWidget extends Widget {

  @factory
  RenderObject createRenderObject(BuildContext context);

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

RenderObjectWidgetRenderObject を作り出すことができる 特殊な Widget である。

この意味で、描画機能を持つ WidgetRenderObjectWidget だけであり、「RenderObjectWidget を持たない Widget tree には描画機能が存在しない」とさえ言うことができる(https://www.youtube.com/watch?v=zcJlHVVM84I)。

StatelessWidgetStatefulWidgetcreateRenderObject() を持たないため、1 対 1 の関係にある Widget tree と Element tree とは異なり、Render tree は RenderObjectWidget / RenderObjectElement にのみ対応する。

Screenshot 2025-11-20 at 6.59.22.png
https://www.youtube.com/watch?v=zcJlHVVM84I

RenderObject は最も近い親 RenderObject を探し出し、参照を保持することで tree を形成する(child model)。

「描画」と言う一面においては、開発者に馴染みが深い StatelessWidgetStatefulWidget は Widget tree と言う骨格を形成するために存在するクラスであり、言うなれば RenderObjectWidget を作り出すための脇役に過ぎない

RenderObjectWidget のサブクラスは、大きく以下の 3 種類。

中身は childchildren の有無だけ。

子を持たない
abstract class LeafRenderObjectWidget extends RenderObjectWidget {
  const LeafRenderObjectWidget({super.key});

  @override
  LeafRenderObjectElement createElement() => LeafRenderObjectElement(this);
}
子を 1 つ持つ
abstract class SingleChildRenderObjectWidget extends RenderObjectWidget {
  const SingleChildRenderObjectWidget({super.key, this.child});
  final Widget? child;

  @override
  SingleChildRenderObjectElement createElement() => SingleChildRenderObjectElement(this);
}
子を複数持つ
abstract class MultiChildRenderObjectWidget extends RenderObjectWidget {
  const MultiChildRenderObjectWidget({super.key, this.children = const <Widget>[]});
  final List<Widget> children;

  @override
  MultiChildRenderObjectElement createElement() => MultiChildRenderObjectElement(this);
}

createRenderObject()

RenderObjectElementmount() 内で呼ばれる。

WidgetRenderObject を生成することで、開発者が Widget に与えたプロパティや UI 的な指示情報が RenderObject(※のサブクラス)のコンストラクタを介して WidgetRenderObject へと伝達される。

RenderObjectabsract class

updateRenderObject()

RenderObject のフィールドを更新するための関数。

Screenshot 2025-11-24 at 20.42.52.png
https://www.youtube.com/watch?v=zcJlHVVM84I&t=2s

UI を更新する際、Widget はインスタンスを「破棄」し、新たに生成するのに対して、RenderObject は同じインスタンスのプロパティを「更新」する

RenderObjectElement._performRebuild() によって呼び出される。

abstract class RenderObjectElement extends Element {
  void _performRebuild() {
    (widget as RenderObjectWidget).updateRenderObject(this, renderObject);
  }
}

Element tree

BuildContext(abstract)

image.png

Element の実装を隠蔽するために存在する抽象クラス。

Element は BuildContext を implements している
abstract class Element implements BuildContext {
}

build(BuildContext context)contextElement が Element tree のどこに位置しているかを知るための参照として機能する。

Element tree と Widget tree の形状は完全に一致する ため、Element の位置を知ることは、すなわち、Widget の Widget tree における位置を知ることでもある。

Widget 自身は、自分が tree 上のどこにいるかを知らない。そのため、context を介して tree 情報にアクセスする。

Screenshot 2025-11-15 at 19.23.50.png
https://www.youtube.com/watch?v=rIaaH87z1-g&t=48s

build(BuildContext context) メソッド内では、BuildContext に定義されたメソッドが context を通じて利用することができる。

image.png

下記のメソッドは BuildContext 内では全て宣言のみで、中身は実装されていない。

  • findAncestorWidgetOfExactType<T extends Widget>()
  • findAncestorStateOfType<T extends State>()
  • findRootAncestorStateOfType<T extends State>()
  • findRenderObject()
  • findAncestorRenderObjectOfType<T extends RenderObject>()
  • dependOnInheritedElement()
  • dependOnInheritedWidgetOfExactType<T extends InheritedWidget>()
  • getInheritedWidgetOfExactType<T extends InheritedWidget>()
  • getElementForInheritedWidgetOfExactType<T extends InheritedWidget>()
  • visitAncestorElements()
  • visitChildElements()

Element にはさらに多くのメソッドが定義されているが、これらは開発者から直接呼ばれないように BuildContext によって隠蔽されている。

BuildContext is Element
BuildContextElement である

Element(abstract)

image.png

Widget tree と Render tree の接続役。

mutable であり、破棄(dispose())可能。

ライフサイクルとしては RenderObject 同様に 長寿命 なオブジェクトである。

各ウィジェットの build(BuildContext context)context は、そのウィジェットに紐づく Element への参照である。

各ウィジェットの build() 内の context オブジェクトは同一オブジェクトではなく、それぞれのウィジェットに紐づいた(Element)オブジェクトである。

技術的側面

ElementBuildContextimplements した抽象クラスである。

Flutter では全てのクラスがインターフェースになることができる。

またサブクラスがスーパークラスを implements した場合、スーパークラスの実装はサブクラスには引き継がれない(implements)。

つまり、サブクラスはスーパークラスで定義された全てのメソッドを実装しなければならない。

ElementBuildContextimplements しているため、BuildContext のメソッドを漏れなく実装する必要がある。

ライフサイクル

_ElementLifecycle
enum _ElementLifecycle { 
    initial,
    active,
    inactive,
    defunct,
}
  1. Widget.createElement()
    1. Element のインスタンスが生成される
    2. Element._widgetcreateElement() を呼んだ Widget が代入される
    3. Element.mountedtrue になる
  2. mount()
    1. Element が Element tree 上に挿入される
    2. Elementactive になる
    3. activeElement は画面に表示される可能性がある
  3. update()
    1. Widget の再 build() ビルドにより、新しい Widget インスタンスが同じ位置(同じ key / runtimeType)に配置された場合、Element.update(newWidget) が呼ばれて widget プロパティが更新される
  4. deactivate()
    1. Element が Element tree から除去される
    2. Elementinactive になる
    3. inactiveElement は画面に表示されない
  5. activate()
    1. deactivate() された Element が、同じフレーム内で別の場所に再挿入された(grafting)とき、呼ばれる
    2. Element が再び active になる
  6. unmount()
    1. 最後に unmount() が呼ばれる
    2. Elementdefunct になる

createElement()

ElementWidget.createElement() によって生成される。

Screenshot 2025-11-20 at 7.15.10.png
https://www.youtube.com/watch?v=xiW3ahr4CRU

Element は自分を作り出した Widget への参照を _widget として内部的に保持する。

StatelessWidget
abstract class StatelessWidget extends Widget {

  @override
  StatelessElement createElement() => StatelessElement(this); // 👈 Widget は、自分自身への参照を Element に渡す
}
Widget への参照が Element._widget に保持される
abstract class Element implements BuildContext {
  Element(Widget widget) : _widget = widget; // Element は、自分を作った Widget を _widget で保持する
}

Elementmounted は、この _widgetnull かどうかを見ているので、この時点で mountedtrue になる(ただし、mount() はまだ呼ばれていない)。

Element.mounted
abstract class Element implements BuildContext {
  Element(Widget widget) : _widget = widget;
  
  Widget? _widget;

  @override
  bool get mounted => _widget != null;
}

WidgetElement との関係は常に 1 対 1 であり、〇〇Widget のインスタンスに対応する 〇〇Element は必ず存在する。

(※ ただし、const Widget インスタンスは使い回される。)

これは Widget tree / Element tree / Render tree の 3 つの tree のうち、Render tree が持たない特徴である。前述 の通り、RenderObjectRenderObjectElement としか紐づかない。

mount()

生成された Element は、mount() によって Element への参照を _parent に代入する ことによって Element tree に挿入される。

親への参照を _parent に代入することによって Element tree に挿入される
void mount(Element? parent, Object? newSlot) {
  _parent = parent;
}

mount() が呼ばれた Elementactive になる。

Element は mount() によって active になる
void mount(Element? parent, Object? newSlot) {
  _lifecycleState = _ElementLifecycle.active;
}

_owner は親から引き継ぐため、BuildOwner は Element tree 上で共有される存在であることがわかる。

_owner は親から引き継ぐ
void mount(Element? parent, Object? newSlot) {
  _parent = parent;
  if (parent != null) {
    _owner = parent.owner;
  }
}

また GlobalKeyBuildOwner に登録される。

GlobalKey は BuildOwner に登録される
abstract class Element implements BuildContext {
  void mount(Element? parent, Object? newSlot) {
    if (parent != null) {
      _owner = parent.owner;
    }
    if (key is GlobalKey) {
      owner!._registerGlobalKey(key, this);
    }
  }
}

class BuildOwner {
  void _registerGlobalKey(GlobalKey key, Element element) {
    _globalKeyRegistry[key] = element;
  }
}
mount() の全体
abstract class Element implements BuildContext {
  @mustCallSuper
  void mount(Element? parent, Object? newSlot) {
    _parent = parent;
    _slot = newSlot;
    _lifecycleState = _ElementLifecycle.active;
    _depth = 1 + (_parent?.depth ?? 0);
    if (parent != null) {
      _owner = parent.owner;
      _parentBuildScope = parent.buildScope;
    }
    final Key? key = widget.key;
    if (key is GlobalKey) {
      owner!._registerGlobalKey(key, this);
    }
    _updateInheritance();
    attachNotificationTree();
  }
}

update()

重要な前提として Widget は「immutable で、頻繁に使い捨てられる短命なオブジェクト」であるのに対して、Element は「mutable で、可能な限り使い回される長寿命なオブジェクト」である。

WidgetState が更新されたことなどを契機として、再 build() が実行された時、「新旧」の子 WidgetrunTypekey が比較される(Widget.canUpdata())。

この比較が共に一致した場合、フレームワークによって update(covariant Widget newWidget) が呼ばれ、Element オブジェクトは再利用される

逆に一致しない場合、unmount() が呼ばれ、古くなった Element は Element tree から削除(dispose())される。

_widget が更新される
abstract class Element implements BuildContext {
  @mustCallSuper
  void update(covariant Widget newWidget) {
    _widget = newWidget;
  }
}

Screenshot 2025-11-23 at 7.33.27.png
https://www.youtube.com/watch?v=zmbmrw07qBc

Screenshot 2025-11-23 at 7.31.59.png
https://www.youtube.com/watch?v=zmbmrw07qBc

Screenshot 2025-11-19 at 6.36.53.png
https://www.youtube.com/watch?v=J1_NW5-ULy0

Widget
@immutable
abstract class Widget extends DiagnosticableTree {
  static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType && oldWidget.key == newWidget.key;
  }
}

deactivate()

Elementinactive になる。

Element が deactive になる
abstract class Element implements BuildContext {
  @mustCallSuper
  void deactivate() {
    if (_dependencies?.isNotEmpty ?? false) {
      for (final InheritedElement dependency in _dependencies!) {
        dependency.removeDependent(this);
      }
    }
    _inheritedElements = null;
    _lifecycleState = _ElementLifecycle.inactive; // 👈
  }
}

unmount()

mount()BuildOwner に登録された GlobalKey が登録解除される。

GlobalKey が登録解除される
void unmount() {
  final Key? key = _widget?.key;
  if (key is GlobalKey) {
    owner!._unregisterGlobalKey(key, this);
  }
}

ElementWidget の紐付けが解除される。

_widget に保持されている Widget への参照が破棄される
void unmount() {
  _widget = null;
}

unmount() された Element のライフサイクルは defunct(消滅)になる。

defunct となった Element は、再び Element tree に挿入されることはない。

Element は unmount() によって defunct になる
void unmount() {
  _lifecycleState = _ElementLifecycle.defunct;
}
unmount()
abstract class Element implements BuildContext {
  @mustCallSuper
  void unmount() {
    final Key? key = _widget?.key;
    if (key is GlobalKey) {
      owner!._unregisterGlobalKey(key, this);
    }
    _widget = null;
    _dependencies = null;
    _lifecycleState = _ElementLifecycle.defunct;
  }
}

markNeedsBuild()

State.setState() をトリガとして実行される、一連の再描画処理(Build pipeline)過程の一つ。

以下の処理を行う。

  • Elementdirty フラグを立てる
  • BuildOwner.scheduleBuildFor() に自分自身(StatefulElement)を渡して、次の frame 時の build() で自分自身が対象として認識されるように、登録を行う

scheduleBuildFor() は、その名の通り build() の予約 を行う。

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

Flutter は、Element tree 内のあらゆる箇所で、紐づく State において発生する大量の状態変化をトリガとして build() を実行している。

ここで重要なのは、

build() は 3 つの tree を「再構築」することであり、UI 的には「再描画」処理であるため、tree 上の複数箇所から再描画要求(setState())があった場合でも、都度 frame を実行するのではなく、可能な限り 1 度の frame で計算処理を済ませたい

という点である。

これを実現するため markNeedsBuild() は、呼ばれても、即 build() を実行することはせず、dirty フラグを On した Element 自分自身を BuildOwner に登録して処理を終える。

登録された dirtyElement は、その後 BuildScope がまとめて build() される。

rebuild()

  • ComponentElementmount() で呼ばれる
  • BuildOwner.scheduleBuildFor() によって Elementdirty として認識された時、同じく BuildOwner によって呼ばれる
  • Widget が更新された時、ComponentELement / StatelessElement / StatefulElement / ProxyElementupdate() で呼ばれる

内部で porformRebuild() を実行する。

rebuild()
void rebuild({bool force = false}) {
  performRebuild();
}
ComponentElement の mount() で呼ばれる
abstract class ComponentElement extends Element {
  @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.
  }
}
Element が dirty として認識された時、BuildOwner によって呼ばれる
abstract class Element implements BuildContext {
  bool _inDirtyList = false; // 👈 フラグ OFF
}

class BuildOwner {
  void scheduleBuildFor(Element element) {
    final BuildScope buildScope = element.buildScope;
    buildScope._scheduleBuildFor(element);
  }
}

final class BuildScope {
  BuildScope({this.scheduleRebuild});
  
  final VoidCallback? scheduleRebuild;
  final List<Element> _dirtyElements = <Element>[];
  
  void _scheduleBuildFor(Element element) {
    if (!element._inDirtyList) {
      _dirtyElements.add(element);
      element._inDirtyList = true; // 👈 フラグ ON
    }
    if (!_buildScheduled && !_building) {
      _buildScheduled = true;
      scheduleRebuild?.call();
    }
    if (_dirtyElementsNeedsResorting != null) {
      _dirtyElementsNeedsResorting = true;
    }
  }
}
Widget が更新された時、update() で呼ばれる
class StatelessElement extends ComponentElement {
  @override
  void update(StatelessWidget newWidget) {
    super.update(newWidget);
    rebuild(force: true);
  }
}
Widget が更新された時、update() で呼ばれる
class StatefulElement extends ComponentElement {
  @override
  void update(StatefulWidget newWidget) {
    super.update(newWidget);
    rebuild(force: true);
  }
Widget が更新された時、update() で呼ばれる
abstract class ProxyElement extends ComponentElement {
  @override
  void update(ProxyWidget newWidget) {
    super.update(newWidget);
    rebuild(force: true);
  }
}

performRebuild()

State.setState() をトリガとして実行される、一連の再描画処理(Build pipeline)過程の一つ。

Element tree 上で leaf(tree の末端)ではない ConponentElementbuild() が実行される。

markNeedsBuild() で ON になった dirty フラグが OFF になる

performRebuild()
abstract class Element extends DiagnosticableTree implements BuildContext {
  void performRebuild() {
    _dirty = false;
  }
}

abstract class ComponentElement extends Element {
  void performRebuild() {
    Widget? built;
    try {
      built = build();
    } 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
    }
    _child = updateChild(_child, built, slot);
  }
}

class StatefulElement extends ComponentElement {
  @override
  void performRebuild() {
    if (_didChangeDependencies) {
      state.didChangeDependencies();
      _didChangeDependencies = false;
    }
    super.performRebuild();
  }
}

その他のメンバ

メンバ 説明
Element? _parent Element tree 上の 親 Element
Element tree を構成する骨格
int _depth Element tree における階層の深さ。
mount() 時に parent.depth + 1 が代入される。
Widget? _widget Widget tree 上で自身の Element に対応する Widget への参照。
Widget.createElement() で自身を生成した Widget への参照。
BuildOwner? _owner BuildOwner
Object? _slot RenderObjectElement 参照。
_ElementLifecycle _lifecycleState Element のライフサイクル を参照。
- initial
- active
- inactive
- defunct
bool dirty 次の frame(描画)時に build() が必要かどうか。
Element の初期化時、もしくは markNeedsBuild()true になり、performRebuild() 後に false になる。

RootElement

Element tree の Root の位置でのみ使用される ElementRootWidget に対応する。

Element への参照 _parent を持たない(null)代わりに _child を持つ。

_parent を持たない
@override
void mount(Element? parent, Object? newSlot) {
  assert(parent == null); // We are the root!
  assert(_child != null);
}

他の Element から mount() されない。

ComponentElement(abstract)

image.png

Element tree を構成するための Element

tree 上で leaf(リーフ)ではなく、node(ノード)としての役割を持つ。

抽象メソッド build() が定義されているため、ComponentElement のサブクラス(StatelessElementStatefulWidget)は build() の実装を提供する必要がある。

build()
abstract class ComponentElement extends Element {
  /// Subclasses should override this function to actually call the appropriate
  /// `build` function (e.g., [StatelessWidget.build] or [State.build]) for
  /// their widget.
  @protected
  Widget build();
}

build()

State.setState() をトリガとして実行される、一連の再描画処理(Build pipeline)過程の一つ。

/// Subclasses should override this function to actually call the appropriate
/// `build` function (e.g., [StatelessWidget.build] or [State.build]) for
/// their widget.
@protected
Widget build();

BuildOwner

Element のライフサイクルを管理するオブジェクト。

BuildOwner は通常 WidgetsBinding によって保持され、Widget.build() / RenderObject.layout() / RenderObject.paint() パイプラインは OS によって駆動される。

dirty な Element を管理して 再 build() を実行する役割を持つ

BuildOwner オブジェクトは、基本的には Element tree 全体で共有される(特定の Element だけと紐付いているわけではない)。

正確には Element.mount() 時、親 Element から引き継がれ、_owner によって保持される。

BuildOwner は 親 Element から受け取、内部で保持される
abstract class Element implements BuildContext {
  BuildOwner? _owner;
  
  void mount(Element? parent, Object? newSlot) {
    _parent = parent;
    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; // 👈 親 Element から引き継ぐ
    }
  }
}

Element を tree の別の場所に再挿入する grafting では、Element の親が変わるため、再代入される。

※ 基本的には、アプリ上存在する tree の Root は 1 つだが、テスト実行時など特殊な環境下では Root を起点とした tree が複数存在する場合があり、この場合は複数の BuildOwner が存在するため再代入によって BuildOwner が別のインスタンスに更新されることもある。

BuildOwner は、親 Element から引き継ぐ
abstract class Element implements BuildContext {
  void _activateWithParent(Element parent, Object? newSlot) {
    _parent = parent;
    _owner = parent.owner;
  }
}

scheduleBuildFor()

State.setState() をトリガとして実行される、一連の再描画処理(Build pipeline)過程の一つ。

dirtyElement を次回の build() の対象として登録するために、Element.markNeedBuild() で呼ばれる。

内部では BuildScope._scheduleBuildFor() を呼ぶだけ。

内部で BuildScope の _scheduleBuildFor() を呼ぶ
/// Adds an element to the dirty elements list so that it will be rebuilt
/// when [WidgetsBinding.drawFrame] calls [buildScope].
void scheduleBuildFor(Element element) {
  final BuildScope buildScope = element.buildScope;
  buildScope._scheduleBuildFor(element);
}

buildScope()

WidgetsBinding.drawFrame() によって呼ばれ、内部で BuildScope._flushDirtyElements() を実行する。

WidgetsBinding.drawFrame()
mixin WidgetsBinding.drawFrame()
  void drawFrame() {
    try {
      if (rootElement != null) {
        buildOwner!.buildScope(rootElement!); // 👈
      }
      super.drawFrame();
      buildOwner!.finalizeTree();
    }
    _needToReportFirstFrame = false;
  }
buildScope()
void buildScope(Element context, [VoidCallback? callback]) {
  final BuildScope buildScope = context.buildScope;
  try {
    _scheduledFlushDirtyElements = true;
    buildScope._building = true;
    buildScope._flushDirtyElements(debugBuildRoot: context); // 👈
  } finally {
    buildScope._building = false;
    _scheduledFlushDirtyElements = false;
}

BuildScope(final)

BuildOwner.buildScope() 時に、build() 対象を スコープ という概念によって取捨選択し、対象を決定するクラス。

BuildOwner.buildScope(Element context) は、引数に渡された Elementスコープ(Element.buildScope)を共有するすべての dirtyElement を再 build()する

一方で、異なるスコープを持つ dirtyElement の 再 build() をスキップする。

「スコープを共有する Element」とは、実装上、同じ BuildScope インスタンスを保持する Element を指す。

Element_parentBuildScopeBuildScope を保持していて、これは Element.mount() 時に親から引き継ぐ形で代入される。

Element は親から BuildScope を受け取って、保持する
abstract class Element implements BuildContext {
  void mount(Element? parent, Object? newSlot) {
    _parent = parent;
    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().
      _parentBuildScope = parent.buildScope;
    }
  }
}

_scheduleBuildFor()

State.setState() をトリガとして実行される、一連の再描画処理(Build pipeline)過程の一つ。

  • 内部で保持する dirtyElement のリスト(List<Element> _dirtyElements) に追加する
  • 各種フラグを ON する

この後 WidgetsBinding.drawFrame() によって BuildScope.buildScope() が呼ばれる。

_scheduleBuildFor()
final class BuildScope {
  final List<Element> _dirtyElements = <Element>[];

  void _scheduleBuildFor(Element element) {
    if (!element._inDirtyList) {
      _dirtyElements.add(element);
      element._inDirtyList = true;
    }
    if (!_buildScheduled && !_building) {
      _buildScheduled = true;
    }
    if (_dirtyElementsNeedsResorting != null) {
      _dirtyElementsNeedsResorting = true;
    }
  }
}

_flushDirtyElements()

State.setState() をトリガとして実行される、一連の再描画処理(Build pipeline)過程の一つ。

BuildOwner.buildScope() から呼ばれ、dirtyElement リストを Element.depth 順に並べ替えた後、それぞれの dirtyElement_tryRebuild(element) を実行する。

_flushDirtyElements()
void _flushDirtyElements({required Element debugBuildRoot}) {
  _dirtyElements.sort(Element._sort); // 👈
  _dirtyElementsNeedsResorting = false;
  try {
    for (
      int index = 0;
      index < _dirtyElements.length;
      index = _dirtyElementIndexAfter(index)
    ) {
      final Element element = _dirtyElements[index];
      if (identical(element.buildScope, this)) {
        _tryRebuild(element);
      }
    }
  } finally {
    for (final Element element in _dirtyElements) {
      if (identical(element.buildScope, this)) {
        element._inDirtyList = false;
      }
    }
    _dirtyElements.clear();
    _dirtyElementsNeedsResorting = null;
    _buildScheduled = false;
  }
}
Element._sort()
abstract class Element extends DiagnosticableTree implements BuildContext {
  /// Returns result < 0 when [a] < [b], result == 0 when [a] == [b], result > 0
  /// when [a] > [b].
  static int _sort(Element a, Element b) {
    final int diff = a.depth - b.depth;
    // If depths are not equal, return the difference.
    if (diff != 0) {
      return diff;
    }
    // If the `dirty` values are not equal, sort with non-dirty elements being
    // less than dirty elements.
    final bool isBDirty = b.dirty;
    if (a.dirty != isBDirty) {
      return isBDirty ? -1 : 1;
    }
    // Otherwise, `depth`s and `dirty`s are equal.
    return 0;
  }
}

_tryRebuild()

State.setState() をトリガとして実行される、一連の再描画処理(Build pipeline)過程の一つ。

_flushDirtyElements() から呼ばれ、内部で Element.build() を実行する。

_tryRebuild()
void _tryRebuild(Element element) {
  element.rebuild();
}

RenderObjectElement

Element tree 上のモードであると同時に 「Render tree 上の RenderObject」を直接保有、管理する唯一の Element

Element tree と Render tree の接続点となり、RenderObjectWidgetRenderObject を仲介する責務を持つ。

Screenshot 2025-11-20 at 6.59.22.png
https://www.youtube.com/watch?v=zcJlHVVM84I

Render tree の構造は Widget tree / Element tree と明らかに異なっていて、特に「子」の持ち方が複雑になっている。

Flutter 公式では、これは child model と表現される。

Sometimes, however, a render object's child model is more complicated.
ただし、RenderObject の child model がより複雑な場合もあります。
https://api.flutter.dev/flutter/widgets/RenderObjectElement-class.html

child model

RenderObject が「子をどう持つか」という構造モデル。

RenderObjectWidget 同様に、大きく 3 タイプに分類される。

ただし RenderObject には、これら 3 つでは表現できない複雑な child model が存在する

Sometimes, however, a render object's child model is more complicated. Maybe it has a two-dimensional array of children. Maybe it constructs children on demand. Maybe it features multiple lists.
しかし、RenderObject の child model がより複雑な場合もあります。例えば、子要素の2次元配列(※)を持つ場合や、必要に応じて子要素を構築する場合(※ on demand)、複数のリストを持つ場合などです。

※ linked list(ContainerRenderObjectMixin 参照)や二次元配列は以下のようなものを指す。また、リストの要素を「必要に迫られてから生成する」方式を、Flutter 公式では on demand と表現する。

linked list
class ListItem {
  ListItem? firstChild;
  ListItem? previousSibling;
  ListItem? nextSibling;
  ListItem? lastChild;
}
二次元配列
children[row][column]
  • LeafRenderObjectElement
  • SingleChildRenderObjectElement
  • MultiChildRenderObjectElement

は Widget tree の設計図に対応する Element であるが、Render tree は上記の特殊な child model を持つために、WidgetRenderObject が 1 対 1 で紐づかない

RenderObject の child model が複雑な場合、Widget tree の単純な子構造をそのまま Render tree に反映できないため、Element(特に RenderObjectElement)による翻訳が必要になる。

ここでの翻訳作業は、「設計図」であるところの Widget tree を実際に描画を担当する Render tree 構造に変換する ことを指す。

実装上、この翻訳作業は slot と言う概念と、RenderObjectElement を継承した「サブクラス」よって実現される。

child model が必要な理由

RenderObject は Engine から提供される描画 API (layout()paint()hitTesting())を frame 単位で何万回も実行している。

archdiagram.png
https://docs.flutter.dev/resources/architectural-overview

Screenshot 2025-12-21 at 11.50.59.png
https://docs.flutter.dev/resources/architectural-overview

Dart の List は、要素の挿入や削除処理の計算量が O(n) であり、要素数 n に対して計算量が比例する。

60 fps(frame per seconds)における frame 処理は 1 s / 60 回 = 16 ms 毎に実行されるため、リスト構造の要素を一つ一つ計算する際の計算量に「O(n)」が一つでも含まれていた場合、これは「要素数 n に依存する致命的な遅延」を意味する(要素が多いリストに対しては 16 ms 以内に描画処理が間に合わないかもと言う事態)。

一方で

linked list
class ListItem {
  ListItem? firstChild;
  ListItem? previousSibling;
  ListItem? nextSibling;
  ListItem? lastChild;
}

のような linkied list 構造における挿入、削除処理の計算量は O(1) であり、要素数に関わらず計算が 1 回(1 loop)で済む。

child model は高速な描画処理を実現するために、abstract RenderObject の各サブクラスに最適化された形になっている。

slot

image.png

RenderObjectElement に対応する RenderObject が Render tree のどの位置に置かれるかを表現する Element のプロパティ。

Element 全てが持つプロパティであるが、RenderObjectElement か否かによって、意味合いが異なる。

  • RenderObjectElement 以外の Element
    • Render tree に紐づかない StatelessElementStatefulElement のこと
    • slot を親から引き継ぐ(伝達するのみ
  • RenderObjectElement のサブクラス
    • Render tree に紐づく Element のこと
    • slot をそれぞれの RenderObjectElement のサブクラスが child model に応じて 解釈する

Screenshot 2025-11-20 at 6.59.22.png
https://www.youtube.com/watch?v=zcJlHVVM84I

具体的には、特殊な child model を持つ RenderObject に紐づく「RenderObjectElement のサブクラス」は、slot の意味を 独自解釈する ことによって、Widget tree の単純な子構造と Render tree の 複雑な child model を変換する。

Elementslot は、Element tree においては「RenderObjectElement 以外の親 Element」から RenderObjectElement に渡され、RenderObjectElement が自身と紐づく RenderObject に応じて解釈ルールを変えながら理解する。

RenderObjectElement が理解した slot は Render tree の child model に対応しているが、あくまでも Element 側の概念である。

RenderObjectElement のサブクラスが理解するのは「それを RenderObject にどう接続するか」となっている。

Object? _slot は時には int 型のインデックスが代入され、時には二次元配列情報が代入される。

mount()

  • WidgetcreateRenderObject() を呼び出して RenderObject を生成する
  • attachRenderObject() によって Render tree に RenderObject を挿入する
@override
void mount(Element? parent, Object? newSlot) {
  super.mount(parent, newSlot);
  _renderObject = (widget as RenderObjectWidget).createRenderObject(this);
  attachRenderObject(newSlot);
  super.performRebuild(); // clears the "dirty" flag
}

attachRenderObject()

abstract class RenderObjectElement extends Element {
  @override
  void attachRenderObject(Object? newSlot) {
    _slot = newSlot;
    _ancestorRenderObjectElement?.insertRenderObjectChild(
      renderObject,
      newSlot,
    );
    final List<ParentDataElement<ParentData>> parentDataElements =
        _findAncestorParentDataElements();
    for (final ParentDataElement<ParentData> parentDataElement
        in parentDataElements) {
      _updateParentData(
        parentDataElement.widget as ParentDataWidget<ParentData>,
      );
    }
  }

Render tree

HitTestTarget

image.png

RenderObjectabstract class

image.png

Widget の情報を paint() に変換し、GPU にて描画処理を実行させる。

Flutter のあらゆるクラスの中で Widget の「位置」と「サイズ」を知っている 唯一の存在。

ライフサイクルとしては Element 同様に 長寿命

  • Layout
    • layout()
  • Painting
    • paint()
    • 描画を行う
  • Accessibility / semantics
    • describeSematicsConfiguration()
  • Hit testing
    • hisTest()

正確には RenderObject 自体はかなり抽象的で、多くの機能、実装を RenderBox のサブクラスが担っている。

Canvas オブジェクトの描画は paint() で書き込んだ命令は Flutter エンジンに渡され、シェーダーが実行し、クリッピングや透過度が解決され、最終的にピクセルバッファーにラスタライズされる。

archdiagram.png
https://docs.flutter.dev/resources/architectural-overview

Screenshot 2025-12-21 at 11.50.59.png
https://docs.flutter.dev/resources/architectural-overview

ContainerRenderObjectMixin<ChildType extends RenderObject, ParentDataType extends ContainerParentDataMixin<ChildType>>mixin

RenderObject が子を持つための mixinchild model)。

mixin ContainerRenderObjectMixin<
  ChildType extends RenderObject,
  ParentDataType extends ContainerParentDataMixin<ChildType>> on RenderObject {

  int _childCount = 0;
  ChildType? _firstChild;
  ChildType? _lastChild;

  void _insertIntoChildList(ChildType child, {ChildType? after}) {
    final ParentDataType childParentData = child.parentData! as ParentDataType;
    _childCount += 1;
    if (after == null) {
      // insert at the start (_firstChild)
      childParentData.nextSibling = _firstChild;
      if (_firstChild != null) {
        final ParentDataType firstChildParentData = _firstChild!.parentData! as ParentDataType;
        firstChildParentData.previousSibling = child;
      }
      _firstChild = child;
      _lastChild ??= child;
    } else {
      final ParentDataType afterParentData = after.parentData! as ParentDataType;
      if (afterParentData.nextSibling == null) {
        // insert at the end (_lastChild); we'll end up with two or more children
        childParentData.previousSibling = after;
        afterParentData.nextSibling = child;
        _lastChild = child;
      } else {
        // insert in the middle; we'll end up with three or more children
        // set up links from child to siblings
        childParentData.nextSibling = afterParentData.nextSibling;
        childParentData.previousSibling = after;
        // set up links from siblings to child
        final ParentDataType childPreviousSiblingParentData =
            childParentData.previousSibling!.parentData! as ParentDataType;
        final ParentDataType childNextSiblingParentData =
            childParentData.nextSibling!.parentData! as ParentDataType;
        childPreviousSiblingParentData.nextSibling = child;
        childNextSiblingParentData.previousSibling = child;
        assert(afterParentData.nextSibling == child);
      }
    }
  }
}

RenderBoxabstract class

学習中。。。

最も一般的な RenderObject

layout()

Screenshot 2025-11-24 at 20.56.26.png
https://www.youtube.com/watch?v=EuG12bebwac&t=8s

Constraints go down, sizes go up, parent sets position.

Screenshot 2025-11-24 at 21.01.20.png
https://www.youtube.com/watch?v=EuG12bebwac&t=8s

paint()

Screenshot 2025-11-24 at 20.57.13.png
https://www.youtube.com/watch?v=EuG12bebwac&t=8s

実際には「ピクセルバッファー」?を生成しない。Rect の概念を保存し、その後、Impeller や Skia によって評価され、シェーダーを介して実行される。

Screenshot 2025-11-24 at 21.01.59.png
https://www.youtube.com/watch?v=EuG12bebwac&t=8s

hitTest()

Screenshot 2025-11-24 at 21.04.46.png
https://www.youtube.com/watch?v=EuG12bebwac&t=8s

Hit test については こちら

Screenshot 2025-11-25 at 6.56.06.png
https://www.youtube.com/watch?v=EuG12bebwac&t=8s

markNeeds*()

Screenshot 2025-11-25 at 7.00.24.png
https://www.youtube.com/watch?v=EuG12bebwac&t=8s

参考

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?