はじめに
Flutter の 3 つの tree を理解したい。
概要

https://www.youtube.com/watch?v=zmbmrw07qBc
-
Widget- 軽量(※)
-
immutable → 再利用されない。
build()の度に生成される。 - ライフサイクルが短く、短命
-
Element-
WidgetとRenderObjectを同期して紐付ける(attach)仲介役 - ライフサイクルが長く、長寿命
BuildContextの実装
-
-
RenderObject- mutable → 再利用される
- ライフサイクルが長く、長寿命
(※)「軽量」とはソースコードを見るとわかるが、Element や RenderObject と比較すると難解な処理が無く、コード量も少ないため簡単に読むことができ、文字通り軽いクラス、オブジェクトを意味する。
-
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

https://www.youtube.com/watch?v=0Xn1QhNtPkQ
MyApp をルートとするツリー構造。
void main() => runApp(const MyApp());
- 子ウィジェット(
child)を一つ持つ- 例:
Container
- 例:
- 子ウィジェット(
children)を複数持つ- 例:
Column、Row
- 例:
Widget(abstract class)
@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()
Element と RenderObject が再利用可能かどうか を返す関数。
Widget の runtyme と key を比較することによって判断する。
canUpdate() が false を返す場合、Element / RenderObject は破棄され、再生成が行われる。
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType &&
oldWidget.key == newWidget.key;
}
StatelessWidget(abstract class)
状態を保持しない Widget。
abstract class StatelessWidget extends Widget {
const StatelessWidget({super.key});
@override
StatelessElement createElement() => StatelessElement(this);
Widget build(BuildContext context);
}
build()
build() は Element によって、かなり 頻繁に 呼び出される。
Element はこの時「自分自身」をパラメータに渡す。つまり BuildContext context は Element である。
class StatelessElement {
@override
Widget build() => (widget as StatelessWidget).build(this);
}
StatelessWidget の build() は以下の 3 つのタイミングで呼ばれる。
- Widget tree に挿入される時
-
Element.rebuild()によって、親が子の構成を変更した時 - 依存する
InheritedWidgetが変更された時
アニメーションなどで Widget の構成を頻繁に変更する場合、処理を最適化するためには以下に気を配る必要がある。
- よりシンプルな
Widgetを選択する- 子要素が一つしかないのに
Row、Columnなどを使用しない。AlignやCustomSingleChildLayoutが使用できないかを検討する - 多数の
ContainerやDecorationではなくCustomPaintが使用できないかを検討する
- 子要素が一つしかないのに
- コンストラクタを
constで定義する- 実行時ではなく、コンパイル時にインスタンスが作られ(
newされる)る - 実行時にインスタンスの生成コストが発生しない
- 同一インスタンスの使い回しが可能となる
- 実行時ではなく、コンパイル時にインスタンスが作られ(
-
StatelessWidget→StatefulWidgetへのリファクタを検討する-
Widgetツリー内の共通部分をStatefulWidgetのStateに保持させることで、ElementとRenderObjectが再利用されるようにする - tree 構造の変更時は
GlobalKeyを使用する
-
-
InheritedWidgetに依存する部分のみを、別のStatelessWidgetとして分解する- 頻繁に実行される
build()によって更新されるサブツリーの範囲を最小限に抑えることができる
- 頻繁に実行される
- 再利用する
Widgetは、関数で生成するのではなくclassで宣言する-
StatefulWidgetのsetState()は、対象Widget全体を再構築する - Widgets vs helper methods | Decoding Flutter
-
StatefulWidget(abstract class)
mutable な State を保持する Widget。
StatefulWidget 自身は immutable。
createState() によって State を生成することができる。
State は StatefulWidget のライフサイクル中に変更する可能性がある情報を指し、開発者は setState() によって State の変更を通知する必要がある。
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 を生成する。
つまり、State は StatefulWidget とライフサイクルを共にする。
ただし、GlobalKey を使用した場合、StatefulWidget は 同一の State を保持したまま(dispose() されないまま) Widget tree 内を移動することができる(同じ frame 内で移動した場合に限る)。
Flutter ではこれを 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>
StatefulWidget の存続期間中に変化しうる状態を表現するクラス。
StatefulWidget.createState() によって生成される。
ライフサイクル

https://www.youtube.com/watch?v=FP737UMx7ss&t=53s
StatefulWidget.createState()StatefulElement.mount()initState()didChangeDependencies()didUpdateWidget()build()dispose()
※ setState()
StatefulWidget.createState() / StatefulElement.mount()
生成された State は、Widget.createState() によって生成された後、すぐさま StatefulElement と紐づけられる。
この 「紐付け」とは State が保持する Element への参照 を意味する。
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() で行われる。
State の bool get mounted は Element と紐づいたタイミングで true になり、Element.unmount() 時に false になる。
個人的に
mount()でmounted = trueになり、unmount()でmounted = falseになるのかと思ったが、厳密にはElement.mount()が実行されるタイミングとState.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
mounted な State が紐づく Element は、Element tree 上に存在することが保証される ため、BuildConext を使うことに対する安全性、setState() に対する安全性も保証される。
abstract class State<T extends StatefulWidget> {
StatefulElement? _element;
bool get mounted => _element != null;
BuildContext get context => _element!;
}
State と Element との紐付けは永続的であり、同じ State インスタンスの生存中に他の BuildConext と紐付けを切り替えることはない(ただし StatefulElement が grafting によって Element tree 内を移動することはある。)
また Element.unmount() は State.dispose() 後に実行されるので、dispose() 後の context への参照や、setState() は行ってはならない。
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;
});
}

