概要
Flutter は「すべてがウィジェット(everything is a widget)」という思想のもと、宣言的・リアクティブに UI を構築するクロスプラットフォーム UI ツールキットである。
普段は Column や Container を組み合わせるだけで画面が作れてしまうが、「なぜ width: 100 を指定しても 100px にならないのか」「ホットリロードが効かないのはなぜか」「RenderFlex overflow は何が起きているのか」といった疑問にぶつかったとき、その裏側の仕組みを知っているかどうかで対応力が大きく変わる。
この記事では、Flutter 公式ドキュメントを要約しつつ、以下のトピックを具体例を交えて整理する。
- Flutter のアーキテクチャ全体像(レイヤー構造 / 3 つのツリー / レンダリングパイプライン)
- Flutter 内部の仕組み(なぜ大量のウィジェットでも高速なのか)
- レイアウトの黄金ルール(Constraints go down. Sizes go up. Parent sets position.)
- ホットリロードの仕組みと効かないケース
- ビルドモード(debug / profile / release)
- ドットショートハンド構文(Dart 3.10 / Flutter 3.44)
最後に、これらを段階的に理解していくための学習ロードマップを示す。
本記事は Flutter 3.44 時点の公式ドキュメントを参照している。
💻 前提環境
- Flutter: 3.44 系を想定
- 言語: Dart 3.10 以降
- 対象読者: Flutter を触り始めた初心者〜中級者
- サンプルコードは説明用の簡略版
🏛️ Flutter のアーキテクチャ全体像
レイヤーモデル(Framework / Engine / Embedder)
Flutter は 拡張可能でレイヤー化されたシステム として設計されている。各レイヤーは下位レイヤーに依存するが、下位レイヤーへの特権的アクセスは持たず、フレームワーク部分はすべて 任意で置き換え可能 になっている。
| レイヤー | 主な言語 | 役割 |
|---|---|---|
| Framework | Dart | 開発者が普段触る層。ウィジェット / レイアウト / アニメーション / Material・Cupertino |
| Engine | C++ | 合成済みシーンのラスタライズ、テキストレイアウト、I/O、Dart ランタイム。dart:ui 経由で公開 |
| Embedder | Java/C++(Android), Swift/ObjC(iOS/macOS), C++(Win/Linux) | OS との橋渡し。エントリポイント / レンダリング面 / 入力 / イベントループ |
flowchart TB
subgraph framework [Framework Dart]
materialCupertino[Material / Cupertino]
widgets[Widgets]
rendering[Rendering]
foundation[Foundation / Animation / Painting / Gestures]
materialCupertino --> widgets --> rendering --> foundation
end
subgraph engine [Engine C++]
dartui[dart:ui]
impeller[Impeller / Skia / Dart Runtime]
dartui --> impeller
end
subgraph embedder [Embedder Platform]
os[Android / iOS / Web / Desktop]
end
framework --> engine --> embedder
Framework 層をさらに下から見ると次のように積み上がっている。
- Foundation: 基礎クラス、アニメーション・描画・ジェスチャなどの共通抽象
- Rendering: レイアウトを扱う層。レンダリング可能オブジェクトのツリーを構築する
- Widgets: 合成の抽象化層。リアクティブなプログラミングモデルはここで導入される
- Material / Cupertino: ウィジェット層の合成プリミティブを使って各デザイン言語のコントロールを実装
Framework 自体は比較的小さく、camera や webview などの多くの高機能はパッケージ(プラグイン)として提供される。
アプリの構成要素
flutter create で生成される標準的なアプリは、以下のピースで構成される。
- Dart App: ウィジェットを組み合わせて UI とビジネスロジックを実装(アプリ開発者が所有)
- Framework: ウィジェットツリーをシーンに合成する高レベル API
- Engine: 合成済みシーンのラスタライズ。
dart:uiで公開し、Embedder API で各プラットフォームと統合 - Embedder: OS とのやり取り、イベントループ管理
- Runner: Embedder のプラットフォーム固有 API を組み合わせ、実行可能なアプリパッケージにする
リアクティブ UI: UI = f(state)
Flutter は React に影響を受けた リアクティブ / 宣言的 UI フレームワークである。 開発者は「アプリの状態 → UI の対応」を記述するだけで、状態が変わったときの UI 更新はフレームワークが担う。
ウィジェットは build() メソッドで自身の UI を宣言する。これは状態を UI に変換する関数だと考えられる。
UI = f(state)build() は 副作用がなく高速 であることが前提で、フレームワークは必要に応じて(最悪フレームごとに)何度でも呼び出せる。
ウィジェットと「合成(Composition)」
Flutter の最大の特徴のひとつが 積極的な合成(aggressive composability) である。
たとえば「余白」は他ウィジェットのプロパティではなく、Padding という独立したウィジェットになっている。中央寄せも Align プロパティをいじるのではなく Center ウィジェットで包む。
// 中央寄せは「プロパティ」ではなく「ウィジェットで包む」Center( child: Padding( padding: const EdgeInsets.all(16), child: const Text('Hello World'), ),)クラス階層をあえて 浅く広く することで、小さな単機能ウィジェットの組み合わせ爆発によって表現力を確保している。
よく使う Container でさえ、内部的には LimitedBox / ConstrainedBox / Align / Padding / DecoratedBox / Transform といったウィジェットの合成で作られている。
3 つのツリー(Widget / Element / RenderObject)
Flutter を理解するうえで最重要なのが、3 種類のツリー が連携して動く点である。
flowchart LR
subgraph w [Widget Tree]
w1["不変の設定値<br/>毎フレーム作り直し可"]
end
subgraph e [Element Tree]
e1["ツリー上の位置と実体<br/>State を保持・永続的"]
end
subgraph r [RenderObject Tree]
r1["レイアウト・描画<br/>ヒットテスト"]
end
w -->|inflate| e -->|生成・更新| r
| ツリー | 性質 | 役割 |
|---|---|---|
| Widget | 不変(immutable) | UI の「設定」。build() が毎回新しく返す |
| Element | 永続的 | ウィジェットの「実体」。ツリー上の位置と State を保持 |
| RenderObject | 永続的 | 実際のレイアウト・描画・ヒットテストを担当 |
ポイントは Widget は使い捨て、Element は再利用される という点。
Text('A') を Text('B') に変えると新しい Widget オブジェクトが作られるが、Element ツリーはフレームをまたいで維持され、変更があった部分だけを差分更新する。これにより「ウィジェットツリーは完全に使い捨てに見えるが、内部表現はキャッシュされている」という設計が成立する。
Element には 2 種類ある。
ComponentElement: 他の Element を保持するホストRenderObjectElement: レイアウトや描画フェーズに参加する Element(対応するRenderObjectの仲介役)
build() の引数 BuildContext は、実体としてはこの Element への参照(ツリー上の位置のハンドル)であり、Theme.of(context) のような呼び出しはこの context を使って祖先を辿っている。
状態管理(State / InheritedWidget)
- 状態を持たないウィジェットは
StatelessWidget - 状態を持つウィジェットは
StatefulWidget。ウィジェット自体は不変なので、可変状態は別のStateクラスに保持する - 状態を変えるときは
setState()を呼び、フレームワークに再 build を依頼する
ツリーが深くなると、状態をバケツリレーするのが煩雑になる。そこで InheritedWidget を使うと、共通の祖先からデータを効率よく取得できる。
// 祖先に置いた状態を、子孫から型で取得するfinal studentState = StudentState.of(context);Flutter 自身もテーマ(Theme.of(context))などで InheritedWidget を多用している。provider などの状態管理パッケージも、その多くは InheritedWidget のラッパーである。
レンダリングパイプライン(Build → Layout → Paint)
UI コードが実際のピクセルになるまでの流れは次の通り。
flowchart LR state[State] -->|build| widget[Widget Tree] widget -->|inflate| element[Element Tree] element -->|create/update| render[RenderObject Tree] render -->|layout| size[サイズ・位置確定] size -->|paint| scene[Scene] scene -->|compositeFrame| gpu[GPU でラスタライズ]
- Build: 状態から Widget ツリーを構築し、対応する Element ツリーへ展開(inflate)。ここで
ContainerがColoredBoxを挿入するなど、コードより深いツリーになることがある - Layout:
RenderObjectツリーを深さ優先で 1 パス走査。親から子へ制約(constraints)を渡し、子から親へサイズ(geometry)を返す。O(n) で完了する - Paint: 各
RenderObjectがpaint()で描画。ルートのRenderViewがcompositeFrame()でシーンを合成し、dart:ui経由で GPU に渡す
なお Flutter は OS のウィジェットを使わず、自前のウィジェットセットを直接描画する(描画は Impeller)。これにより OS バージョンによらず同じ見た目・性能を保ち、Flutter ↔ プラットフォーム間の往復オーバーヘッドを避けている。
⚙️ Flutter 内部の仕組み(なぜ高速なのか)
積極的な合成の結果、Flutter の UI は 大量のウィジェット で構成される。それでも高速に動くのは、レイアウトとビルドに サブリニア(sublinear)なアルゴリズム を採用しているためである。
サブリニアなレイアウト
- Flutter のレイアウトは 1 フレームにつき 1 回・単一パス で行われる
- 親が子の
layoutを呼んで制約を渡し、子はサイズを返す。一度返したRenderObjectはそのフレームでは原則 二度と訪問されない - 各
RenderObjectはレイアウト中に 最大 2 回(下りと上り)しか訪問されない
さらに、無駄な再計算を避ける仕組みがある。
- 子が dirty でなく、前回と 同じ制約 を受け取ったなら、子は即座に return できる(走査を打ち切る)
- 親が子のサイズを使わないなら、子のサイズが変わっても親は再レイアウト不要
- tight 制約(最小 = 最大)なら、子は親から新しい制約が来ない限りサイズを変えられないので、親の再計算も不要
結果として、dirty なノードとその周辺の限定的な部分木だけが訪問される。
サブリニアなウィジェットビルド
- ビルド後のウィジェットは Element ツリーが保持する(ウィジェットは不変で親子関係を覚えられないため)
setState()などで Element が dirty になると、フレームワークは dirty な Element のリストを持ち、ビルド時にそこへ直接ジャンプして clean な Element を飛ばす- ビルド中は情報が 下方向にしか流れない ため、各 Element はビルド時に最大 1 回しか訪問されない
ここで効くのが const と「再投影(reprojection)」パターン。
ウィジェットが不変なので、親が 同一インスタンス の子で再ビルドすれば、Element はオブジェクト同一性の比較だけで「変わっていない」と判断し、即座に return できる。
// const を付けると同一インスタンスとして扱われ、再ビルドがスキップされやすいconst SizedBox(height: 20)また、InheritedWidget は各 Element にハッシュテーブルとして情報を 下方向にプッシュ することで、テーマ参照などのたびに親チェーンを辿る O(N²) を回避している。
線形の差分(reconciliation)と key
意外なことに、Flutter は ツリー差分(tree-diffing)アルゴリズムを使っていない。 代わりに、各 Element の子リストを個別に O(N) で照合する。最適化されるケースは以下。
- 旧子リストが空
- 新旧リストが同一
- 1 か所だけ挿入・削除された
- 同じ key を持つウィジェット同士をマッチさせる
key を付けると、リスト内で要素が並び替わっても「同じものだ」と認識でき、State を取り違えずに済む。
ツリーサージェリー(GlobalKey)
Element の再利用は、State と RenderObject を保持できる ため性能上きわめて重要。
GlobalKey を使うと、Element ツリーの 別の場所へウィジェットを移動 しても、新規生成ではなく既存 Element を付け替え(reparent)て部分木全体を維持できる。
これは Hero アニメーション やナビゲーションで多用される。移動先の親が同じ制約を渡せば、子はレイアウトを即 return できるため、移動してもレイアウト情報が再利用される。
無限スクロール(Viewport / Sliver)
ListView のような無限スクロールは、表示されているぶんだけウィジェットをオンデマンド生成する。
- スクロール領域の外側は
Viewport(「中身が外より大きい箱」) - その子は
RenderBoxではなくRenderSliver(ビューポートを意識したレイアウトプロトコルを持つ) - Sliver は「見えている残り空間」の情報を受け取れるので、無限の子を持ちうる場合でも 有限個だけ 生成する
これを成立させるため、Flutter は ビルドとレイアウトのフェーズを織り交ぜる。レイアウト中(=見える空間が分かった時点)に、その配下のウィジェットを必要なぶんだけ生成できる。
Element と RenderObject を分ける理由
両ツリーはほぼ同型(RenderObject ツリーは Element ツリーの部分集合)だが、あえて分けている。
- 性能: レイアウト変更時、合成由来の余計なノードを飛ばして必要部分だけ走査できる
- 明確さ: ウィジェットのプロトコルと描画のプロトコルをそれぞれ専門化でき、API が単純になる
- 型安全: RenderObject ツリーは座標系ごとに型を保証できる
📐 レイアウトの黄金ルール
Flutter のレイアウトでつまずく多くの原因は、HTML/CSS の感覚を持ち込んでしまうこと。 公式は次のルールを 早い段階で暗記すべき としている。
Constraints go down. Sizes go up. Parent sets position. (制約は下りていく。サイズは上っていく。位置は親が決める。)
詳細にすると次の流れ。
- ウィジェットは親から 制約(constraints) を受け取る。制約とは「最小・最大の幅」「最小・最大の高さ」の 4 つの double
- ウィジェットは各子に制約を伝え、子に「どんなサイズになりたいか」を尋ねる
- ウィジェットは子の 位置 を決める
- 最後に、ウィジェットは自分の サイズ を親に伝える(受け取った制約の範囲内で)
親子の「交渉」の具体例
公式の例(幅 300・高さ 85 の制約、5px の padding 付き Column)を要約すると、こうした会話になる。
- 親「君は幅 0〜300px、高さ 0〜85px の範囲ね」
- ウィジェット「padding 5px ぶん引くので、子には最大 290 × 75 を渡そう」
- 第 1 子「では幅 290px、高さ 20px にします」
- ウィジェット「2 つ目は第 1 子の下に置くから、残り高さは 55px」
- 第 2 子「幅 140px、高さ 30px にします」
- ウィジェット「位置は第 1 子 (5,5)、第 2 子 (80,25)。自分は 300×60 にする」と親へ報告
つまり 位置は子自身では決められず、親が決める。子は与えられた制約の範囲でしかサイズを選べない。
ボックスの 3 つのタイプ
制約の扱い方で、ボックスは大きく 3 種類に分かれる。
| タイプ | 振る舞い | 例 |
|---|---|---|
| できるだけ大きくなる | 与えられた制約いっぱいに広がる | Center, ListView |
| 子と同じサイズになる | 子に合わせる | Transform, Opacity |
| 特定サイズになろうとする | 固有のサイズを持つ | Image, Text |
Container は引数次第で変化する。デフォルトは「できるだけ大きく」だが、width を与えるとそのサイズを尊重しようとする。
これが「width: 100 を指定したのに 100px にならない」問題の正体。
たとえば画面いっぱいの制約を受けた Container(width: 100) は、親(画面)の tight な制約 に従って画面サイズになってしまう。Center で包むと、Center が子に「0〜画面サイズ」の loose な制約 を渡すため、ようやく 100px になれる。
// これは画面いっぱいになる(親が tight な制約を渡すため width が無視される)Container(width: 100, height: 100, color: Colors.red)
// Center で包むと loose な制約になり、100x100 になれるCenter( child: Container(width: 100, height: 100, color: Colors.red),)tight 制約と loose 制約
- tight(きつい)制約: 最小 = 最大。満たすサイズはただ 1 つ。例: 画面ルートが子に渡す制約
- loose(ゆるい)制約: 最小が 0。「最大までなら好きなサイズで良い」。例:
Centerが子に渡す制約
ConstrainedBox と UnconstrainedBox の違いも理解の助けになる。
ConstrainedBox: 親から受けた制約に 追加の制約 を課す。親が tight ならその追加は効かないことがあるUnconstrainedBox: 子に制約を課さず、子が好きなサイズになれる。ただし子が大きすぎると、あのoverflow警告 が出る
// Row の中で子が大きすぎると "RenderFlex overflow" になるRow( children: [ Container(width: 4000, color: Colors.blue), // 画面幅を超えて溢れる ],)Row / Column のオーバーフローは、「子に制約を課さない」性質と「画面という有限の制約」がぶつかった結果である。Expanded や Flexible で残り空間を分配したり、SingleChildScrollView でスクロール可能にしたりして解決する。
🔥 ホットリロードの仕組み
ホットリロードは、更新したソースコードを実行中の Dart ランタイムへ注入 する機能。クラスのフィールドや関数を新しい版に差し替えたあと、Flutter フレームワークがウィジェットツリーを自動的に再ビルドし、変更を即座に確認できる。
仕組み
- ホスト側が前回のコンパイル以降に編集されたコードを調べ、変更されたライブラリ・アプリのメインライブラリ・そこに至るライブラリを再コンパイルする
- 生成された kernel ファイルをデバイスの Dart VM に送る
- Dart VM が新しい kernel からライブラリを再ロードする(この時点ではまだコードは再実行されない)
- フレームワークが既存の全ウィジェット / RenderObject の再ビルド・再レイアウト・再描画を発火する
重要なのは、ホットリロードは既存ウィジェットの再ビルドを引き起こすだけ という点。再ビルドに関わるコードしか再実行されないため、main() や initState() は 再実行されない。
State が保持される
ホットリロードは アプリの状態を保持 する。ログイン後の深い画面で UI を修正してリロードしても、ログインし直さずに変更を確認できる。これが通常は望ましい挙動。
ただし、状態に影響する変更をした場合、「最初から実行したときの状態」とは食い違うことがある。その場合は ホットリスタート(状態を捨てて再起動)が必要になる。
ホットリロードが効かない / 効きにくいケース
| ケース | 挙動 | 対処 |
|---|---|---|
main() / initState() の変更 | 再実行されないため反映されない | ホットリスタート |
| グローバル変数・static フィールドの初期化子変更 | 遅延初期化のため再実行されない | ホットリスタート |
| enum ↔ 通常クラスの相互変更 | 反映されない | ホットリスタート |
ジェネリック型の宣言変更(A<T> → A<T, V>) | 反映されない | ホットリスタート |
| ネイティブコード(Kotlin/Swift 等)の変更 | 反映されない | フルリスタート(停止して再起動) |
| コンパイルエラー | 拒否されエラー表示 | 該当行を修正 |
| アプリが kill された | 中断される | 再起動 |
// 例: ルートウィジェットの build より「下流」の変更はホットリロードで反映されるclass Counter extends StatelessWidget { const Counter({super.key});
@override Widget build(BuildContext context) { // ここの文字色やテキストを変えるとホットリロードで即反映される return const Text('count: 0'); }}判断の目安は「変更したコードが、ルートウィジェットの build() より下流で再実行されるか」。下流なら反映される。final bar = foo; のような初期化子の変更は反映されにくいので、const にするか getter(int get bar => foo;)に置き換えると良い。
🏗️ ビルドモード
Flutter はコンパイル時に 3 つのモードを持つ(加えてテスト用のヘッドレスモード)。開発サイクルのどこにいるかで使い分ける。
| モード | 用途 | コンパイル | 主な特徴 |
|---|---|---|---|
| debug | 開発中(ホットリロード) | JIT | assert 有効、サービス拡張有効、デバッグ可能。実行速度より開発速度を最適化 |
| profile | 性能分析 | AOT | release に近いが、トレースと一部サービス拡張(パフォーマンスオーバーレイ等)が有効 |
| release | 本番リリース | AOT | assert 無効、デバッグ情報除去、起動・実行・サイズを最適化 |
flutter run # debug(デフォルト)flutter run --profile # profileflutter run --release # releaseflutter build apk # release ビルドの生成(ターゲット指定)ポイント。
- debug は JIT、profile / release は AOT(事前にネイティブコードへコンパイル)でマシンコードに変換される
- ホットリロードは debug モードのみ 利用できる
- profile / release は実機が必要で、エミュレータ / シミュレータでは無効(実機相当の性能が出ないため)
- Web では debug が
dartdevc、release がdart2js(minify + tree shaking)でコンパイルされる
開発は debug、カクつきの原因調査は profile、ストア提出は release、と覚えておけば良い。
✨ ドットショートハンド構文(Dart 3.10 / Flutter 3.44)
ドットショートハンドは、コンパイラが文脈から型を推論できる場合に、静的メンバ・コンストラクタ・enum 値の型名を省略できる 機能。
Flutter は深くネストしたウィジェットツリーを書くことが多く、Alignment.center や MainAxisAlignment.center のような型名の繰り返しがボイラープレートになりがちだった。これを .center のように短く書ける。
Before / After
// Before(型名を明示)Container( alignment: Alignment.center, padding: const EdgeInsets.all(16.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Hello World', style: TextStyle(fontWeight: FontWeight.bold)), ], ),);// After(ドットショートハンド)Container( alignment: .center, // Alignment.center padding: const .all(16.0), // EdgeInsets.all(16.0) child: Column( mainAxisAlignment: .center, // MainAxisAlignment.center crossAxisAlignment: .start, // CrossAxisAlignment.start children: [ Text('Hello World', style: TextStyle(fontWeight: .bold)), // FontWeight.bold ], ),);使える場所
コンパイラが明確な「コンテキスト型」を持つ場所、つまりウィジェットのプロパティ指定のほぼすべてで使える。
- enum:
MainAxisAlignment,CrossAxisAlignment,BoxFit,TextDirectionなど - 静的プロパティ / メソッド:
FontWeight.bold→.boldなど - コンストラクタ:
EdgeInsets.all()→.all()、BorderRadius.circular()→.circular()
// 名前付きコンストラクタ(基底クラス EdgeInsetsGeometry を推論)Padding( padding: .symmetric(horizontal: 16.0, vertical: 8.0), child: Text('Spaced out text'),)
// .new で無名コンストラクタも呼べる(ウィジェットツリーでは稀)final ScrollController _scrollController = .new(); // ScrollController()型名が省けるぶん可読性が上がる一方、文脈が曖昧だと推論が効かない。慣れるまでは「何の型が推論されているか」を意識しておくと安全。
🗺️ 理解を深めるためのロードマップ
ここまでの内容を、手を動かしながら段階的に深めていくためのロードマップを示す。各フェーズの「課題」を実際に試すことで、ドキュメントの知識が腑に落ちる。
flowchart TB p1["Phase 1: 入門<br/>ウィジェットで画面を作る"] p2["Phase 2: 基礎<br/>レイアウトと状態管理"] p3["Phase 3: 内部理解<br/>3 ツリーとパイプライン"] p4["Phase 4: 実践<br/>性能・最適化・運用"] p1 --> p2 --> p3 --> p4
Phase 1: 入門 — ウィジェットで画面を作る
| 項目 | 手を動かす課題 | 対応ドキュメント |
|---|---|---|
| Stateless / Stateful の違い | カウンタアプリを setState で実装 | architectural-overview |
| 合成(composition) | Center + Padding + Column で簡単な画面を組む | architectural-overview |
| ホットリロード体験 | 色やテキストを変えて即反映を確認、main() 変更が効かないことも体験 | hot-reload |
ゴール: 「すべてがウィジェット」「UI = f(state)」を体感する。
Phase 2: 基礎 — レイアウトと状態管理
| 項目 | 手を動かす課題 | 対応ドキュメント |
|---|---|---|
| レイアウトの黄金ルール | Container(width:100) が画面いっぱいになる→Center で 100px になる、を再現 | constraints |
| tight / loose 制約 | ConstrainedBox と UnconstrainedBox の違いを確認 | constraints |
| overflow の解消 | Row をわざと溢れさせ、Expanded / SingleChildScrollView で直す | constraints |
| 状態の受け渡し | InheritedWidget(または provider)でテーマや状態を共有 | architectural-overview |
ゴール: 「Constraints go down. Sizes go up. Parent sets position.」を説明できる。
Phase 3: 内部理解 — 3 つのツリーとパイプライン
| 項目 | 手を動かす課題 | 対応ドキュメント |
|---|---|---|
| Widget / Element / RenderObject | Flutter DevTools の Inspector で実際のツリーの深さを観察 | architectural-overview / inside-flutter |
| key の役割 | リストの並び替えで State が崩れる例を作り、key で直す | inside-flutter |
| GlobalKey とツリーサージェリー | Hero アニメーションを実装し、State 保持を確認 | inside-flutter |
| const の効果 | const の有無で再ビルド回数が変わることを確認 | inside-flutter |
| 無限スクロール | ListView.builder で大量データを表示し、Sliver の遅延生成を理解 | inside-flutter |
ゴール: 「なぜ大量のウィジェットでも高速なのか(サブリニア / 線形差分)」を説明できる。
Phase 4: 実践 — 性能・最適化・運用
| 項目 | 手を動かす課題 | 対応ドキュメント |
|---|---|---|
| ビルドモードの使い分け | debug / profile / release を切り替えて性能差を体感 | build-modes |
| 性能分析 | profile モードで DevTools の Performance を見て jank を調査 | build-modes |
RepaintBoundary | 再描画範囲を絞って描画コストを下げる | inside-flutter |
| ドットショートハンド | 既存コードを .center 等で書き換えて可読性を改善 | dot-shorthands |
| プラットフォーム連携 | Platform Channel / FFI でネイティブ機能を呼ぶ | architectural-overview |
ゴール: 性能問題を計測ベースで切り分け、適切な最適化を選べる。