Jetpack Compose 1.1.0にanimateItemPlacementというmodifierが追加されて、LazyColumn/Rowのアイテムアニメーションが追加されました。
これにより、リストの位置が変更されたときに自動でアニメーションが効くようになりました。
まだExperimentalAPIですが、いい感じに動作しています。現状は単純にmodifierに指定するだけで使うことができるのでとてもかんたんです。
items(uiState.cards, key = { it.id }) {
Column(modifier = Modifier.animateItemPlacement()) {
// Copose...
}
}
itemsの要素にanimateItemPlacementをつけて上げる感じです。
また、itemsのkey指定は必須のようです。このkeyのdiffを取って動作するものと思われます。
Swipe to refreshするたびに要素がシャッフルするサンプルでの挙動はこんな感じ。
animateItemPlacement()
の引数にはanimateionを指定することができるようなので動きのカスタマイズもできそうです。

ですが、先程のツイートのスレッドに記載があったのですが、残念ながらアイテムの追加と削除にはアニメーションは機能しないようです🤔
これでは、挿入/削除のアニメーションは提供されないことに注意してください。公式のガイダンスが提供される予定ですが、AnimatedVisibilityですでに可能になっているはずです。リストからアイテムを実際に削除する代わりに、アイテムを非表示としてマークし、LazyColumnアイテムとして渡し続けます (Google翻訳)
ためしに、先程のサンプルを使ってカードをタップしたら要素が削除されるように実装してみました。
また、Swipe to refreshをした際にもとのデータを再取得するので追加の動きも確認できます。

なんとなくいい感じに動いているように見えますが、追加と削除に対してはアニメーションがかかっていません。
位置の変更に対しては animateItemPlacement
でアニメーションがかかっているため詰めるようなアニメーションや、追加の際の位置変更のアニメーションはかかっています。
先程のツイートにもあるように、追加と削除にもアニメーションを書けたい場合は、既存の AnimatedVisibility
を使って対応することができます。
items(uiState.cards, key = { it.id }) {
AnimatedVisibility(
modifier = Modifier.animateItemPlacement(),
visible = it.isVisible,
enter = fadeIn(),
exit = fadeOut(),
) {
Column {
// compose...
}
}
}
Itemsの直下の要素をまるごとAnimatedVisibilityで囲ってあげて、アイテムのVisibleでアニメーションをします。
注意点として、 animateItemPlacement()
は直下の要素につけるので、AnimatedVisibilityのmodifierにつけてあげないとうまく動作しませんでした。
実際に動かしてみるとこのような感じで、削除時にfadeoutがかかるようになりました。

このやり方に対して、件のツイートでもやりとりがあったのですが、追加/削除に対してVisibleでもってアニメーションを書けなければならないという点がなかなか面倒だなと感じました。
AnimatedVisibilityはVisibleにしかアニメーションがかからないため、すでに要素がある状態、あるいは保持している状態からの変化でしかアニメーションがかかりません。
つまり、事前に要素をすべて持っている必要があります。また、削除のときも同様に、Visibleの変化をするためにデータから削除することはできません。
その点に対してツイートでパフォーマンス的にもよくないのでは?という指摘がされていたのですが、それに対する回答は、「アニメーションが済んだら削除するのをおすすめするよ」でした。ロジック部分のコードが増えそうですし、なかなか難解なコードになりそうでそれはやりたくないなと思ってしまいました😅 笑
おわりに
まだJetpack ComposeもStableは1.1.0ですし、そもそも1.0.0では変更にすらアニメーションがかからなかったところからとても便利になりました。
animateItemPlacement()
もExperimentalAPIですし、フィードバック次第では追加と削除にも対応されるのかも?とも思いました。
現状のAPIでやろうとすると、今回のようにAnimatedVisibilityも併用してやる感じかなというまとめでした。