https://www.youtube.com/watch?v=bzWaMpD1LHY
initState()
mount() 後、フレームワークによって呼ばれる。
AnimationController、 StreamSubscription などのライフサイクルを持つリソースの生成や、Stream<T> のサブスクライブ開始処理などに利用される。
@override
void initState() {
super.initState();
controller = AnimationController(vsync: this);
}
didChangeDependencies()
自分よりも上層の tree にある InheritedWidget が変化したとき、実行されるコールバック。
initState() の直後にも必ず呼ばれる。
createState()initState()didChangeDependencies()didUpdateWidget()build()
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 が依存する場合、State の build() もしくは didChangeDependencies() で依存登録を行うことがある(実際には of() を通じて依存登録を行う。of() を使う時は、取得対象のオブジェクトが、取得処理を実行する階層よりも上層で 注入(DI) されている必要がある。詳細は こちら)。
context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>();
依存性の登録によって BuildContext.dependOnInheritedWidgetOfExactType() を使うと、依存先オブジェクトに変更があったときに didChangeDependencies() が呼ばれるようになる。
前述の通り、initState() 内であれば context へは安全にアクセスすることはできるが、InheritedWidget(Provider / Theme / MediaQuery 等)の動きはまだ不安定とされているため of() によって依存オブジェクトを取得できない可能性があることに注意する。
そのため、依存性の登録はここで行う(ただし build()、didUpdateWidget() 内でも良い。initState() 内は非推奨)。
「build() で実行すれば良いのでは?」「build() と didChangeDependencies() の違いは?」と思うが、build() は依存オブジェクトの更新だけでなく別のトリガーでも実行されるのに対して、didChangeDependencies() は 依存先の更新時にのみ呼ばれる ため、そういった意味では build() よりも 限定された役割を持つ 存在であると言える。

https://www.youtube.com/watch?v=og-vJqLzg2c
また didChangeDependencies() の dependency とは、Widget tree の祖先(tree 上の親子関係:ancestors)のことを指しているのはなく、dependOnInheritedWidgetOfExactType() で依存性を登録したオブジェクト(Theme 等)のことを指している。

https://www.youtube.com/watch?v=og-vJqLzg2c
また、依存オブジェクトを用いたコストの低い(inexpensive)処理は build() 内で実行しても良いが、

