RenderObject Tree はFlutterの階層構造の第3レベルであり、画面上にインターフェースを実際にレンダリングする責任を担います。このツリーの各ノードは、サイズ計算(layout)と描画(painting)を実行し、ウィジェットの構成をピクセルに変換します。Widget Treeとは異なり、RenderObject Treeは一度作成され、ジオメトリーや外観が変わった場合にのみ更新されます。Flutter API Reference, 2025によると、RenderObject Treeの効率は、アニメーションの滑らかさとアプリケーションのレスポンス性を直接決定します。
まとめ
RenderObject Tree は、メモリ上のインターフェースの実際の表現となるレンダリングオブジェクトの階層構造です。各RenderObjectは、自分のサイズ、画面上の位置、および自分を描画する方法を知っています。ウィジェット(軽量で秒間に何百回でも再生成できる)とは異なり、RenderObjectは重いオブジェクトで、グラフィックスパイプラインに直接アクセスできます。
ウィジェットからピクセルへの道は3つのステージを経ます: Widget Treeは画面上に何を表示すべきかを説明し、Element Treeはライフサイクルを管理し、RenderObject Tree が実際の仕事を行います。各RenderObjectWidget(Padding、Transform、CustomPaintなど)は、レンダリングツリーに追加されるRenderObjectを作成します。RenderBoxは、標準ウィジェットの99%で使用される最も一般的なRenderObjectのタイプです。
RenderObject は、layoutとpaintのためのインターフェースを定義する抽象クラスです。これには、親RenderObjectと子オブジェクトへの参照、および抽象メソッド(performLayout、paint、hitTest)が含まれます。各具体的なRenderObjectは、その動作に応じてこれらのメソッドを実装します: RenderFlexは子要素間に空閒を分配し、RenderImageは画像を表示し、RenderParagraphはテキストをレンダリングします。
abstract class RenderObject {
RenderObject? parent;
Constraints constraints;
ParentData? parentData;
bool _needsLayout = true;
bool _needsPaint = true;
void performLayout();
void paint(PaintingContext context, Offset offset);
}
この簡単な構造で、RenderObject には_needsLayoutと_needsPaintのフラグがあり、オブジェクトが更新を必要としていることを示します。Widget Treeが変わると、Element Treeは対応するRenderObjectをlayoutまたはpaintのために「ダーティ」としてマークし、次のフレームでFlutterは必要な操作のみを実行します。
各RenderObject は2つの主なフェーズを実行します: layout(サイズと位置の決定)とpaint(描画)。これらのフェーズは厳密な順序で実行されます: まずツリー全体のためのlayout、その後paintです。layoutが変わっていなければpaintフェーズをスキップできるため、GPUリソースを節約できます。
layout中、FlutterはRenderObject Treeを通じて上から下へ制約(constraints)を伝えます。各親要素は、子要素の最小および最大幅と高さを設定します。子要素は、これらの制約内で自分のサイズを計算し、結果を親要素に返します。このプロセスは「下りパス(制約を下へ伝える)」および「上りパス(サイズを上へ伝える)」と呼ばれます。
layoutが完了すると、Flutterはpaintを実行します — 各RenderObjectを描画します。親要素はPaintingContextを作成し、それを子オブジェクトに渡し、オフセット(offset)を指定します。各RenderObjectは、グラフィックスプリミティブ(矩形、円、テキスト、画像、変換)を使用してCanvas上に自分を描画します。Canvasは直接SkiaまたはImpellerとインタラクションします。
Flutterは作業を最小限にするために「ダーティ」ノードメカニズムを使用します。あるRenderObjectのサイズだけが変わった場合、Flutterはツリー全体のlayoutを再計算せずに、変化したノードと、固定サイズの最も近いRenderBoxまでの可能な先祖をマークします。同様に、外観が変わった場合は、layoutをやり直すことなくpaintだけがマークされます。
Flutterは、様々な任務に対して複数のカテゴリのRenderObjectを提供しています: 標準矩形要素にはRenderBox、スクロール可能エリアにはRenderSliver、CustomPainterを通じた非標準レンダリングにはカスタムRenderObjectです。各タイプは、レンダリングツリー内での役割に応じて最適化されています。
RenderBox は矩形インターフェース要素の基本クラスです。そのサブタイプには、RenderPadding(パディングを追加)、RenderTransform(変換を適用)、RenderFlex(RowとColumnを実装)、RenderStack(要素を重ねる)、RenderImage(画像を表示)、RenderParagraph(テキストをレンダリング)があります。各サブタイプは、自分のロジックに応じてperformLayoutとpaintをオーバライドします。
RenderSliver は、スクロール可能エリアを扱うためのRenderObjectのタイプです。RenderBoxとは異なり、SliverはSliverConstraintsを使用します。これにはビューポート情報が含まれます。RenderSliverListとRenderSliverGridは、可視エリアのアイテムにのみRenderObjectを作成し、数百万項のリストを処理できます。
非標準グラフィックスには、CustomPaintとCustomPainterを使用します。CustomPaintはRenderCustomPaintを作成し、CustomPainterのpaintメソッドを呼び出します。これにより、Canvasを完全に制御して、任意の形、グラフ、アニメーションを描画できます。Flutterチームによると、複雑なベクターグラフィックスには、CustomPainterはネスト標準ウィジェットより効率的です。
class CirclePainter extends CustomPainter {
final Color color;
CirclePainter({required this.color});
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()..color = color;
canvas.drawCircle(
Offset(size.width / 2, size.height / 2),
size.width / 3,
paint,
);
}
@override
bool shouldRepaint(CirclePainter oldDelegate) =>
oldDelegate.color != color;
}
この例では、CirclePainter がCanvas上に円を描画します。shouldRepaintメソッドは、色が変わった場合にのみtrueを返し、不要な再描画を防ぎます。CustomPainterはできるだけ軽くすべきです — 重い計算はすべてpaintメソッドの外で実行してください。
RenderObject Tree は、RenderObjectWidgetメカニズムを通じてElement Treeから作成されます。各RenderObjectWidget(Padding、Transform、CustomPaint)はRenderObjectElementを作成し、これが対応するRenderObjectを作成および管理します。要素は中立者として機能します: ウィジェットからRenderObjectへ構成を伝え、変更をRenderObjectに通知します。
RenderObjectElementがマウントされると、そのウィジェットのcreateRenderObjectメソッドを呼び出します。ウィジェットはRenderObjectのインスタンスを作成し、それを要素に返します。要素は、親RenderObjectのinsertChildLayoutメソッドを呼び出してRenderObjectをRenderObject Treeに挿入します。このプロセスは最初のマウントのみで起こります — その後の更新では、要素は既存のRenderObjectのパラメータを簡単に更新するだけです。
ウィジェットの構成が変わった場合(例えば、padding値が変わった場合)、要素はupdateRenderObjectメソッドを呼び出し、新しい構成を既存のRenderObjectに伝えます。RenderObjectは、layoutまたはpaintのために自己を「ダーティ」とマークし、次のフレームでフレームワークが必要な更新を実行します。
要素がアンマウントされると、unmountメソッドが呼ばれ、RenderObjectをRenderObject Treeから削除し、リソースを解放します。親からの切離にはRenderObject.removeが呼ばれ、その後オブジェクトはガーベジコレクションの対象となります。Flutterは、対応する要素なしにRenderObjectがツリー内に残ることがないことを保証します。
RepaintBoundary は、そのコンテンツをレンダリングするための独立したレイヤーを作成するウィジェットです。RepaintBoundary内のコンテンツが変わった場合、そのレイヤーだけが再描画され、他の画面は変わりません。RepaintBoundaryは、アニメーション、ビデオプレイヤー、インタラクティブグラフ、その他頻繁に更新される要素に特に便利です。
RepaintBoundaryの中心にあるのがRenderRepaintBoundary — 独立したPictureLayerを作成する特殊なRenderObjectです。最初のレンダリングで、RenderRepaintBoundaryはグラフィックスコマンドをこのレイヤーに記録します。その後の更新で、RepaintBoundary内のコンテンツだけが変わった場合、Flutterは画面全体の代わりにこのレイヤーだけを再描画します。他のレイヤーは変わらず、再利用されます。
すべてのウィジェットにRepaintBoundaryが必要なわけではありません。インターフェースの一部が高頻度(60 FPS以上)で更新され、他の画面が静止している場合に使用します。典型的な例: アニメーション読み込みインジケーター、ビデオプレイヤー、ゲームCanvas、頻繁に再描画されるCustomPainter。静止テキストやボタンには、RepaintBoundaryは過剰であり、メモリ消費を増やすだけです。
| シナリオ | 推奨 | 理由 |
|---|---|---|
| アニメーション | RepaintBoundaryで包む | 頻繁に更新される領域をアイソレート |
| スクロール可能リスト | 不要 | ListViewは自動的にレイヤーを使用 |
| 静止テキスト | 不要 | 頻繁な再描画なし |
| CustomPainter | 推奨 | 頻繁なグラフィックス再描画 |
パフォーマンス は、RenderObject Treeのノード数、ツリーの深さ、および再描画頻度に依存します。Flutter DevTools(「Rendering」タブ)では、RenderObject Treeをリアルタイムで分析できます: 再描画回数、layoutとpaintの所要時間、レイヤー数とそのサイズ。定期的な分析は、ボトルネックを特定するのに役立ちます。
オーバードロー とは、あるピクセルが1フレームに複数回描画される状況です。例えば、半透明なウィジェットが別のウィジェットに重なった場合、GPUは両方のレイヤーを描画します。非透明要素には、オパークフラグ(decorationの代わりにcolorを使用するContainer)を使用して、Flutterが不可視レイヤーをスキップできるようにします。Flutterチームによると、オーバードローを減らすことで、paint時間を30%まで縮少できます。
各RepaintBoundaryといくつかのウィジェット(Opacity、ClipRRect、Transform)は、独立したレイヤー(PictureLayer)を作成します。レイヤーが多すぎると、コンポジション時間が増えます。グルーピングを使用します: 個々の要素に複数のOpacityウィジェットを使う代わりに、コンテナに1つのOpacityを適用します。各要素にClipRRectを使う代わりに、共通コンテナにClipRRectを使用します。
ウィジェットがconstとして宣言されている場合、Flutterは構成が変わらないことを知っており、対応するRenderObjectを再作成せずに再利用できます。constコンストラクタは、ガーベジコレクションの負荷を減らし、最初のフレームを高速化します。固定パラメータのウィジェット(アイコン、タイトル、装飾要素)にはconstを使用します。
const Text("Title", style: TextStyle(fontSize: 24));
const Icon(Icons.home, color: Colors.blue);
const SizedBox.shrink();
よくある質問
Widget Tree は、再ビルドごとに再生成される軽量なインターフェース構成です。RenderObject Treeは、残り続けて、ジオメトリーや外観が変わった場合にのみ更新される重いレンダリングオブジェクトのツリーです。
Flutter DevTools を使用します — 「Rendering」タブです。レンダリングレイヤー、layoutとpaintの所要時間、および各RenderObjectの詳細(サイズ、制約、ダーティフラグ)が表示されます。
Dirty とは、更新が必要としてマークされたRenderObjectです。構成が変わったときに_needsLayoutまたは_needsPaintフラグが設定され、次のフレームでFlutterがそのノードのためにlayoutまたはpaintを実行します。
はい、RenderBoxのサブクラスを作成し、performLayoutとpaintメソッドをオーバライドします。カスタムRenderObjectをWidget Treeに埋め込むには、RenderObjectWidgetを使用します。これは、非標準レンダリングのための高度な技術です。
RepaintBoundary はRenderRepaintBoundaryを作成し、RenderObject Treeの一部を独立したレイヤーにアイソレートします。コンテンツが変わった場合、そのレイヤーだけが再描画され、他のツリーは変わりません。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。