Contents
Jetpack Composeでアニメーションを始める前に
Jetpack ComposeはAndroid開発の宣言型UIツールキットとして注目を集めていますが、ユーザー体験向上に欠かせない「アニメーション」の実装について理解するには、まず宣言型UIと状態変化の関係性を把握する必要があります。
Jetpack ComposeではUI要素が状態(State)に応じて自動的に再描画されるため、アニメーションは自然な流れとして導入できます。この記事ではTransition APIを中心に、Jetpack Composeでの基本的なアニメーション技法をステップバイステップで解説します。
宣言型UIと状態変化の関係性について
宣言型UIでは、「**何を描画するか」を明示的に定義し、その内容が状態に基づいて動的に変化します。この特徴により、アニメーションの実装は「状態の変化」と「UI要素の再描画」の連携に集約されます。
- 重要なポイント
- 状態(State)を変更すると、Jetpack Composeは自動的にUIを再描画します
- アニメーションはこの「状態→UIの変化」の流れを制御することで実現可能
例: ボタンのクリックイベントで
isClickedというBoolean型のStateを更新すると、それに応じてUIがアニメーション付きで切り替わる仕組みになります。
Transition APIの基礎と使い方
Transition APIはJetpack Composeでシンプルなアニメーションを実装するためのコア技術です。このセクションでは、Transition APIの基本構造やライフサイクルについてステップバイステップで解説します。
Transition APIの基本構造
Transition APIは「入力(State)→アニメーション処理→出力(UI変化)」という流れをサポートしています。基本的な使い方は以下の3ステップです。
-
状態を定義する
kotlin
var isExpanded by remember { mutableStateOf(false) } -
Transitionオブジェクトを作成し、アニメーションのタイミングを指定する
kotlin
val transition = if (isExpanded)
Transition(AnimationSpec.linear)
else
Transition(AnimationSpec.linear)注意:
Transition(AnimationSpec.linear)は非推奨です。rememberTransitionやTransitionScopeを使用する方法が適切です。 -
UI要素にTransitionを適用して描画する
kotlin
Box(modifier = Modifier.transition(transition, { it }), contentAlignment = Alignment.Center) {
Text("アニメーション中のテキスト")
}
重要なのは、TransitionオブジェクトはStateに基づいて自動的に更新されることです。
アニメーションのライフサイクル
Transition APIでは「インスタンス生成→適用→実行→廃棄」というライフサイクルが存在します。特に、Transitionを再利用する際にはrememberTransitionやTransitionScopeで状態管理を行う必要があります。
| ライフサイクル | 説明 |
|---|---|
| 生成 | Transition()またはrememberTransition()でオブジェクトを作成 |
| 適用 | transition(...)関数をUI要素に適用 |
| 実行 | Stateの変化により自動的にアニメーションが開始 |
| 廃棄 | UIが破棄されたときにTransitionも一緒にクリーンアップされる |
Fade・Slideアニメーションの実装例
FadeやSlideなどの基本的なアニメーションは、Transition APIを活用して簡単に実装できます。具体的なコードサンプルと手順について解説します。
FadeTransitionの具体例
Fadeアニメーションは要素の表示/非表示に使われます。AnimatedVisibilityを使って簡単な実装が可能です。
実装手順:
isExpandedというState変数を定義するAnimatedVisibilityコンポーネントで条件分岐をアニメーション化- ボタンクリックイベントでStateを切り替える
|
1 2 3 4 5 6 7 8 9 10 11 12 |
val isExpanded by remember { mutableStateOf(false) } Column { Button(onClick = { isExpanded = !isExpanded }) { Text("表示/非表示") } AnimatedVisibility(visible = isExpanded, enter = fadeIn(), exit = fadeOut()) { Text(text = "Fadeアニメーション中", fontSize = 24.sp) } } |
SlideTransitionの具体例
Slideアニメーションは要素をスライドさせて移動させる効果です。OffsetやModifier.slide()を使うことで実現できます。
コード例:
|
1 2 3 4 5 6 7 8 9 10 |
val isSlided by remember { mutableStateOf(false) } Box( modifier = Modifier .size(100.dp) .slide(if (isSlided) 200.dp else 0.dp, Direction.LeftToRight) ) { Text("Slide中", fontSize = 18.sp) } |
注意:
Modifier.slide(Direction)は非公式APIであり、実装が不安定です。代わりにOffsetTransitionやカスタムアニメーションを使用することを推奨します。
AnimatedVisibilityとanimateContentSize
UI要素の可視性変化やコンテンツサイズのアニメーションには、AnimatedVisibilityやanimateContentSizeが役立ちます。ここではそれぞれの使い方と注意点について解説します。
AnimatedVisibilityの使いどころ
AnimatedVisibilityは「表示/非表示切り替え」に特化したコンポーネントです。普通のif条件分岐で要素を出し入れする代わりに、アニメーション付きでUIが変化します。
使用例:
|
1 2 3 4 5 6 |
val isVisible by remember { mutableStateOf(false) } AnimatedVisibility(visible = isVisible, enter = fadeIn(), exit = fadeOut()) { Text("表示されるテキスト", fontSize = 20.sp) } |
注意点:
AnimatedVisibilityは要素の出現・消失をアニメーション化するため、UIのレイアウト変化には自動的に対応しません。必要に応じてModifier.animateLayout()やanimateContentSize()と併用してください。
animateContentSizeの適用例
コンテンツサイズの変化に伴うアニメーションはanimateContentSize()で実現できます。動的な内容(例: リストやテキスト)をスムーズに表示・隠すのに適しています。
コード例:
|
1 2 3 4 5 6 7 8 9 10 |
val isExpanded by remember { mutableStateOf(false) } Box( modifier = Modifier .heightIn(min = 50.dp) .animateContentSize() ) { Text(if (isExpanded) "詳細表示" else "要約") } |
リストやダイナミックなコンテンツで使うと、スムーズなUI変化が実現できます。
AnimatedVisibilityとの併用も有効です。
Jetpack Navigation ComposeとTransition APIを組み合わせることで、画面遷移時のアニメーションが簡単に実装可能です。ここでは具体的手順について解説します。
画面遷移時のアニメーション設定
Navigation ComposeのNavHostやNavControllerにTransition APIを統合する方法です。
手順:
navGraphでenterAnimとexitAnimを指定transitionオブジェクトを作成し、画面遷移時に適用する
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
val navController = rememberNavController() NavHost(navController = navController, startDestination = "home") { composable("home") { // ホーム画面のUI } composable("detail") { AnimatedVisibility( visible = true, enter = fadeIn() + slideIn(AnimationDirection.LeftToRight), exit = fadeOut() + slideOut(AnimationDirection.RightToLeft) ) { DetailScreen() } } } |
ナビゲーショングラフへの統合
Transition APIをナビゲーショングラフに統合するには、TransitionSpecやEnterExitTransitionを使って画面ごとに異なるアニメーションを指定します。
コード例:
|
1 2 3 4 5 6 7 8 9 |
val enterTransition = fadeIn() + slideIn(AnimationDirection.LeftToRight) val exitTransition = fadeOut() + slideOut(AnimationDirection.RightToLeft) NavHost(navController, startDestination = "home") { composable("home", enterTransition = enterTransition, exitTransition = exitTransition) { HomeScreen() } } |
GitHubサンプルコードによる即戦力化
Jetpack Composeのアニメーションを学ぶ際、GitHub上のサンプルコードは非常に有効です。ここでは提供されたサンプルプロジェクトの構成と重要なコード部分について解説します。
サンプルプロジェクトの構成確認
GitHubで公開されているサンプルプロジェクトには、以下のファイルやディレクトリが含まれています:
MainActivity.kt: アプリケーション全体の起点AnimationScreen.kt: Fade・Slideアニメーションの実装コードNavigationSample.kt: Navigation ComposeとTransition APIの統合例resources/anim/: カスタムアニメーションXMLファイル(一部)
プロジェクト構成は、Jetpack Compose標準ライブラリとTransition APIを用いたシンプルな実装がベースになっています。
主要なアニメーションコードの解読
AnimationScreen.kt内の代表的なコードスニペットは以下の通りです:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
val isFade by remember { mutableStateOf(false) } Box(modifier = Modifier.fillMaxSize()) { AnimatedVisibility( visible = isFade, enter = fadeIn(), exit = fadeOut() ) { Text("Fadeアニメーション中", fontSize = 24.sp) } Button(onClick = { isFade = !isFade }) { Text("Fadeをトグル") } } |
このコードは、ボタンクリックでisFadeの状態を切り替えることで、テキストの表示/非表示にFadeアニメーションを適用しています。
即戦力化のヒント: GitHubサンプルコードをローカルにクローンし、Android Studioで実行することで、学んだ知識をすぐに試すことができます。
まとめ
Jetpack ComposeでのアニメーションはTransition APIを活用すれば簡単ですが、注意点も複数あります。Modifier.slide(Direction)のような非公式APIや、UIのレイアウト変化への対応不足など、安定した実装のために十分な注意が必要です。GitHubサンプルコードの参考と併せて、公式ドキュメントを確認し、ベストプラクティスに沿った実装を目指してください。