https://www.youtube.com/watch?v=og-vJqLzg2c
高コストな処理は didChangeDependencies() 内で行い、計算結果をキャッシュしておくことと良い(of() 自体は低コストである)。

https://www.youtube.com/watch?v=og-vJqLzg2c
didUpdateWidget()
createState()initState()didChangeDependencies()didUpdateWidget()build()
State が 「_widget の変更」に対して反応するためのコールバック関数。
State が持っている Widget への参照(_widget)が更新された場合、didUpdateWidget() が実行される。
親 Widget の build() によって、「同じ runtimeType かつ同じ key(canUpdate() = true)」の新しい子 Widget(StatefulWidget) が作成されると、子 State の _widget プロパティが更新され、その直後に didUpdateWidget() が呼ばれる。
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), // 👈
);
}
- 親の
build()が実行される - 子の
Widgetが新しくなる(トリガー) - 子の
Elementがupdate()を実行する - 子の
StateのdidUpdateWidget()が呼ばれる - 子の
Stateのbuild()が呼ばれる
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 を比較して差分に応じた処理を行うことができる(例:アニメーションを開始する、AnimationController の duration を変えるなど)。
@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() のトリガー
「StatefulWidget の State が持っている Widget への参照(_widget)が更新される」の具体例について。
immutable で短命な Widget と違い、State は mutable で長寿命なオブジェクトである。
Flutter は、Widget インスタンスについては build() の度に新たに生成して「再利用しない」一方で、State(Element も)はツリー内に残して「再利用しよう」とする。
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() を呼ぶことによって自分で再描画を要求できる。
void increment() {
setState(() { _counter++; });
}
build() で実行される処理は Future や await が存在しない、完全に 同期的な処理 である(Synchronous BuildContexts | Decoding Flutter)。
build() は同期処理。
dispose()
Stream<T> を閉じたり、AnimationController を破棄するために利用する。
initState() と対(つい)になる処理を行うことが多い。
dispose() は Element によって呼ばれ、実行後は mounted が false になるため、ここで setState() を実行すると例外が発生する。
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 や別の key の Widget に置き換えられるなどして、State(と Element) が tree から外れると deactivate() が呼ばれる。
@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() を実行するのを忘れないようにする。
@override
void deactivate() {
// リソースの解放など
super.deactivate() // 👈 忘れないよう注意
}
activate()
一旦 tree から外れた State(Element)が、同じフレーム内で別の場所に再挿入された(grafting)とき呼ばれるコールバック関数。
setState()
UI からの再描画要求。
Build pipeline のトリガ。
中で実行しているのは 2 つだけ。
-
setState()に渡されたコールバック関数の実行 -
Element.markNeedsBuild()の呼び出し
void setState(VoidCallback fn) {
final Object? result = fn() as dynamic;
_element!.markNeedsBuild();
}
reassemble()
開発時 のホットリロードに対応するためのコールバック関数。
initState() で準備したデータを再初期化したいときに使うことができる。
通常のリリースビルド版では呼ばれない、開発用のフック。
RenderObjectWidget(abstract class)
StatelessWidget や State のように build() を持たない Widget。

