1. 始める前に
この Codelab では、Android アプリにシンプルなアニメーションを追加する方法について説明します。アニメーションを使用することで、アプリはよりインタラクティブかつ魅力的で、ユーザーが簡単に解釈できるものになります。画面上いっぱいに情報が表示されていても、変更箇所のそれぞれにアニメーションを適用すれば、ユーザーは何が変更されたかを容易に把握できます。
アプリのユーザー インターフェースで使用できるアニメーションにはさまざまなものがあります。アイテムの表示と非表示をフェードイン / フェードアウトで表現したり、アイテムを画面上で(時には画面外に出ていくように)動かしたり、注意を引く仕方で変形させたりできます。これにより、アプリの UI が表現力豊かなものになり、使いやすくなります。
また、アニメーションでアプリの外観を洗練させることができます。これはエレガントな印象を与えるだけでなく、ユーザーにもメリットがあります。
あるタスクに対する報酬をユーザーに提供する際、それをアニメーションで表現すれば、ユーザー ジャーニーにおける重要な場面がより意味のあるものになります。 | |
キーパッドの入力操作に反応するアニメーション要素で、操作が成功したかどうかを示すフィードバックを提供できます。 | |
リストアイテムにアニメーションを取り入れて、コンテンツが読み込み中であることを視覚的に示します。 | |
スワイプで開く操作をアニメーションで表示することで、必要となる操作を誘導できます。 | |
アイコンをアニメーションにすることで、そのアイコンの意味を面白い形で補完したり、加えたりできます。 |
前提条件
- 関数、ラムダ、ステートレス コンポーザブルなど、Kotlin に関する知識。
- Jetpack Compose でレイアウトを作成する方法に関する基本的な知識。
- Jetpack Compose でリストを作成する方法に関する基本的な知識。
- マテリアル デザインに関する基本的な知識。
学習内容
- Jetpack Compose で簡単なスプリング アニメーションを作成する方法。
作成するアプリの概要
- Jetpack Compose でのマテリアル テーマ設定の Codelab で作成した Woof アプリに対し、ユーザーの操作に応答するシンプルなアニメーションを追加します。
必要なもの
- Android Studio の最新バージョン
- スターター コードをダウンロードするためのインターネット接続。
2. Code-Along 動画を見る(省略可)
コースの講師が Codelab を完了する様子を視聴する場合は、以下の動画を再生してください。
動画を拡大して全画面表示にすることをおすすめします(動画の右下隅のアイコン を使用します)。そうすれば、Android Studio とコードがもっとはっきり見えるようになります。
このステップは省略可能です。動画をスキップして、すぐに Codelab の学習を開始することもできます。
3. アプリの概要
Jetpack Compose でのマテリアル テーマ設定の Codelab では、マテリアル デザインを使用し、各犬とそれぞれの情報のリストを表示する Woof アプリを作成しました。
この Codelab では、Woof アプリにアニメーションを追加します。また、それぞれの犬の好きなことに関する情報を追加して、リストアイテムを展開するとその情報が表示されるようにします。さらに、展開時のリストアイテムをアニメーション化するためのスプリング アニメーションも追加します。
スターター コードを取得する
まず、スターター コードをダウンロードします。
または、GitHub リポジトリのクローンを作成してコードを入手することもできます。
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-woof.git $ cd basic-android-kotlin-compose-training-woof $ git checkout material
コードは Woof app
GitHub リポジトリで確認できます。
4. 展開アイコンを追加する
スプリング アニメーションを作成する最初のステップとして、展開アイコン を追加します。展開アイコンは、ユーザーがリストアイテムを展開するためのボタンになります。
アイコン
アイコンとは、目的の機能を視覚的に伝達することで、ユーザーがユーザー インターフェースを理解することを助けるシンボルです。多くの場合、アイコンはユーザーがよく知っていると思われる現実の物体から着想を得ています。ほとんどのアイコンのデザインは、ユーザーが即座に理解できる最小限のレベルまで簡略化されています。たとえば、現実の鉛筆は字を書くために使用されるので、通常、鉛筆のアイコンは作成や編集を表します。
|
マテリアル デザインでは、多くのニーズに対応した多数のアイコンが一般的なカテゴリ別に用意されています。
Gradle 依存関係を追加する
プロジェクトに material-icons-extended
ライブラリ依存関係を追加します。このライブラリの Icons.Filled.ExpandLess
アイコン と
Icons.Filled.ExpandMore
アイコン を使用します。
- [Project] ペインで、[Gradle Scripts] > [build.gradle (Module: Woof.app)] を開きます。
build.gradle (Module: Woof.app)
ファイルの最後までスクロールします。dependencies{}
ブロックに次の行を追加します。
implementation "androidx.compose.material:material-icons-extended"
アイコン コンポーザブルを追加する
マテリアル アイコン ライブラリの展開アイコンを表示する関数を追加して、これをボタンとして使用します。
MainActivity.kt
で、DogItem()
関数の後に、DogItemButton()
という新しいコンポーズ可能な関数を作成します。- 展開された状態を示す
Boolean
、ボタンのクリック イベントのラムダ式、オプションのModifier
を次のように渡します。
@Composable
private fun DogItemButton(
expanded: Boolean,
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
}
DogItemButton()
関数内に、onClick
という名前付きパラメータを受け入れるIconButton()
コンポーザブルを追加します。これは、アイコンが押されたときに呼び出される末尾のラムダ構文を使用するラムダで、これを渡されるonClick
引数に設定します。
@Composable
private fun DogItemButton(
// ...
) {
IconButton(onClick = onClick) {
}
}
IconButton()
ラムダブロック内に、imageVector
という名前付きパラメータを持つIcon
コンポーザブルを追加し、Icons.Filled.ExpandMore
に設定します。これが、リストアイテムの末尾に表示されるアイコンボタンになります。Android Studio に
Icon()
コンポーザブルのパラメータに関する警告が表示されますが、これは後で修正します。- 名前付きパラメータ
tint
を追加し、アイコンの色をMaterialTheme.colors.secondary
に設定します。名前付きパラメータcontentDescription
を追加し、文字列リソースR.string.expand_button_content_description
に設定します。
import androidx.compose.material.Icon
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ExpandMore
IconButton(onClick = onClick) {
Icon(
imageVector = Icons.Filled.ExpandMore,
tint = MaterialTheme.colors.secondary,
contentDescription = stringResource(R.string.expand_button_content_description)
)
}
アイコンを表示する
DogItemButton()
コンポーザブルをレイアウトに追加して表示します。
- リストアイテムの展開の状態を保存するため、コンポーズ可能な関数
DogItem()
の先頭にvar
を追加します。初期値をfalse
に設定します。
var expanded by remember { mutableStateOf(false) }
- リストアイテム内にアイコンボタンを表示するには、
Row
ブロックの最後のコンポーズ可能な関数DogItem()
で、DogInformation()
を呼び出した後にDogItemButton()
を呼び出します。コールバック用にexpanded
状態と空のラムダを渡します。このラムダ関数は、後のステップで定義します。
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
DogIcon(dog.imageResourceId)
DogInformation(dog.name, dog.age)
DogItemButton(
expanded = expanded,
onClick = { }
)
}
- [Design] ペインでプレビューの [Build & Refresh] をクリックします。
展開ボタンがリストアイテムの末端に配置されていません。これは次のステップで修正します。
展開ボタンを配置する
展開ボタンをリストアイテムの末端に配置するには、Modifier.weight()
属性を使用してスペーサーをレイアウトに追加する必要があります。
Woof アプリでは、各リストアイテムの行に、犬の写真、犬の情報、展開ボタンが表示されます。展開ボタンが適切な位置に配置されるよう、そのボタンアイコンの前に、重みが 1f
の Spacer
コンポーザブルを追加します。このスペーサーが、該当の行内で唯一重みを持つ子要素であるため、重みを持たない他の子要素の長さが決定された後に、行内の残りのスペースを埋めることになります。
スペーサーをリストアイテムの行に追加する
- コンポーズ可能な関数
DogItem()
のRow
ブロックの末尾に、Spacer
を追加します。weight(1f)
でModifier
を渡します。Modifier.weight()
により、行の残りのスペースがスペーサーで埋められます。
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
DogIcon(dog.imageResourceId)
DogInformation(dog.name, dog.age)
Spacer(Modifier.weight(1f))
DogItemButton(
expanded = expanded,
onClick = { }
)
}
- [Design] ペインでプレビューの [Build & Refresh] をクリックします。展開ボタンがリストアイテムの末端に表示されるようになりました。
5. 好きなことを表示するコンポーザブルを追加する
このタスクでは、Text
コンポーザブルを追加して犬の好きなことの情報を表示します。
- 犬の好きなことの文字列リソース ID とオプションの
Modifier
を受け取る、DogHobby()
という新しいコンポーズ可能な関数を作成します。 DogHobby()
関数内で次のパディング属性を含む列を作成して、列と子コンポーザブルの間にスペースを追加します。
import androidx.annotation.StringRes
@Composable
fun DogHobby(@StringRes dogHobby: Int, modifier: Modifier = Modifier) {
Column(
modifier = modifier.padding(
start = 16.dp,
top = 8.dp,
bottom = 16.dp,
end = 16.dp
)
) { }
}
- 列ブロック内に 2 つの
Text
コンポーザブルを追加します。一つは好きなことの情報の上に About テキストを表示するコンポーザブルで、もう一つは好きなことの情報を表示するコンポーザブルです。
- About のテキストは、スタイルを
h3
(見出し 3)に、色をonBackground
に設定します。犬の好きなことの情報については、スタイルをbody1
に設定します。
Column(
modifier = modifier.padding(
//..
)
) {
Text(
text = stringResource(R.string.about),
style = MaterialTheme.typography.h3,
)
Text(
text = stringResource(dogHobby),
style = MaterialTheme.typography.body1,
)
}
- 完成したコンポーズ可能な関数
DogHobby()
は次のようになります。
@Composable
fun DogHobby(@StringRes dogHobby: Int, modifier: Modifier = Modifier) {
Column(
modifier = modifier.padding(
start = 16.dp,
top = 8.dp,
bottom = 16.dp,
end = 16.dp
)
) {
Text(
text = stringResource(R.string.about),
style = MaterialTheme.typography.h3
)
Text(
text = stringResource(dogHobby),
style = MaterialTheme.typography.body1
)
}
}
DogHobby()
コンポーザブルを表示するには、DogItem()
内でColumn
によりRow
をラップします。Row
の後に、2 番目の子としてDogHobby()
関数を呼び出し、dog.hobbies
をパラメータとして渡します。
Column() {
Row(
//..
) {
//..
}
DogHobby(dog.hobbies)
}
完全な DogItem()
関数は次のようになります。
@Composable
fun DogItem(dog: Dog, modifier: Modifier = Modifier) {
var expanded by remember { mutableStateOf(false) }
Card(
elevation = 4.dp,
modifier = modifier.padding(8.dp)
) {
Column() {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
DogIcon(dog.imageResourceId)
DogInformation(dog.name, dog.age)
Spacer(Modifier.weight(1f))
DogItemButton(
expanded = expanded,
onClick = { expanded = !expanded },
)
}
DogHobby(dog.hobbies)
}
}
}
- [Design] ペインでプレビューの [Build & Refresh] をクリックします。犬の好きなことが表示されるようになりました。
6. ボタンのクリックで好きなことの表示 / 非表示を切り替える
アプリの各リストアイテムに展開ボタンが表示されるようになりましたが、ボタンにはまだ何の機能もありません。このセクションでは、ユーザーが展開ボタンをクリックしたときに、好きなことの情報を表示または非表示にするオプションを追加します。
- コンポーズ可能な関数
DogItem()
のDogItemButton()
関数呼び出し内でonClick()
ラムダ式を定義し、ボタンがクリックされたときにexpanded
状態値(ブール値)をtrue
に変更し、ボタンが再度クリックされるとfalse
に戻るようにします。
DogItemButton(
expanded = expanded,
onClick = { expanded = !expanded }
)
DogItem()
関数で、DogHobby()
関数呼び出しをexpanded
ブール値のif
チェックでラップします。
// No need to copy over
@Composable
fun DogItem(dog: Dog, modifier: Modifier = Modifier) {
var expanded by remember { mutableStateOf(false) }
Card(
//..
) {
Column() {
Row(
//..
) {
//..
}
if (expanded) {
DogHobby(dog.hobbies)
}
}
}
}
上のコードでは、expanded
の値が true
である場合に限り、犬の好きなことに関する情報が表示されます。
- プレビューで UI の外観を確認できます。また、UI を操作することもできます。UI プレビューを操作するには、[Design] ペインの右上にあるインタラクティブ モード ボタン
をクリックします。クリックすると、インタラクティブ モードでプレビューが開始されます。
- 展開ボタンをクリックして、プレビューを操作します。展開ボタンをクリックすると、犬の好きなことに関する情報の表示 / 非表示が切り替わります。
このままでは、リストアイテムを展開しても、展開ボタンのアイコンは変化しません。ユーザー エクスペリエンスを向上させるために、ExpandMore
で下向きの矢印 を表示し、
ExpandLess
で上向きの矢印 を表示するようにアイコンを変更します。
DogItemButton()
関数で、expanded
の状態を基準とするようにimageVector
の値を次のように更新します。
import androidx.compose.material.icons.filled.ExpandLess
@Composable
private fun DogItemButton(
//..
) {
IconButton(onClick = onClick) {
Icon(
imageVector = if (expanded) Icons.Filled.ExpandLess else Icons.Filled.ExpandMore,
//..
)
}
}
- デバイスまたはエミュレータでアプリを実行するか、もう一度プレビューでインタラクティブ モードを使用します。展開ボタンが、
ExpandMore
のアイコンと
ExpandLess
のアイコンを交互に表示するようになりました。
これで、アイコンの更新が完了しました。
リストアイテムを展開した際に、高さが瞬間的に変わったことに気づいたでしょうか。高さが瞬間的に変わってしまうと、洗練されたアプリには見えません。次はこの問題を解決するために、アプリにアニメーションを追加します。
7. アニメーションを追加する
アニメーションを使用すると、アプリ内で何が起こっているのかを、視覚的な手掛かりを通じてユーザーにわかりやすく伝えることができます。アニメーションは、新しいコンテンツがロードされたときや、新しいアクションが利用可能になったときなど、UI の状態が変化したときに特に役立ちます。また、アニメーションにより、アプリの外観が洗練されたものになります。
このセクションでは、スプリング アニメーションを追加して、リストアイテムの高さの変化をアニメーションにします。
スプリング アニメーション
スプリング アニメーションは、ばねの力で動く物理学ベースのアニメーションです。スプリング アニメーションでは、適用されるばねの力に基づいて動きの値と速度が計算されます。
たとえば、画面上でアプリアイコンをドラッグし、アイコンから指を離すと、目に見えない力によってアイコンが元の位置に勢いよく戻ります。
次のアニメーションは、ばねの効果を示しています。アイコンから指を離すと、アイコンがばねのように再び元の場所に戻ります。
ばねの効果
ばねの力は、次の 2 つの特性に応じて決まります。
- 減衰率: ばねの弾力性。
- 剛性のレベル: ばねの固さ。ばねの収縮の速さを示す。
以下は、減衰率と剛性のレベルが異なるアニメーションの例です。
|
|
|
|
それでは、スプリング アニメーションをアプリに追加しましょう。
MainActivity.kt
のDogItem()
で、modifier
パラメータをColumn
レイアウトに追加します。
@Composable
fun DogItem(dog: Dog, modifier: Modifier = Modifier) {
//..
Card(
//..
) {
Column(
modifier = Modifier
){
//..
}
}
}
コンポーズ可能な関数 DogItem()
内の DogHobby()
関数呼び出しを確認します。このコンポジションには、expanded
のブール値に基づく、犬の好きなことに関する情報が含まれています。リストアイテムの高さは、好きなことの情報の表示 / 非表示に応じて変化します。animateContentSize
修飾子を使用して、新しい高さと古い高さの間の遷移を組み込みます。
// No need to copy over
@Composable
fun DogItem(...) {
//..
if (expanded) {
DogHobby(dog.hobbies)
}
}
- 修飾子に
animateContentSize
修飾子を連結して、サイズ(リストアイテムの高さ)の変化をアニメーションにします。
import androidx.compose.animation.animateContentSize
Column(
modifier = Modifier
.animateContentSize()
) {
//..
}
この実装により、アプリのリストアイテムの高さをアニメーション化することはできるものの、そのアニメーションは実際のアプリで識別するのが困難なほど微妙なものです。この問題を解決するために、オプションの animationSpec
パラメータを使用して、アニメーションをカスタマイズします。
animateContentSize()
関数呼び出しにanimationSpec
パラメータを追加します。DampingRatioMediumBouncy
パラメータとStiffnessLow
パラメータを使用し、このパラメータをスプリング アニメーションに設定します。
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
Column(
modifier = Modifier
.animateContentSize(
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
)
)
)
- [Design] ペインでプレビューの [Build & Refresh] をクリックし、インタラクティブ モードを使用するか、エミュレータまたはデバイスでアプリを実行してスプリング アニメーションの動作を確認します。
エミュレータまたはデバイスでアプリを再実行して、アニメーション付きの美しいアプリを確認してみましょう。
8. (省略可)他のアニメーションを試す
animate*AsState
animate*AsState()
関数は、Compose で単一の値をアニメーション化するための最もシンプルなアニメーション API です。終了値(またはターゲット値)を指定するだけで、API は現在の値から指定された終了値までのアニメーションを開始します。
Compose には、Float
、Color
、Dp
、Size
、Offset
、Int
など、多数のデータ型に使える animate*AsState()
関数が用意されています。汎用型を受け入れる animateValueAsState()
を使用すると、他のデータ型のサポートを簡単に追加できます。
リストアイテムが展開されるときの色をアニメーション化するには、animateColorAsState()
関数を使用します。
ヒント:
- 色を宣言し、その初期化を
animateColorAsState()
関数に委任します。 expanded
のブール値に応じて、targetValue
名前付きパラメータを設定します。
@Composable
fun DogItem(dog: Dog, modifier: Modifier = Modifier) {
//..
val color by animateColorAsState(
targetValue = if (expanded) Green25 else MaterialTheme.colors.surface,
)
Card(
//..
) {...}
}
- 上のコード例でバックグラウンド修飾子として宣言した
color
をColumn
に設定します。
@Composable
fun DogItem(dog: Dog, modifier: Modifier = Modifier) {
//..
Card(
//..
) {
Column(
modifier = Modifier
.animateContentSize(
//..
)
)
.background(color = color)
) {...}
}
9. 解答コードを取得する
この Codelab の完成したコードをダウンロードするには、次の git コマンドを使用します。
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-woof.git
または、リポジトリを ZIP ファイルとしてダウンロードし、Android Studio で開くこともできます。
解答コードを確認する場合は、GitHub で表示します。
10. まとめ
これで、犬に関する情報の表示 / 非表示を切り替えるボタンを追加できました。スプリング アニメーションを使ってユーザー エクスペリエンスを向上させました。また、[Design] ペインでインタラクティブ モードを使用する方法も学習しました。
別の種類の Jetpack Compose アニメーションを試すことも可能です。作成したら、#AndroidBasics を付けて、ソーシャル メディアで共有しましょう。