2024年11月14日木曜日

M3 の LinearProgressIndicator の progress は lambda になっているが、使い方に注意しないと recomposition が走ることがある

M2 の LinearProgressIndicator の progress 引数は Float でしたが、M3 では () -> Float になっています。 androidx.compose.material.LinearProgressIndicator( progress = 0.5f, ) androidx.compose.material3.LinearProgressIndicator( progress = { 0.5f }, ) いずれも内部の実装は Canvas composable を使って描画しています。
そのため、progress を lambda にすることで Composition と Layout phase をスキップして Drawing phase だけやり直せばよくなり、その分パフォーマンスが良くなります。
https://developer.android.com/develop/ui/compose/phases

実際以下のコードを実行して Layout Inspector で recomposition の回数を見ると、M2 の方は recompositoin されていますが M3 の方は skip されています。 Column( verticalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.fillMaxSize().padding(16.dp), ) { var progress by remember { mutableFloatStateOf(0f) } androidx.compose.material3.Button( onClick = { progress = Random.nextFloat() }, ) { Text("update progress") } androidx.compose.material.LinearProgressIndicator( progress = progress, modifier = Modifier.fillMaxWidth(), ) androidx.compose.material3.LinearProgressIndicator( progress = { progress }, modifier = Modifier.fillMaxWidth(), ) }



M3 の LinearProgressIndicator を wrap するときは、wrap する component でも progress を lambda で取るように注意してください(より正確に言うと、lamda の中で state から読み出しを行うようにするということ)。そうしないと M3 の LinearProgressIndicator を使っていても recompose が走ります。 Column( verticalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.fillMaxSize().padding(16.dp), ) { var progress by remember { mutableFloatStateOf(0f) } androidx.compose.material3.Button( onClick = { progress = Random.nextFloat() }, ) { Text("update progress") } LinearProgressIndicatorM2(progress) LinearProgressIndicatorM3_Bad(progress) LinearProgressIndicatorM3_Good({ progress }) } @Composable private fun LinearProgressIndicatorM2(progress: Float) { androidx.compose.material.LinearProgressIndicator( progress = progress, modifier = Modifier.fillMaxWidth(), ) } @Composable private fun LinearProgressIndicatorM3_Bad(progress: Float) { androidx.compose.material3.LinearProgressIndicator( progress = { progress }, modifier = Modifier.fillMaxWidth(), ) } @Composable private fun LinearProgressIndicatorM3_Good(progress: () -> Float) { androidx.compose.material3.LinearProgressIndicator( progress = progress, modifier = Modifier.fillMaxWidth(), ) }



そうは言っても、階層のどこかで progress が読み出されていることもあるでしょう(Text Composable で progress の値を表示しているとか)。その場合は rememberUpdatedState を使うことで LinearProgressIndicator の recomposition を skip させることができます。 @Composable private fun Wrap(progress: Float, onUpdate: () -> Unit) { val updatedProgress by rememberUpdatedState(progress) Column( verticalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.fillMaxSize().padding(16.dp), ) { ... LinearProgressIndicatorM3_Good({ updatedProgress }) } }

0 件のコメント:

コメントを投稿