https://www.youtube.com/watch?v=zcJlHVVM84I
build() が無い代わりに、RenderObjectWidget の持つ責務と関わりの深い、以下のメソッドが定義されている。
-
createRenderObject()-
RenderObjectを生成する
-
-
updateRenderObject()-
RenderObjectを更新する
-
abstract class RenderObjectWidget extends Widget {
@factory
RenderObject createRenderObject(BuildContext context);
@protected
void updateRenderObject(BuildContext context, covariant RenderObject renderObject) {}
}
RenderObjectWidget は RenderObject を作り出すことができる 特殊な Widget である。
この意味で、描画機能を持つ Widget は RenderObjectWidget だけであり、「RenderObjectWidget を持たない Widget tree には描画機能が存在しない」とさえ言うことができる(https://www.youtube.com/watch?v=zcJlHVVM84I)。
StatelessWidget と StatefulWidget は createRenderObject() を持たないため、1 対 1 の関係にある Widget tree と Element tree とは異なり、Render tree は RenderObjectWidget / RenderObjectElement にのみ対応する。

https://www.youtube.com/watch?v=zcJlHVVM84I
RenderObject は最も近い親 RenderObject を探し出し、参照を保持することで tree を形成する(child model)。
「描画」と言う一面においては、開発者に馴染みが深い StatelessWidget と StatefulWidget は Widget tree と言う骨格を形成するために存在するクラスであり、言うなれば RenderObjectWidget を作り出すための脇役に過ぎない。
RenderObjectWidget のサブクラスは、大きく以下の 3 種類。
-
LeafRenderObjectWidget- 子を持たない
-
SingleChildRenderObjectWidget- 子を 1 つ持つ
-
MultiChildRenderObjectWidget- 子を複数持つ
中身は child や children の有無だけ。
abstract class LeafRenderObjectWidget extends RenderObjectWidget {
const LeafRenderObjectWidget({super.key});
@override
LeafRenderObjectElement createElement() => LeafRenderObjectElement(this);
}
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()
RenderObjectElement の mount() 内で呼ばれる。
Widget が RenderObject を生成することで、開発者が Widget に与えたプロパティや UI 的な指示情報が RenderObject(※のサブクラス)のコンストラクタを介して Widget → RenderObject へと伝達される。
※ RenderObject は absract class。
updateRenderObject()
RenderObject のフィールドを更新するための関数。

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)
Element の実装を隠蔽するために存在する抽象クラス。
abstract class Element implements BuildContext {
}
build(BuildContext context) の context。Element が Element tree のどこに位置しているかを知るための参照として機能する。
Element tree と Widget tree の形状は完全に一致する ため、Element の位置を知ることは、すなわち、Widget の Widget tree における位置を知ることでもある。
Widget 自身は、自分が tree 上のどこにいるかを知らない。そのため、context を介して tree 情報にアクセスする。

https://www.youtube.com/watch?v=rIaaH87z1-g&t=48s
build(BuildContext context) メソッド内では、BuildContext に定義されたメソッドが context を通じて利用することができる。
下記のメソッドは 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
BuildContext は Element である
Element(abstract)
Widget tree と Render tree の接続役。
mutable であり、破棄(dispose())可能。
ライフサイクルとしては RenderObject 同様に 長寿命 なオブジェクトである。
各ウィジェットの build(BuildContext context) の context は、そのウィジェットに紐づく Element への参照である。
各ウィジェットの build() 内の context オブジェクトは同一オブジェクトではなく、それぞれのウィジェットに紐づいた(Element)オブジェクトである。
技術的側面
Element は BuildContext を implements した抽象クラスである。
Flutter では全てのクラスがインターフェースになることができる。
またサブクラスがスーパークラスを implements した場合、スーパークラスの実装はサブクラスには引き継がれない(implements)。
つまり、サブクラスはスーパークラスで定義された全てのメソッドを実装しなければならない。
Element は BuildContext を implements しているため、BuildContext のメソッドを漏れなく実装する必要がある。
ライフサイクル
enum _ElementLifecycle {
initial,
active,
inactive,
defunct,
}
-
Widget.createElement()-
Elementのインスタンスが生成される -
Element._widgetにcreateElement()を呼んだWidgetが代入される -
Element.mountedがtrueになる
-
-
mount()-
Elementが Element tree 上に挿入される -
Elementが active になる -
active な
Elementは画面に表示される可能性がある
-
-
update()- 親
Widgetの再build()ビルドにより、新しいWidgetインスタンスが同じ位置(同じkey/runtimeType)に配置された場合、Element.update(newWidget)が呼ばれてwidgetプロパティが更新される
- 親
-
deactivate()-
Elementが Element tree から除去される -
Elementが inactive になる -
inactive な
Elementは画面に表示されない
-
-
activate()-
deactivate()されたElementが、同じフレーム内で別の場所に再挿入された(grafting)とき、呼ばれる -
Elementが再び active になる
-
-
unmount()- 最後に
unmount()が呼ばれる -
Elementは defunct になる
- 最後に
createElement()
Element は Widget.createElement() によって生成される。

