
この記事は、CYBOZU SUMMER BLOG FES ’25の記事です。
こんにちは。サイボウズ OfficeでAndroidアプリ開発をしている、25卒Androidアプリエンジニアのこたです。
先月、DroidKaigi 2025というAndroidアプリエンジニア向けのカンファレンスに参加しました。
DroidKaigiでは、カンファレンスで参加者が使用するアプリをOSSとして公開し、当日まで参加者からのコントリビュートを受け付けるという試みを行っています1。
このアプリにはセッションの時間を一覧として表示する機能や、それをお気に入りとして保持する機能などが実装されています。
今年は、Compose Multiplatform2がアツいのか、AndroidやiOS、デスクトップ版まで開発されており、とても興味深いプロジェクトになっていました。
もちろん、私も1人のAndroidアプリエンジニア、1人の参加者として、issueに取り組みました。
しかし、私が取り組んだ「リスト内アイテム削除時にアニメーションを動作させる」というissueには、頭を悩ませる問題があったのです。
この記事では、DroidKaigi公式アプリにコントリビュートした際に直面した問題と、当時取った解決策について書き記していきます。
この記事では説明を簡単にするため、DroidKaigi公式アプリケーションで私が取り組んだ範囲を高度に抽象化したアプリを一つ用意しました。
以降、そのアプリの画面をベースに解説していきます。
リポジトリ:https://github.com/kota-shiokara/NestedListAnimLab
今回取り組んだissueは、「お気に入り削除時にアニメーションを追加する」というものです。
以下の動画をご覧ください。
実際の画面はより複雑な形になっていますが、タイムラインに各セッションのアイテムが存在する画面です。
以降はお気に入り画面と呼ぶことにします。
動画を見ていただければわかる通り、削除された時にUIが急に動くため、ユーザー体験を考えた際には物足りない画面となっています。
さて、実際にアニメーションをつけたらどうなるのでしょうか。
おや?アニメーションは動作しているようですが、何か違和感がありますね。
アニメーションは一見上手くいっているように見えますが、見出し部分の動きがアイテムと揃っておらず、ぎこちない印象を受けます。
その理由は、LazyColumnの中に各時間帯の見出しとColumnのリストが一つのアイテムとして内包されており、アニメーションが適用されているのがColumn内のアイテム部分のみだからです。
// example code data class TimeSlot(val startAt: LocalTime, val endAt: LocalTime) data class Session(val title: String) data class TimeTable(items: MapList >) LazyColumn { timeTableItems.forEach { slot, sessions -> stickyHeader(key = ...) { Text("$slot") } items(sessions) { session -> SessionCard( session = session modifier = Modifier.animateItem() // アニメーションするのはCardだけ!! ) } } }

ということは、見出し部分にもアニメーションをつければ良いのでしょうか?
できてしまったようです。それでは一体何が私を悩ませたのでしょうか?
ここで、この記事の冒頭でDroidKaigi公式アプリについて触れたことを思い出してください。
「デスクトップ版まで開発されており」と書きましたね。
デスクトップアプリは通常、モバイルとは異なり横長のディスプレイを使用します。
つまり、アイテムのレイアウトがモバイルとは異なるのです。
あれだけ頑張ってつけたアニメーションも、様々な画面の大きさに合わせる場合(アダプティブレイアウトと呼ばれます)は、処理の関係で消えてしまいますし、実装する場合には複雑な計算をした上で出しわけまでしなければなりません。
当時はここからこねくり回す時間と精神的余裕がなかったため、見出し部分は通常通りアニメーションさせ、アイテムはフェードイン/フェードアウトのアニメーションを付与してお茶を濁すことにしました。
実際の動作は以下の動画をご覧ください。
当時のPull Requestはこちらから確認できます。
問題を振り返ってみると、パッと考えるだけでもいくつかの代替案が浮かんできます。
一つ目は、そもそもアダプティブレイアウトをやめるという選択肢です。
モバイルとデスクトップで同じレイアウトを使用すれば、アニメーションの実装は比較的シンプルになります。
しかし、これでは横長画面を活かせず、デスクトップユーザーにとって最適な体験を提供できません。
加えて、モバイルでも横画面にするユースケースは多いため、あまり取りたくない選択肢です。
二つ目は、アニメーションをつける箇所とつけない箇所を戦略的に選び、少ないアニメーションで最大限の効果を得るというアプローチです。
例えば、アイテムのアニメーションに注力し、見出し部分は一切アニメーションをしないという選択肢もあったでしょう。
今回は見出し部分のアニメーションを通常通り行い、アイテムは単純なフェードイン/フェードアウトに留めるという手法を採用しました。
しかし、この手法では全ての要素に視覚的な効果があるとはいえ、アニメーションに統一感がありません。
今振り返ってみると、目先のユーザー体験に意識を割きすぎて、認知負荷が高いUIになってしまったと少し後悔しています。
このアニメーションに関する問題は現在も最適解を模索中ですが、もしより良い方法をご存知でしたら、ぜひ感想と共にTwitter(現X)でツイートしていただけると嬉しいです。
DroidKaigiの公式アプリは、例年挑戦的な取り組みをしており、多くのAndroidエンジニアが開発に携わっています。
コードを眺めるだけでも大きな学びがあるはずです。
また、運営の方々が初学者から上級者まで参加しやすい体制を整えてくださっているので、この記事をご覧のあなたも、来年のDroidKaigiにぜひ挑戦してみてはいかがでしょうか。
- DroidKaigi/conference-app-2025: The Official Conference App for DroidKaigi 2025, https://github.com/DroidKaigi/conference-app-2025↩
- Compose Multiplatform(CMP): Kotlinで複数のプラットフォームのアプリケーションを開発できるKotlin Multiplatform(KMP)のUIフレームワークです。 https://www.jetbrains.com/compose-multiplatform/↩
コメント