Design Division 所属アプリエンジニアのスージです。 良いデザインでクライアントのビジネスを前進させるために開発している、Jetpack Compose と KMM が好きなエンジニアです。
Jetpack Compose の バージョン 1.2.0 が rc になりました。フォントのパディングの改善を含め、たくさんのAPIの追加や更新があります。
その中で個人的に気になった API を紹介しようと思います。
- movableContentOf
- LazyGrid にも animateItemPlacement 追加
- TextStyle に Brush の追加
- 新しい @Preview デバイス追加
- kotlinx.collections の @Stable 推論
- LazyLayout
- 終わりに
movableContentOf
movableContentOf
のドキュメンテーションを見てみると、以下のように書かれています。
Convert a lambda into one that moves the remembered state and nodes created in a previous call to the new location it is called.
つまり、movableContentOf
を使うとある @Composable lambda が remember(保特)され Compositionツリーの中に再コンポジションをせず移動できます。
分かりにくいと思いますので、サンプルを使って調べてみましょう。
まずは、movableContent
を使わずコンテンツを isVertical
フラグで Column
と Row
に切り替えるようにしましょう。
@Composable fun Screen() { .. var isVertical by remember { mutableStateOf(false) } ScreenContent(isVertical = isVertical, ..) { ItemA(modifier = Modifier.size(50.dp)) ItemB(modifier = Modifier.size(50.dp)) } Button(onClick = { isVertical = !isVertical }) { Text(text = "Toggle") } } @Composable fun ScreenContent(isVertical: Boolean, ..) { .. if (isVertical) { Column { content() } } else { Row { content() } } }
コンポジションや再コンポジションを確認するために ItemA
と ItemB
の中 SideEffect
にログを取ってます。再コンポジションのデバッグ方法についてこの記事で詳しく書かれているのでぜひ読んでみてください。
@Composable fun ItemA(..) { SideEffect { // Log.e("movableContent", "Compose A") } .. }
毎回 Column
と Row
に切り替えると @Composable の再コンポジション行います。
では、movableContentOf
を使ってみるとこのようになります。
@Composable fun ScreenContent( isVertical: Boolean, modifier: Modifier = Modifier, content: @Composable () -> Unit, ) { val movableContent by remember(content) { movableContentOf(content) } if (isVertical) { Column(..) { movableContent() } } else { Row(..) { movableContent() } } }
今回は movableContentOf
を使うと再コンポジションが行いません。
以前呼び出された場所で @Composable lambda の状態を保特し、再コンポジションせず次呼び出される場所に持っていくので Shared transitions は実現可能になるかもしれないですね。 すでに Zach Klipp 氏はそれを実装した例を共有してくれました。
I built a thing in Compose over the weekend. It's an idea I'd tried doing about a year ago, but it didn't work without a new feature that was recently introduced (movableContentOf). Videos > words: pic.twitter.com/loXbxE0Led
— Zach Klipp 🌻 #BlackLivesMatter #ACAB (@zachklipp) April 5, 2022
LazyGrid にも animateItemPlacement 追加
1.1.0 で LazyColumn
と LazyRow
に追加された animateItemPlacement
が、1.2.0 で LazyGrid
にも追加されてます。
LazyColumn
、LazyRow
と同じ key
を定義し grid アイテムに Modifier.animateItemPlacement()
を追加します。
LazyVerticalGrid(columns = Fixed(4)..) { items(items, key = { it }) { Card(modifier = Modifier.. .animateItemPlacement(animationSpec = tween(durationMillis = 300)), ) {..} } }
TextStyle に Brush の追加
Text
の TextStyle
にやっと Brush
対応が入りました。グラディエーション色設定できるようなりましたね。
val transition = rememberInfiniteTransition() val color1 by transition.animateColor(..) val color2 by transition.animateColor(..) val color3 by transition.animateColor(..) val color4 by transition.animateColor(..) Text( text = "いいね!", style = TextStyle( brush = Brush.linearGradient( colorStops = arrayOf( Pair(0f, color1), Pair(.3f, color2), Pair(.6f, color3), Pair(1f, color4), ), start = Offset.Zero, end = Offset.Infinite ), ), .. ) )
新しい @Preview デバイス追加
今年、タブレットとフォルダブルのための12Lの発表があると Material Design 3 Adaptive design のガイドラインもちゃんと書かれています。
そして 1.2.0 から @Preview
には Devices.DESKTOP
、Devices.TABLET
と Devices.FOLDABLE
3つの新しい reference デバイスが追加されてます。
@Preview(device = Devices.TABLET, name = "Tablet") @Preview(device = Devices.DESKTOP, name = "Desktop") @Preview(device = Devices.FOLDABLE, name = "Foldable") @Composable fun PreviewDevice() {..}
kotlinx.collections の @Stable 推論
kotlinx.collections の一部が @Stable
と推論されるようになってます。
今までは List<T>
を読み込んでる @Composable 変数は再コンポションの時もスキップされないですね。つまり、リストされてない時も再コンポジション行われます。
サンプルを見てみましょう。ここではisBlue
のみが更新されて list
は変わってないですね。でもリストを読み込んでる ListReadingComponent
のコンポジションを管理すると ListReadingComponent
の再コンポジションが行われてることがわかりますね。
var isBlue by remember { mutableStateOf(false) } val list = remember { listOf("A", "B", "C", "D") } Column { Row { Button(onClick = { isBlue = !isBlue }) { Text(text = "Toggle color") } Box( modifier = Modifier .size(48.dp) .background( color = if (isBlue) Color.Blue else Color.Red, shape = RoundedCornerShape(8.dp) ) ) } ListReadingComponent(list = list) } @Composable fun ListReadingComponent(list: List<String>..) { SideEffect { // Log.e("stableList", "Compose") } LazyColumn(..) { items(list) { Text(text = it) } } }
ではListReadingComponent
に渡すリストを kotlinx.ImmutableList
に変更してみましょう。1.2.0 から ImmutableList
は @Stable
と推論されてますので、更新されてない場合は再コンポジションが行われない。
これについてKotlinlang Slackでは「1.2.0 からパフォーマンスを改善するために全部のリストを kotlinx 化して方がいいのでは?」という質問がありました。 Ben Trengrove 氏はこのように回答しました。「全部を kotlinx 化しないと思いますが、リストを読み込んでパフォーマンスが落ちてるかを確認しながら一つずつ回数に kotlinx に切り替えるすると思います。」
LazyLayout
I/O 2022 でも発表がありましたが、LazyRow
、LazyColumn
LazyGrid
以外もカスタムUIのため LazyLayout
追加されてます。
Lazy layouts in Compose セッションと事例のため wear の ScalingLazyColumn
を調べてみてください。
終わりに
ここで書いてない 1.2.0 のAPI追加と更新盛り沢山です。1.3.0 には多分 Doris Liu 氏が紹介してくれた LookAheadLayout が追加される予定なので Compose にも Shared transitions が実現しやすくなることは楽しみですね!
Yup, LookaheadLayout is a new concept that we've been building. I can barely contain my excitement about the possibilities it opens up, shared element, auto-resizing, and more! 🤩🥰 Should be ready to release soon.
— Doris Liu (@doris4lt) May 30, 2022
Spoiler: LookaheadLayout + movableContentOf = 🤯
Sneak peek: 👇 https://t.co/vQpocq5mF9 pic.twitter.com/KXp9CUOgas
Goodpatch には、デザインと技術の両方を追求できる環境があります。 少しでもご興味を持ってくださった方、ぜひ一度カジュアルにお話ししましょう!