https://www.youtube.com/watch?v=xiW3ahr4CRU
Element は自分を作り出した Widget への参照を _widget として内部的に保持する。
abstract class StatelessWidget extends Widget {
@override
StatelessElement createElement() => StatelessElement(this); // 👈 Widget は、自分自身への参照を Element に渡す
}
abstract class Element implements BuildContext {
Element(Widget widget) : _widget = widget; // Element は、自分を作った Widget を _widget で保持する
}
Element の mounted は、この _widget が null かどうかを見ているので、この時点で mounted が true になる(ただし、mount() はまだ呼ばれていない)。
abstract class Element implements BuildContext {
Element(Widget widget) : _widget = widget;
Widget? _widget;
@override
bool get mounted => _widget != null;
}
Widget と Element との関係は常に 1 対 1 であり、〇〇Widget のインスタンスに対応する 〇〇Element は必ず存在する。
(※ ただし、const Widget インスタンスは使い回される。)
これは Widget tree / Element tree / Render tree の 3 つの tree のうち、Render tree が持たない特徴である。前述 の通り、RenderObject は RenderObjectElement としか紐づかない。
mount()
生成された Element は、mount() によって 親 Element への参照を _parent に代入する ことによって Element tree に挿入される。
void mount(Element? parent, Object? newSlot) {
_parent = parent;
}
mount() が呼ばれた Element は active になる。
void mount(Element? parent, Object? newSlot) {
_lifecycleState = _ElementLifecycle.active;
}
_owner は親から引き継ぐため、BuildOwner は Element tree 上で共有される存在であることがわかる。
void mount(Element? parent, Object? newSlot) {
_parent = parent;
if (parent != null) {
_owner = parent.owner;
}
}
また 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;
}
}
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 で、可能な限り使い回される長寿命なオブジェクト」である。
親 Widget で State が更新されたことなどを契機として、再 build() が実行された時、「新旧」の子 Widget の runType と key が比較される(Widget.canUpdata())。
この比較が共に一致した場合、フレームワークによって update(covariant Widget newWidget) が呼ばれ、Element オブジェクトは再利用される。
逆に一致しない場合、unmount() が呼ばれ、古くなった Element は Element tree から削除(dispose())される。
abstract class Element implements BuildContext {
@mustCallSuper
void update(covariant Widget newWidget) {
_widget = newWidget;
}
}

https://www.youtube.com/watch?v=zmbmrw07qBc

https://www.youtube.com/watch?v=zmbmrw07qBc

