
みなさまこんにちは〜!
メモリアインクのすだです。
今回は、UI部分の実装に使用するJetpack Composeにおいて
再コンポーズとは何か、どういう時に起こるのか、そして起こった時にUIに起こる変化について解説していきます。
この記事を読んでわかること…
・再コンポーズとは
・再コンポーズが起こるタイミングについて
・再コンポーズ時の最適化について
環境
- Kotlin (ver 1.9.0)
- Android Studio (Giraffe | 2022.3.1 Patch 3)
再コンポーズとは(基本の概念)
再コンポーズ(Recomposition)とは、Jetpack Compose で UI が更新される仕組みのことです。
Composable 関数は、状態(State)が変化したときに再び呼び出され、画面を新しい状態で描画しなおします。これが「再コンポーズ」です。
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
Column {
Text("回数: $count") // ← ここが再コンポーズされる
Button(onClick = { count++ }) {
Text("増やす")
}
}
}
この場合、count++
が呼ばれるたびに count
の値が変わるので
それをJetpackComposeが自動で判別してText("回数: $count")
という部分を再コンポーズ =「もう一度実行して、新しい count に基づいた UI を再描画」します。
このとき、Button などの他の部品や Column全体のUIは再コンポーズされず
あくまでText()だけ再コンポーズ=再描画されるのです。
再コンポーズが起きる主なタイミング
まとめると、以下のような場面でComposableは再コンポーズされます。
① State
が変わったとき
count
の値が変わると、それを使っている Text()
が再コンポーズされる
var count by remember { mutableStateOf(0) }
Button(onClick = { count++ }) {
Text("回数: $count")
}
② 親Composableが再コンポーズされたとき
親が再コンポーズされれば、子も再コンポーズされる(必要に応じて)
@Composable
fun Parent() {
val name = "Hanako"
Child(name)
}
@Composable
fun Child(name: String) {
Text("こんにちは、$name")
}
③ ダークモード切り替え、フォントサイズ変更など UIテーマが変わったとき
システムの変更があれば、全体が再コンポーズされます(設定画面から戻ってきたときなど)
④ 画面遷移して戻ってきたとき
NavHost
による画面遷移で、「ある画面」に戻ってきたらそのComposableが再コンポーズされます。
⑤ キーボードの表示 / 非表示など、UIのサイズが変わるとき
レイアウトが大きく変わると、該当部分が再構築されることがあります。
再コンポーズを最適化するためのテクニック
remember
Composeでは再コンポーズが頻繁に起きますが、毎回重たい処理が走るとパフォーマンスが悪化します。remember
を使うことで 「無駄な再計算」を防ぎ、パフォーマンスを維持できます。
例:カラーパレットを表示する画面 ▼
@Composable
fun ColorPaletteScreen() {
// 毎回再コンポーズが起きても、colors の再生成は防がれる
val colors = remember {
listOf(Color.Red, Color.Green, Color.Blue, Color.Yellow, Color.Cyan)
}
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
items(colors) { color ->
Box(
modifier = Modifier
.fillMaxWidth()
.height(60.dp)
.padding(vertical = 8.dp)
.background(color)
)
}
}
}
もし val colors = listOf(...)
を remember
を使わずに定義していた場合、
再コンポーズのたびに listOf(...)
が再実行されて、毎回新しいリストが生成されてしまいます。
remember
を使うことで、Composableが再描画されても、最初の1回だけリストを生成し、以後はキャッシュされたものを使い続けてくれます。
derivedStateOf
Compose では、複数の State を使っていると、状態の依存関係が複雑になります。
そこで derivedStateOf
を使うと、「この状態はこの値から自動で決まるよ」と明示できるようになります。
例:カウントが偶数か奇数かで背景色を切り替える ▼
@Composable
fun EvenOddCounter() {
var count by remember { mutableStateOf(0) }
// count の変化に応じて isEven を更新
val isEven by remember(count) {
derivedStateOf { count % 2 == 0 }
}
val backgroundColor = if (isEven) Color.LightGray else Color(0xFFFFE0B2) // オレンジっぽい色
Column(
modifier = Modifier
.fillMaxSize()
.background(backgroundColor)
.padding(32.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("現在のカウント: $count", fontSize = 20.sp)
Spacer(modifier = Modifier.height(16.dp))
Button(onClick = { count++ }) {
Text("カウントを増やす")
}
}
}
isEvenの意味としては、「count
が偶数のときだけ isEven
は true に、奇数のときは false になる。」
それにderivedStateOf
をつけると、依存元 (count
) に変化があったときのみ派生状態 (isEven
) を再評価してくれるのです。
(val isEven = count % 2 == 0
仮にこう書いてしまうと、countが変わっていなくても別の要因で再コンポーズが起きたときにこの処理が走って再計算されてしまうため)
まとめ
おつかれさまでした。いかがでしたでしょうか!
再コンポーズは
・状態が変わったとき
・ 親Composableが変わったとき
・ テーマや画面の条件が変わったとき
に「賢く必要なところだけ再描画するしくみ」であるということを覚えてください!



技術者としてのキャリアパスを次のレベルへと進めたい皆様、
<未経験からIT・Webエンジニアを目指すなら【ユニゾンキャリア】>
自分の市場価値をさらに向上させてみませんか?
それではまた次回の記事でお会いしましょう!
コメント