はじめに
Android の UI には、歴史的に 2つの“ツリー構造” が存在します。
- XML / View ベースの View ツリー
- Composable 関数から生成される Compose UI ツリー
どちらも画面 UI を構成しますが、
仕組み・更新モデル・パフォーマンス・設計思想 が根本から違います。
この記事では、
内部構造から Recomposition まで
「Android エンジニアが本当に知りたいポイント」をまとめます。
1. View ツリーとは
View ツリーは「View オブジェクトの階層構造」です。
XML を Inflate すると、以下のようなツリーが生成されます。
ViewGroup (LinearLayout)
├─ View (TextView)
└─ View (Button)
特徴
- UI は オブジェクトとして生き続ける
- プロパティを 命令的に変更して UI を変える
- 描画は measure → layout → draw
- ライフサイクルは Activity/Fragment と強烈に依存
- ViewGroup ネストが増えると重くなる
例:
Button の text を変えるときはこう:
textView.text = "OK"
→ オブジェクトに直接命令して UI を変える
→ 典型的な“命令的 UI”。
2. Compose UI ツリーとは?
Compose は「Composable 関数の結果として UI ノードができる」宣言的 UI。
Compose Root
└─ Column (LayoutNode)
├─ Text (LayoutNode)
└─ Button (LayoutNode)
└─ Text (LayoutNode)
特徴
- UI は「関数の呼び出し結果」であり、不変に近い
- 状態が変わると その部分だけ再構築(Recomposition)
- LayoutNode + ModifierNode が UI の本体
- Android の View とは完全に独立
- パフォーマンスは View より軽量になりやすい
例:
var text by remember { mutableStateOf("Hello") }
Button(onClick = { text = "OK" }) {
Text(text)
}
→ text の値が変わる
→ Text() Composable だけ再構築
→ LayoutNode も該当部分だけ差し替え
3. 更新モデルの違い:ここが最重要!
3.1 View:オブジェクトを書き換える(命令的)
View ツリーの更新は 状態とは無関係。
textView.text = "OK"
button.isEnabled = false
このように:
- プロパティを直接 mutate
- View 自体は生き続ける
- レイアウトパス(measure/layout/draw)が走る
3.2 Compose:状態が変われば再構築(宣言的)
状態を UI に反映するための唯一の手段は:
Composable を再呼び出す(Recompose)こと
text = "OK" // ← 値が変わる
↓
Text(text) が再呼び出される(Recomposition)
↓
該当する LayoutNode だけ差し替え
↓
画面が変わる
状態が UI を作る → React のようなモデル
だが React より速い。理由は:
- 差分を Runtime が把握している
- Virtual DOM を持たない
- Slot Table により “何がどの状態に依存するか” を追跡している
4. パフォーマンスモデルの違い
4.1 View は「重い」
View は:
- 一つ一つが巨大オブジェクト
- measure/layout がコスト高
- ViewGroup がネストするとさらに遅い
4.2 Compose は「軽量で効率的」
内部構造(オリジナル図):
Composable()
↓
Slot Table(記録)
↓
LayoutNode(軽量 UI ノード)
↓
ModifierNode(責務のパイプライン化)
理由:
- LayoutNode は超軽量
- ModifierNode は必要な機能だけを積む
- 不要ノードは Recomposition で作られない
- スロットテーブルによる“差分再構築”
結果:
→ 中〜大規模 UI ほど Compose の強さが出る
→ アニメーションや状態更新が滑らか
5. どう共存するの?(Interop 重要)
5.1 View に Compose を挿入(ComposeView)
ComposeView(context).setContent {
Text("Hello Compose")
}
View ツリーの中に:
ViewGroup
└─ ComposeView
└─ Compose UI ツリー
5.2 Compose に View を挿入(AndroidView)
AndroidView(factory = { context ->
WebView(context)
})
Compose UI ツリーの中に:
Compose Root
└─ AndroidView
└─ View ツリー
6. 全体まとめ
| 比較項目 | Compose UI ツリー | View ツリー |
|---|---|---|
| UI の実体 | LayoutNode / ModifierNode | View / ViewGroup |
| UI の生成 | 関数実行 | オブジェクト生成 |
| 主な更新 | Recomposition(差し替え) | プロパティ変更 |
| 描画パイプライン | Compose 独自 | measure/layout/draw |
| メモリ効率 | 高い | 重い |
| 状態管理 | 宣言的(State → UI) | バラバラ(UI が State を持つ) |
| ライフサイクル | 独立 | Activity/Fragment と密接 |
| カスタム UI | Modifier / Layout | Custom View |
7. 結論:Compose が現代 UI の本命である理由
Compose UI ツリーは:
- 関数式で安全
- 状態と UI の同期が自然
- パフォーマンスが現代的
- 再利用性が高い
- モジュール化しやすい
- テストしやすい
View ツリーはレガシーに見えるかもしれないけど:
- MapView / WebView など重 UI は依然必要
- レガシーとの兼ね合いでは共存が最善策