https://www.youtube.com/watch?v=J1_NW5-ULy0
@immutable
abstract class Widget extends DiagnosticableTree {
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType && oldWidget.key == newWidget.key;
}
}
deactivate()
Element が inactive になる。
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 が登録解除される。
void unmount() {
final Key? key = _widget?.key;
if (key is GlobalKey) {
owner!._unregisterGlobalKey(key, this);
}
}
Element と Widget の紐付けが解除される。
void unmount() {
_widget = null;
}
unmount() された Element のライフサイクルは defunct(消滅)になる。
defunct となった Element は、再び Element tree に挿入されることはない。
void unmount() {
_lifecycleState = _ElementLifecycle.defunct;
}
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)過程の一つ。
以下の処理を行う。
-
Elementにdirtyフラグを立てる -
BuildOwner.scheduleBuildFor()に自分自身(StatefulElement)を渡して、次の frame 時のbuild()で自分自身が対象として認識されるように、登録を行う
scheduleBuildFor() は、その名の通り build() の予約 を行う。
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 に登録して処理を終える。
登録された dirty な Element は、その後 BuildScope がまとめて build() される。
rebuild()
-
ComponentElementのmount()で呼ばれる -
BuildOwner.scheduleBuildFor()によってElementがdirtyとして認識された時、同じくBuildOwnerによって呼ばれる-
State.setState()をトリガとして実行される、一連の再描画処理過程の一つ -
BuildScope._tryRebuild()から呼ばれる
-
-
Widgetが更新された時、ComponentELement/StatelessElement/StatefulElement/ProxyElementのupdate()で呼ばれる
内部で porformRebuild() を実行する。
void rebuild({bool force = false}) {
performRebuild();
}
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.
}
}
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;
}
}
}
class StatelessElement extends ComponentElement {
@override
void update(StatelessWidget newWidget) {
super.update(newWidget);
rebuild(force: true);
}
}
class StatefulElement extends ComponentElement {
@override
void update(StatefulWidget newWidget) {
super.update(newWidget);
rebuild(force: true);
}
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 の末端)ではない ConponentElement で build() が実行される。
markNeedsBuild() で ON になった dirty フラグが OFF になる。
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 の位置でのみ使用される Element。RootWidget に対応する。
親 Element への参照 _parent を持たない(null)代わりに _child を持つ。
@override
void mount(Element? parent, Object? newSlot) {
assert(parent == null); // We are the root!
assert(_child != null);
}
他の Element から mount() されない。
ComponentElement(abstract)
Element tree を構成するための Element。
tree 上で leaf(リーフ)ではなく、node(ノード)としての役割を持つ。
抽象メソッド build() が定義されているため、ComponentElement のサブクラス(StatelessElement や StatefulWidget)は 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 によって保持される。
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 が別のインスタンスに更新されることもある。
abstract class Element implements BuildContext {
void _activateWithParent(Element parent, Object? newSlot) {
_parent = parent;
_owner = parent.owner;
}
}
scheduleBuildFor()
State.setState() をトリガとして実行される、一連の再描画処理(Build pipeline)過程の一つ。
dirty な Element を次回の build() の対象として登録するために、Element.markNeedBuild() で呼ばれる。
内部では 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() を実行する。
mixin WidgetsBinding.drawFrame()
void drawFrame() {
try {
if (rootElement != null) {
buildOwner!.buildScope(rootElement!); // 👈
}
super.drawFrame();
buildOwner!.finalizeTree();
}
_needToReportFirstFrame = false;
}
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)を共有するすべての dirty な Element を再 build()する。
一方で、異なるスコープを持つ dirty な Element の 再 build() をスキップする。
「スコープを共有する Element」とは、実装上、同じ BuildScope インスタンスを保持する Element を指す。
Element は _parentBuildScope で BuildScope を保持していて、これは Element.mount() 時に親から引き継ぐ形で代入される。
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)過程の一つ。
- 内部で保持する
dirtyなElementのリスト(List<Element> _dirtyElements) に追加する - 各種フラグを ON する
この後 WidgetsBinding.drawFrame() によって BuildScope.buildScope() が呼ばれる。
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() から呼ばれ、dirty な Element リストを Element.depth 順に並べ替えた後、それぞれの dirty な Element に _tryRebuild(element) を実行する。
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;
}
}
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() を実行する。
void _tryRebuild(Element element) {
element.rebuild();
}
RenderObjectElement
Element tree 上のモードであると同時に 「Render tree 上の RenderObject」を直接保有、管理する唯一の Element。
Element tree と Render tree の接続点となり、RenderObjectWidget と RenderObject を仲介する責務を持つ。

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 タイプに分類される。
-
LeafRenderObjectElement- 子を持たない(leaf)
-
SingleChildRenderObjectElement- 子を 1 つ持つ(child)
-
MultiChildRenderObjectElement- 子を複数持つ(children)
- linked list(※)
ただし 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 と表現する。
class ListItem {
ListItem? firstChild;
ListItem? previousSibling;
ListItem? nextSibling;
ListItem? lastChild;
}
children[row][column]
LeafRenderObjectElementSingleChildRenderObjectElementMultiChildRenderObjectElement
は Widget tree の設計図に対応する Element であるが、Render tree は上記の特殊な child model を持つために、Widget と RenderObject が 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 単位で何万回も実行している。

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

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 以内に描画処理が間に合わないかもと言う事態)。
一方で
class ListItem {
ListItem? firstChild;
ListItem? previousSibling;
ListItem? nextSibling;
ListItem? lastChild;
}
のような linkied list 構造における挿入、削除処理の計算量は O(1) であり、要素数に関わらず計算が 1 回(1 loop)で済む。
child model は高速な描画処理を実現するために、abstract RenderObject の各サブクラスに最適化された形になっている。
slot
RenderObjectElement に対応する RenderObject が Render tree のどの位置に置かれるかを表現する Element のプロパティ。
Element 全てが持つプロパティであるが、RenderObjectElement か否かによって、意味合いが異なる。
-
RenderObjectElement以外のElement- Render tree に紐づかない
StatelessElementやStatefulElementのこと -
slotを親から引き継ぐ(伝達するのみ)
- Render tree に紐づかない
-
RenderObjectElementのサブクラス- Render tree に紐づく
Elementのこと -
slotをそれぞれのRenderObjectElementのサブクラスが child model に応じて 解釈する
- Render tree に紐づく

