1. 始める前に
この Codelab では、ユーザーが Button
コンポーザブルをタップしてサイコロを振る、インタラクティブな Dice Roller アプリを作成します。サイコロを振った結果は Image
コンポーザブルで画面に表示されます。
Kotlin で Jetpack Compose を使用してアプリ レイアウトを作成し、Button
コンポーザブルがタップされたときの動作を処理するビジネス ロジックを作成します。
前提条件
- Android Studio で基本的な Compose アプリを作成して実行できること。
- アプリで
Text
コンポーザブルを使用する方法を熟知していること。 - アプリの翻訳や文字列の再利用のために、テキストを文字列リソースに抽出する方法を理解していること。
- Kotlin プログラミングの基礎知識があること。
学習内容
- Compose を使用して Android アプリに
Button
コンポーザブルを追加する方法。 - Compose を使用して Android アプリの
Button
コンポーザブルに動作を追加する方法。 - Android アプリの
Activity
コードを開いて変更する方法。
作成するアプリの概要
- ユーザーにサイコロを振らせてその結果を表示する、Dice Roller というインタラクティブな Android アプリ。
必要なもの
- Android Studio がインストールされているパソコン
この Codelab が完了すると、アプリは次のようになります。
2. ベースラインを確立する
プロジェクトを作成する
- Android Studio で、[File] > [New] > [New Project] をクリックします。
- [New Project] ダイアログで [Empty Activity] を選択し、[Next] をクリックします。
- [Name] フィールドに「
Dice Roller
」と入力します。 - [Minimum SDK] フィールドでメニューから API レベル 24(Nougat)以上を選択し、[Finish] をクリックします。
3. レイアウト インフラストラクチャを作成する
プロジェクトをプレビューする
プロジェクトをプレビューするには:
- [Split] ペインまたは [Design] ペインで [Build & Refresh] をクリックします。
[Design] ペインにプレビューが表示されます。表示が小さくても、レイアウトを変更すると表示も変化するため心配する必要はありません。
サンプルコードを再構成する
Dice Roller アプリのテーマに近づけるために、生成されたコードの一部を変更する必要があります。
完成したアプリのスクリーンショットには、サイコロの画像と、サイコロを転がすためのボタンがありました。このアーキテクチャを反映するように、コンポーズ可能な関数を構成します。
サンプルコードを再構成するには:
GreetingPreview()
関数を削除します。@Composable
アノテーションを付けてDiceWithButtonAndImage()
関数を作成します。
このコンポーズ可能な関数はレイアウトの UI コンポーネントを表し、また、ボタンクリックと画像表示のロジックを保持します。
Greeting(name: String, modifier: Modifier = Modifier)
関数を削除します。@Preview
アノテーションと@Composable
アノテーションを付けてDiceRollerApp()
関数を作成します。
このアプリはボタンと画像のみで構成されているため、このコンポーズ可能な関数がアプリそのものだと考えてください。そのため、DiceRollerApp()
関数という名前になっています。
MainActivity.kt
@Preview
@Composable
fun DiceRollerApp() {
}
@Composable
fun DiceWithButtonAndImage() {
}
Greeting()
関数を削除したため、DiceRollerTheme()
ラムダ本文の Greeting("Android")
の呼び出しが赤色でハイライト表示されます。これは、コンパイラがその関数への参照を見つけられなくなったためです。
onCreate()
メソッドにあるsetContent{}
ラムダ内のコードをすべて削除します。setContent{}
ラムダ本体でDiceRollerTheme{}
ラムダを呼び出し、DiceRollerTheme{}
ラムダ内でDiceRollerApp()
関数を呼び出します。
MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
DiceRollerTheme {
DiceRollerApp()
}
}
}
DiceRollerApp()
関数で、DiceWithButtonAndImage()
関数を呼び出します。
MainActivity.kt
@Preview
@Composable
fun DiceRollerApp() {
DiceWithButtonAndImage()
}
修飾子を追加する
Compose は、Compose UI 要素の動作を装飾または変更する要素のコレクションである Modifier
オブジェクトを使用します。これを使用して Dice Roller アプリのコンポーネントの UI コンポーネントをスタイル設定します。
修飾子を追加するには:
Modifier
型のmodifier
引数を受け入れるようにDiceWithButtonAndImage()
関数を変更し、デフォルト値Modifier
を代入します。
MainActivity.kt
@Composable
fun DiceWithButtonAndImage(modifier: Modifier = Modifier) {
}
このコード スニペットではわかりにくい可能性があるため、詳しく説明します。この関数では、modifier
パラメータを渡すことができます。modifier
パラメータのデフォルト値は Modifier
オブジェクトであるため、メソッド シグネチャに = Modifier
部分があります。このパラメータのデフォルト値により、今後このメソッドを呼び出す場合、パラメータの値を渡すかどうかを決めることができます。独自の Modifier
オブジェクトを渡せば、UI の動作と装飾をカスタマイズできます。Modifier
オブジェクトを渡さない場合は、デフォルトの値(プレーンな Modifier
オブジェクト)であるとみなされます。この手法はどのようなパラメータにも適用できます。デフォルトの引数の詳細については、Default arguments をご覧ください。
DiceWithButtonAndImage()
コンポーザブルに修飾子パラメータが設定されたため、コンポーザブルが呼び出されたときに修飾子を渡します。DiceWithButtonAndImage()
関数のメソッド シグネチャが変更されたため、呼び出されたときに、必要な装飾を行ったModifier
オブジェクトを渡す必要があります。Modifier
クラスは、DiceRollerApp()
関数でコンポーザブルの装飾(動作の追加)を行います。この場合、DiceWithButtonAndImage()
関数に渡すModifier
オブジェクトに重要な装飾を追加します。
デフォルトがあるのに、どうしてわざわざ Modifier
引数を渡す必要があるのか、と思うかもしれません。これは、コンポーザブルが再コンポーズされる可能性があるためです。つまり実質、@Composable
メソッドのコードブロックが再実行されます。Modifier
オブジェクトがコードブロック内で作成された場合、再作成される可能性があるため、効率的ではありません。再コンポーズについては、この Codelab で後ほど説明します。
MainActivity.kt
DiceWithButtonAndImage(modifier = Modifier)
Modifier
オブジェクトにfillMaxSize()
メソッドを連結して、レイアウトが画面全体に表示されるようにします。
このメソッドは、利用可能なスペースをコンポーネントで埋めることを指定します。この Codelab ではこれまでに、完成した Dice Roller アプリの UI のスクリーンショットを確認しました。注目すべき特徴は、サイコロとボタンが画面の中央に配置されていることです。wrapContentSize()
メソッドは、利用可能なスペースが少なくとも内部のコンポーネントと同じ大きさである必要があるということを指定します。ただし fillMaxSize()
メソッドを使用しているため、利用可能なスペースよりレイアウト内部のコンポーネントが小さい場合は、Alignment
オブジェクトを wrapContentSize()
メソッドに渡して、利用可能なスペース内でコンポーネントをどのように配置するかを指定できます。
MainActivity.kt
DiceWithButtonAndImage(modifier = Modifier
.fillMaxSize()
)
wrapContentSize()
メソッドをModifier
オブジェクトに連結し、コンポーネントを中央に配置するための引数としてAlignment.Center
を渡します。Alignment.Center
は、コンポーネントを縦方向と横方向の両方で中央に配置することを指定します。
MainActivity.kt
DiceWithButtonAndImage(modifier = Modifier
.fillMaxSize()
.wrapContentSize(Alignment.Center)
)
4. 縦向きレイアウトを作成する
Compose では、縦向きレイアウトは Column()
関数を使用して作成します。
Column()
関数は、子を縦方向に並べて配置するコンポーザブル レイアウトです。想定されるアプリデザインでは、次のようにサイコロの画像が [Roll] ボタンの上方に表示されます。
縦向きレイアウトを作成するには:
DiceWithButtonAndImage()
関数にColumn()
関数を追加します。
DiceWithButtonAndImage()
メソッド シグネチャからmodifier
引数をColumn()
の修飾子引数に渡します。
modifier
引数により、Column()
関数内のコンポーザブルが、modifier
インスタンスで呼び出される制約に従うようになります。
horizontalAlignment
引数をColumn()
関数に渡し、Alignment.CenterHorizontally
の値に設定します。
これにより、列内の子が幅に対してデバイス画面の中央に配置されます。
MainActivity.kt
fun DiceWithButtonAndImage(modifier: Modifier = Modifier) {
Column (
modifier = modifier,
horizontalAlignment = Alignment.CenterHorizontally
) {}
}
5. ボタンを追加する
strings.xml
ファイルに文字列を追加し、Roll
値に設定します。
res/values/strings.xml
<string name="roll">Roll</string>
Column()
のラムダ本体にButton()
関数を追加します。
MainActivity.kt
ファイルで、関数のラムダ本体のButton()
にText()
関数を追加します。roll
文字列の文字列リソース ID をstringResource()
関数に渡し、結果をText
コンポーザブルに渡します。
MainActivity.kt
Column(
modifier = modifier,
horizontalAlignment = Alignment.CenterHorizontally
) {
Button(onClick = { /*TODO*/ }) {
Text(stringResource(R.string.roll))
}
}
6. 画像を追加する
このアプリに不可欠なもう一つのコンポーネントは、ユーザーが [Roll] ボタンをタップすると結果を表示する、サイコロの画像です。Image
コンポーザブルを使用して画像を追加しますが、画像リソースが必要です。そのため、まずはこのアプリのために用意された画像をダウンロードする必要があります。
サイコロの画像をダウンロードする
- こちらの URL を開いて、サイコロの画像を ZIP ファイル形式でパソコンにダウンロードします。ダウンロードが完了するまで待機します。
パソコンに保存したファイルを見つけます。通常は Downloads フォルダにあります。
- ZIP ファイルを解凍すると、1~6 の目を持つサイコロの画像ファイルが 6 つ入った、新しい
dice_images
フォルダが作成されます。
アプリにサイコロの画像を追加する
- Android Studio で、[View] > [Tool Windows] > [Resource Manager] をクリックします。
- [+] > [Import Drawables] をクリックしてファイル ブラウザを開きます。
- 6 つのサイコロの画像フォルダを見つけて選択し、アップロードに進みます。
アップロードされた画像は次のようになります。
- [Next] をクリックします。
[Import drawables] ダイアログが表示され、ファイル構造内のリソース ファイルの移動先が表示されます。
- [Import] をクリックして、6 つの画像をインポートすることを確認します。
画像が [Resource Manager] ペインに表示されます。
お疲れさまでした。次のタスクでは、これらの画像をアプリで使用します。
Image
コンポーザブルを追加する
サイコロの画像は [Roll] ボタンの上に表示されます。Compose は、その性質上、UI コンポーネントを順番に配置します。言い換えると、最初に宣言されたコンポーザブルが最初に表示されます。つまり、最初に宣言されたコンポーザブルが、その後に宣言されたコンポーザブルの上または前に表示されます。Column
コンポーザブル内のコンポーザブルは、デバイス上で上下に重なり合って表示されます。このアプリでは、Column
を使用してコンポーザブルを縦に積み重ねます。そのため、Column()
関数内で最初に宣言されたコンポーザブルは、同じ Column()
関数内で後に宣言されたコンポーザブルより前に表示されます。
Image
コンポーザブルを追加するには:
Column()
関数本体で、Button()
関数の前にImage()
関数を作成します。
MainActivity.kt
Column(
modifier = modifier,
horizontalAlignment = Alignment.CenterHorizontally
) {
Image()
Button(onClick = { /*TODO*/ }) {
Text(stringResource(R.string.roll))
}
}
Image()
関数にpainter
引数を渡し、ドローアブル リソース ID 引数を受け入れるpainterResource
値を代入します。ここでは、リソース IDR.drawable.dice_1
引数を渡します。
MainActivity.kt
Image(
painter = painterResource(R.drawable.dice_1)
)
- アプリで画像を作成するときは、必ず「コンテンツの説明」を用意してください。コンテンツの説明は、Android 開発における重要な要素です。ユーザー補助を強化するために、それぞれの UI コンポーネントに説明を付けます。コンテンツの説明について詳しくは、各 UI 要素について説明するをご覧ください。コンテンツの説明をパラメータとして画像に渡すことができます。
MainActivity.kt
Image(
painter = painterResource(R.drawable.dice_1),
contentDescription = "1"
)
これで、必要な UI コンポーネントがすべて揃いました。しかし、Button
と Image
が少し詰まっています。
- これを修正するには、
Image
コンポーザブルとButton
コンポーザブルの間にSpacer
コンポーザブルを追加します。Spacer
は、パラメータとしてModifier
を受け取ります。この場合、Image
がButton
の上にあるため、両者の間に縦方向のスペースが必要です。そのため、Modifier
の高さを設定してSpacer
に適用できます。高さを16.dp
に設定してみます。通常、dp ディメンションは4.dp
単位で変更されます。
MainActivity.kt
Spacer(modifier = Modifier.height(16.dp))
- [Preview] ペインで、[Build & Refresh] をクリックします。
次の画像のように表示されます。
7. サイコロを振るロジックを作成する
必要なコンポーザブルがすべて揃ったため、ボタンをタップするとサイコロが振られるようにアプリを変更します。
ボタンをインタラクティブにする
DiceWithButtonAndImage()
関数で、Column()
関数の前にresult
変数を作成し、1
値と等しくなるように設定します。Button
コンポーザブルを見てみると、内部にコメント/*TODO*/
を含む中かっこのペアに設定されたonClick
パラメータが渡されています。ここでは、この中かっこはラムダというものを表します。中かっこの内部がラムダ本体です。関数を引数として渡す場合については、「コールバック」とも呼ばれます。
MainActivity.kt
Button(onClick = { /*TODO*/ })
ラムダは関数リテラルです。他の関数と同様に機能しますが、fun
キーワードで個別に宣言するのではなく、インラインで記述し、式として渡します。Button
コンポーザブルは、関数が onClick
パラメータとして渡されることを想定しています。ここはラムダを使用するために最適です。このセクションではラムダ本体を記述します。
Button()
関数で、onClick
パラメータのラムダ本体の値からコメント/*TODO*/
を削除します。- サイコロの出目はランダムです。それをコードに反映させるには、正しい構文を使用して乱数を生成する必要があります。Kotlin では、数値範囲に対して
random()
メソッドを使用できます。onClick
ラムダ本体で、result
変数を 1~6 の範囲に設定し、その範囲に対してrandom()
メソッドを呼び出します。Kotlin では、範囲の最初の数字と最後の数字の間にピリオドを 2 つ入れることで範囲を指定することに留意してください。
MainActivity.kt
fun DiceWithButtonAndImage(modifier: Modifier = Modifier) {
var result = 1
Column(
modifier = modifier,
horizontalAlignment = Alignment.CenterHorizontally
) {
Image(
painter = painterResource(R.drawable.dice_1),
contentDescription = "1"
)
Spacer(modifier = Modifier.height(16.dp))
Button(onClick = { result = (1..6).random() }) {
Text(stringResource(R.string.roll))
}
}
}
これでボタンをタップできるようになりましたが、ボタンをタップしても見た目は変わりません。機能を構築する必要があります。
Dice Roller アプリに条件を追加する
前のセクションでは、result
変数を作成し、1
値にハードコードしました。最終的に、[Roll] ボタンをタップすると result
変数の値がリセットされ、表示される画像が決定されます。
コンポーザブルは、デフォルトではステートレスです。つまり、値は保持されず、システムがいつでも再コンポーズでき、結果的に値がリセットされます。しかし、Compose ではこれを簡単に回避できます。コンポーズ可能な関数は、remember
コンポーザブルを使用してオブジェクトをメモリに格納できます。
result
変数をremember
コンポーザブルにします。
remember
コンポーザブルには、渡す関数が必要です。
remember
コンポーザブル本体で、mutableStateOf()
関数を渡してから、その関数に1
引数を渡します。
mutableStateOf()
関数はオブザーバブルを返します。オブザーバブルについては後ほど詳しく説明しますが、ここでは基本的に、result
変数の値が変更されると再コンポーズがトリガーされ、結果の値が反映されて、UI が更新されます。
MainActivity.kt
var result by remember { mutableStateOf(1) }
ボタンをタップすると result
変数が乱数の値で更新されます。
result
変数を使用して、表示する画像を決定できるようになりました。
result
変数のインスタンス化の下で、不変のimageResource
変数を作成してresult
変数を受け入れるwhen
式に設定し、考えられる結果をそれぞれのドローアブルに設定します。
MainActivity.kt
val imageResource = when (result) {
1 -> R.drawable.dice_1
2 -> R.drawable.dice_2
3 -> R.drawable.dice_3
4 -> R.drawable.dice_4
5 -> R.drawable.dice_5
else -> R.drawable.dice_6
}
Image
コンポーザブルのpainterResource
パラメータに渡される ID をR.drawable.dice_1
ドローアブルからimageResource
変数に変更します。result
変数をtoString()
で文字列に変換し、contentDescription
として渡すことで、result
変数の値を反映するようにImage
コンポーザブルのcontentDescription
パラメータを変更します。
MainActivity.kt
Image(
painter = painterResource(imageResource),
contentDescription = result.toString()
)
- アプリを実行します。
Dice Roller アプリが完全に機能するようになりました。
8. 解答コードを取得する
この Codelab の完成したコードをダウンロードするには、次の git コマンドを使用します。
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-dice-roller.git
または、リポジトリを ZIP ファイルとしてダウンロードし、Android Studio で開くこともできます。
解答コードを確認する場合は、GitHub で表示します。
- プロジェクト用に提供されている GitHub リポジトリ ページに移動します。
- ブランチ名が Codelab で指定されたブランチ名と一致していることを確認します。たとえば、次のスクリーンショットでは、ブランチ名は main です。
- プロジェクトの GitHub ページで、[Code] ボタンをクリックすると、ポップアップが表示されます。
- ポップアップで、[Download ZIP] をクリックして、プロジェクトをパソコンに保存します。ダウンロードが完了するまで待ちます。
- パソコンに保存したファイルを見つけます([ダウンロード] フォルダなど)。
- ZIP ファイルをダブルクリックして展開します。プロジェクト ファイルが入った新しいフォルダが作成されます。
Android Studio でプロジェクトを開く
- Android Studio を起動します。
- [Welcome to Android Studio] ウィンドウで、[Open] をクリックします。
注: Android Studio がすでに開いている場合は、メニューから [File] > [Open] を選択します。
- ファイル ブラウザで、展開したプロジェクト フォルダがある場所([ダウンロード] フォルダなど)に移動します。
- そのプロジェクト フォルダをダブルクリックします。
- Android Studio でプロジェクトが開かれるまで待ちます。
- 実行ボタン をクリックして、アプリをビルドし、実行します。期待どおりにビルドされることを確認します。
9. おわりに
Compose を使用して Android 用のインタラクティブな Dice Roller アプリを作成しました。
まとめ
- コンポーズ可能な関数を定義する。
- Composition でレイアウトを作成する。
Button
コンポーザブルでボタンを作成します。drawable
リソースをインポートする。Image
コンポーザブルを使用して画像を表示する。- コンポーザブルでインタラクティブな UI を作成する。
remember
コンポーザブルを使用して、Composition 内のオブジェクトをメモリに保存する。mutableStateOf()
関数で UI を更新して、オブザーバブルを作成する。