https://www.youtube.com/watch?v=zcJlHVVM84I
具体的には、特殊な child model を持つ RenderObject に紐づく「RenderObjectElement のサブクラス」は、slot の意味を 独自解釈する ことによって、Widget tree の単純な子構造と Render tree の 複雑な child model を変換する。
Element の slot は、Element tree においては「RenderObjectElement 以外の親 Element」から RenderObjectElement に渡され、RenderObjectElement が自身と紐づく RenderObject に応じて解釈ルールを変えながら理解する。
RenderObjectElement が理解した slot は Render tree の child model に対応しているが、あくまでも Element 側の概念である。
RenderObjectElement のサブクラスが理解するのは「それを RenderObject にどう接続するか」となっている。
Object? _slot は時には int 型のインデックスが代入され、時には二次元配列情報が代入される。
mount()
-
WidgetのcreateRenderObject()を呼び出して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
RenderObject(abstract class)
Widget の情報を paint() に変換し、GPU にて描画処理を実行させる。
Flutter のあらゆるクラスの中で Widget の「位置」と「サイズ」を知っている 唯一の存在。
ライフサイクルとしては Element 同様に 長寿命。
-
Layout
layout()
-
Painting
paint()- 描画を行う
-
Accessibility / semantics
describeSematicsConfiguration()
-
Hit testing
hisTest()
正確には RenderObject 自体はかなり抽象的で、多くの機能、実装を RenderBox のサブクラスが担っている。
Canvas オブジェクトの描画は paint() で書き込んだ命令は Flutter エンジンに渡され、シェーダーが実行し、クリッピングや透過度が解決され、最終的にピクセルバッファーにラスタライズされる。

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

https://docs.flutter.dev/resources/architectural-overview
ContainerRenderObjectMixin<ChildType extends RenderObject, ParentDataType extends ContainerParentDataMixin<ChildType>>(mixin)
RenderObject が子を持つための mixin(child 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);
}
}
}
}
RenderBox(abstract class)
学習中。。。
最も一般的な RenderObject。
layout()

https://www.youtube.com/watch?v=EuG12bebwac&t=8s
Constraints go down, sizes go up, parent sets position.

https://www.youtube.com/watch?v=EuG12bebwac&t=8s
paint()

https://www.youtube.com/watch?v=EuG12bebwac&t=8s
実際には「ピクセルバッファー」?を生成しない。Rect の概念を保存し、その後、Impeller や Skia によって評価され、シェーダーを介して実行される。

https://www.youtube.com/watch?v=EuG12bebwac&t=8s
hitTest()

https://www.youtube.com/watch?v=EuG12bebwac&t=8s
Hit test については こちら。

https://www.youtube.com/watch?v=EuG12bebwac&t=8s
markNeeds*()

https://www.youtube.com/watch?v=EuG12bebwac&t=8s











