1. 始める前に
このパスウェイで、アプリにボタンを追加する方法と、アプリをボタンクリックに応答するように変更する方法を学習しました。次は、アプリを作成して、学んだことを実践しましょう。
作成するのは Lemonade アプリというアプリです。まず、Lemonade アプリの要件を確認し、アプリの外観と動作を把握しましょう。挑戦したい人は、自力で作成してもかまいません。行き詰まっても、以降のセクションに目を通せば、問題を分割して段階的に進める方法のヒントや指針が得られます。
自分のやりやすいペースで練習問題に取り組みましょう。必要なだけ時間をかけ、アプリの機能を部分ごとに作成してください。Lemonade アプリの解答コードは最後に用意していますが、解答を確認する前に自分でアプリを作成してみることをおすすめします。用意されている解答は Lemonade アプリを作成する唯一の方法ではないので、アプリの要件が満たされていれば、別の方法で作成してもまったく問題ありません。
前提条件
- Compose で、テキスト コンポーザブルと画像コンポーザブルを使用するシンプルな UI レイアウトを作成できること
- ボタンクリックに応答するインタラクティブ アプリを作成できること
- コンポジションと再コンポジションに関する基本的な知識があること
- Kotlin プログラミング言語の基本(関数、変数、条件、ラムダなど)に精通していること
必要なもの
- Android Studio がインストールされた、インターネットに接続できるパソコン。
2. アプリの概要
あなたは、デジタル上でレモネードを作るという私たちのビジョンを一緒に実現しようとしています。プロジェクトの目標は、画面上の画像をタップするとレモンを絞り、グラス 1 杯分のレモネードを作るという、シンプルでインタラクティブなアプリを作成することです。このアプリはメタファーとして、あるいはちょっとした暇つぶしの方法として考えてください。
アプリの動作は次のとおりです。
- 最初に起動されたとき、レモンの木を表示します。そこには、レモンの木の画像をタップし、木からレモンを「選ぶ」ように促すラベルがあります。
- レモンの木がタップされたら、レモンを表示します。レモンをタップして「絞り」、レモネードを作るように促します。レモンを絞るには、数回タップする必要があります。レモンを絞るのに必要なタップの回数は毎回異なり、2 から 4 までのランダムに生成された数となります。
- 必要な回数レモンがタップされたら、新鮮なレモネードを表示します。グラスをタップしてレモネードを「飲む」ようユーザーに求めます。
- レモネードのグラスがタップされたら、空のグラスを表示します。やり直すには、空のグラスをタップするようユーザーに求めます。
- 空のグラスがタップされたら、レモンの木を表示して、この過程をもう一度始めます。レモネードがおかわりできます。
以下は、アプリの外観を大きめのスクリーンショットで表したものです。
レモネードを作成するステップごとに、画面上の画像とテキストラベルは異なり、クリックに対する応答も異なります。たとえば、レモンの木をタップすると、レモンが表示されます。
ここでは、アプリの UI レイアウトを作成し、レモネードを作るためのすべてのステップをユーザーが完了するためのロジックを実装することです。
3. 始める
プロジェクトの作成
Android Studio で、Empty Activity テンプレートを使用して、以下のプロジェクトを作成します。
- Name: Lemonade
- Package name: com.example.lemonade
- Minimum SDK: 24
アプリが正常に作成され、プロジェクトがビルドされたら、次のセクションに進みます。
画像を追加する
Lemonade アプリで使用する 4 つのベクター型ドローアブル ファイルが用意されています。
次の手順でファイルを入手します。
- アプリの画像の ZIP ファイルをダウンロードします。
- ZIP ファイルをダブルクリックします。このステップでは、画像がフォルダに展開されます。
- 画像をアプリの
drawable
フォルダに追加します。方法がわからない場合は、インタラクティブな Dice Roller アプリを作成する Codelab で確認してください。
プロジェクト フォルダは次のスクリーンショットのように、lemon_drink.xml
、lemon_restart.xml
、lemon_squeeze.xml
、lemon_tree.xml
のアセットが res > drawable ディレクトリに表示されるようにしてください。
- ベクター型ドローアブル ファイルをダブルクリックして、画像プレビューを表示します。
- [Design] ペイン([Code] または [Split] ビューではありません)を選択すると、画像がビューの幅いっぱいに表示されます。
アプリに画像ファイルが追加されると、コード内で参照できるようになります。たとえば、ベクター型ドローアブル ファイルの名前が lemon_tree.xml
である場合、Kotlin コードでは、R.drawable.lemon_tree
という形式のリソース ID を使ってそのドローアブルを参照できます。
文字列リソースを追加する
プロジェクトの res > values > strings.xml ファイルに、次の文字列を追加します。
Tap the lemon tree to select a lemon
Keep tapping the lemon to squeeze it
Tap the lemonade to drink it
Tap the empty glass to start again
プロジェクトには、以下の文字列も必要です。ユーザー インターフェースの画面には表示されませんが、アプリ内の画像の内容説明で、画像を説明するために使用されます。以下の文字列をアプリの strings.xml
ファイルに追加します。
Lemon tree
Lemon
Glass of lemonade
Empty glass
アプリで文字列リソースを宣言する方法がわからない場合は、インタラクティブな Dice Roller アプリを作成する Codelab または文字列で確認してください。各文字列リソースに、含まれる値を表す適切な識別名を付けましょう。たとえば、文字列 "Lemon"
の場合、strings.xml
ファイルで識別名 lemon_content_description
で宣言すると、コードではリソース ID R.string.lemon_content_description
で参照できます。
レモネードを作る手順
これで、アプリの実装に必要な文字列リソースと画像アセットを用意できました。各ステップの概要と表示される画面は次のとおりです。
ステップ 1:
- テキスト:
Tap the lemon tree to select a lemon
- 画像: レモンの木(
lemon_tree.xml
)
ステップ 2:
- テキスト:
Keep tapping the lemon to squeeze it
- 画像: レモン(
lemon_squeeze.xml
)
ステップ 3:
- テキスト:
Tap the lemonade to drink it
- 画像: グラスいっぱいのレモネード(
lemon_drink.xml
)
ステップ 4:
- テキスト:
Tap the empty glass to start again
- 画像: 空のグラス(
lemon_restart.xml
)
見た目を整える
アプリの見た目を完成版のスクリーンショットに近づけるために、見た目の調整をいくつか行います。
- テキストのフォントサイズを大きくして、デフォルトよりも大きくします(
18sp
など)。 - テキストラベルとその下の画像の間にはスペースを入れて、近づきすぎないようにします(
16dp
など)。 - ボタンにアクセント カラーを設定し、角を少し丸くして、画像がタップ可能であることをユーザーが理解できるようにします。
挑戦したい人は、アプリの残りの部分を必要な動作の説明をもとに作成しましょう。さらに手引きが必要な場合は、次に進んでください。
4. アプリの作成方法を計画する
アプリを作成する際は、最低限動作するバージョンから始めることをおすすめします。その後に、完成するまで必要な機能を段階的に追加しましょう。すべての機能の中から、最初に作成できる機能を見つけましょう。
Lemonade アプリでは、アプリの主要な部分が段階的に変化し、毎回別の画像とテキストラベルが表示されます。どの程度絞ったかを示すための動作は、最初は無視します。この機能は、アプリのベースを作成した後で追加できます。
以下は、おすすめする作成手順の概要です。
- レモネードを作る最初のステップの UI レイアウトを作成します。木になっているレモンを選択するように求めるステップです。細かな見た目は後で調整できるので、画像の枠線は省略します。
- レモンの木がタップされたら、レモンの画像とそれに対応するテキストラベルを表示する、というアプリの動作を実装します。これが、レモネードを作る手順の最初の 2 つのステップです。
- コードを追加して、画像がタップされるたびにレモネードを作る手順の残りのステップを表示するようにします。この段階では、レモンを 1 回タップすると、レモネードの入ったグラスが表示されます。
- レモンを絞るステップにカスタムの動作を追加して 2~4 の範囲の数をランダムに生成し、ユーザーがレモンをその回数だけ「絞る」(タップする)動作を必須にします。
- その他に必要な見た目の調整を行ってアプリを完成させます。たとえば、フォントサイズを変更し、画像の周りに枠線を付けて、アプリの見た目を良くするなどです。Kotlin のコーディング スタイル ガイドラインを遵守している、コードにコメントを追加しているなど、適切なコーディング手法に従っていることを確認します。
以上の作成手順の概要を採用して Lemonade アプリを実装する場合は、自力でアプリを作成してください。5 つのステップそれぞれで、さらに手引きが必要な場合は、次のセクションに進みます。
5. アプリを実装する
UI レイアウトを作成する
まず、レモンの木の画像とそれに対応するテキストラベル(Tap the lemon tree to select a lemon
)を画面の中央に表示するようにアプリを変更します。また、テキストとその下の画像の間に 16dp
のスペースを入れる必要もあります。
必要に応じて、次のスターター コードを MainActivity.kt
ファイルで使用します。
package com.example.lemonade
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.example.lemonade.ui.theme.LemonadeTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
LemonadeTheme {
LemonApp()
}
}
}
}
@Composable
fun LemonApp() {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Text(text = "Hello there!")
}
}
@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
LemonadeTheme {
LemonApp()
}
}
このコードは、Android Studio が自動生成したコードに似ていますが、Greeting()
コンポーザブルの代わりに、LemonApp()
コンポーザブルが定義されており、パラメータは受け取りません。また、コードを簡単にプレビューできるように、DefaultPreview()
コンポーザブルが LemonApp()
コンポーザブルを使用するように更新されています。
Android Studio でこのコードを入力したら、LemonApp()
コンポーザブルを修正して、アプリの内容を入れる必要があります。以下に作業の参考となる質問をいくつかご紹介します。
- どのコンポーザブルを使用しますか。
- コンポーザブルを目的の位置に配置するのに役立つ標準の Compose レイアウト コンポーネントはありますか。
このステップを実装して、起動時にレモンの木とテキストラベルが表示されるようにします。コードを変更したときには、Android Studio でコンポーザブルをプレビューして、UI がどのように表示されるかを確認しましょう。アプリを実行して、前のセクションのスクリーンショットと同じように表示されることを確認します。
画像をタップしたときの動作の追加方法について手引きが必要な場合は、作業後にこの手順に戻ってください。
クリック動作を追加する
次に、レモンの木の画像がタップされたら、レモンの画像がテキストラベル Keep tapping the lemon to squeeze it
とともに表示されるようにコードを追加します。つまり、レモンの木をタップすると、テキストと画像が変化するということです。
このパスウェイの前半で、ボタンをクリック可能にする方法を学習しました。Lemonade アプリの場合は、Button
コンポーザブルがありません。しかし、clickable
修飾子を指定すれば、ボタンだけでなく任意のコンポーザブルをクリック可能にできます。例については、クリック可能のドキュメントのページを確認してください。
画像がクリックされたときには、何をすべきでしょうか。この動作を実装するコードは簡単でないため、以前に学習したアプリに戻ります。
Dice Roller アプリを確認する
Dice Roller アプリのコードに戻り、サイコロの出目の値に基づいてアプリが別々のサイコロ画像を表示する方法を確認してください。
Dice Roller アプリの MainActivity.kt
...
@Composable
fun DiceWithButtonAndImage(modifier: Modifier = Modifier) {
var result by remember { mutableStateOf(1) }
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
}
Column(modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally) {
Image(painter = painterResource(id = imageResource), contentDescription = result.toString())
Button(onClick = { result = (1..6).random() }) {
Text(stringResource(id = R.string.roll))
}
}
}
...
Dice Roller アプリのコードに関する以下の質問に答えてみましょう。
- 表示するサイコロの画像は、どの変数の値で決まりますか。
- 変数を変化させるトリガーとなるのは、ユーザーのどのような操作ですか。
コンポーズ可能な関数 DiceWithButtonAndImage()
は、次のコードで remember
コンポーザブルと mutableStateOf()
関数で定義された result
変数にサイコロの直近の出目を格納します。
var result by remember { mutableStateOf(1) }
result
変数が新しい値に更新されると、Compose が DiceWithButtonAndImage()
コンポーザブルの再コンポジションをトリガーします。つまり、コンポーザブルが再度実行されます。result
値は再コンポーズ後も保存されているため、DiceWithButtonAndImage()
コンポーザブルが再度実行されると、最新の result
値が使用されます。このコンポーザブルは、result
変数の値に対して when
文を使用して、新たに表示するドローアブル リソース ID を決め、Image
コンポーザブルがそれを表示します。
学習した内容を Lemonade アプリに応用する
次に、Lemonade アプリに関する以下の質問に答えてみましょう。
- 画面に表示するテキストと画像を決めるために使用できる変数はありますか。その変数をコードで定義してください。
- Kotlin の条件構文を使用し、その変数の値に基づいてアプリが異なる動作を実行するようにできますか。できる場合は、その条件文をコードに記述してください。
- 変数を変化させるトリガーとなるのは、ユーザーのどのような操作ですか。それが発生するコード内の適切な場所を見つけてください。そこに変数を更新するコードを追加してください。
このセクションの実装はとても難しく、正しく動作させるには、コードの複数箇所を変更する必要があります。すぐにアプリが期待どおりに動作しなくても、気を落とすことはありません。この動作を実装する正しい方法は複数あります。
完了したら、アプリを実行して正しく動作することを確認します。起動すると、レモンの木の画像とそれに対応するテキストラベルが表示されます。レモンの木の画像を 1 回タップすると、テキストラベルが更新され、レモンの画像が表示されます。この時点でレモンの画像をタップしても何も起こりません。
残りのステップを追加する
これで、レモネードを作る手順の 2 つのステップを表示できるようになりました。この時点の LemonApp()
コンポーザブルは、次のコード スニペットのようになります。アプリの動作が同じであれば、多少コードが違っても問題ありません。
MainActivity.kt
...
@Composable
fun LemonApp() {
// Current step the app is displaying (remember allows the state to be retained
// across recompositions).
var currentStep by remember { mutableStateOf(1) }
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
when (currentStep) {
1 -> {
Column (
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxSize()
){
Text(text = stringResource(R.string.lemon_select))
Spacer(modifier = Modifier.height(32.dp))
Image(
painter = painterResource(R.drawable.lemon_tree),
contentDescription = stringResource(R.string.lemon_tree_content_description),
modifier = Modifier
.wrapContentSize()
.clickable {
currentStep = 2
}
)
}
}
2 -> {
Column (
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxSize()
){
Text(text = stringResource(R.string.lemon_squeeze))
Spacer(modifier = Modifier.height(32
.dp))
Image(
painter = painterResource(R.drawable.lemon_squeeze),
contentDescription = stringResource(R.string.lemon_content_description),
modifier = Modifier.wrapContentSize()
)
}
}
}
}
}
...
次に、レモネードを作る手順の残りのステップを追加します。画像を 1 回タップすると、レモネードを作る手順の次のステップに進み、テキストと画像の両方が更新されます。前半の 2 つのステップだけでなく、すべてのステップをより柔軟に処理できるようにコードを変更する必要があります。
画像がクリックされるたびに動作を変えるには、クリック可能な動作をカスタマイズする必要があります。具体的には、画像がクリックされたときに実行されるラムダで、どのステップに遷移しようとしているかを認識している必要があります。
レモネードを作る手順の各ステップの中に同じコードがあると気づき始めたと思います。前のコード スニペットの when
文で、1
の場合のコードは 2
の場合によく似ていますが、わずかな違いがあります。可能であれば、UI で画像の上にテキストを表示する LemonTextAndImage()
といったコンポーズ可能な関数を作成しましょう。入力パラメータを受け取る新しいコンポーズ可能な関数を作成すると、渡す入力さえ変えれば複数のシナリオで役立つ再利用可能な関数となります。どのような入力パラメータにするかは、自身で決めてください。このコンポーズ可能な関数を作成したら、関連する場所で新しい関数を呼び出すように既存のコードを更新します。
LemonTextAndImage()
のような別のコンポーザブルを使用することには、コードが整理され、堅牢になるという利点もあります。LemonTextAndImage()
を呼び出すと、確実にテキストと画像の両方を新しい値にできます。これを使用しない場合は、更新されたテキストラベルが間違った画像とともに表示されてしまうケースが見逃されやすくなります。
もう一つのヒントとして、ラムダ関数をコンポーザブルに渡すというものもあります。関数型の表記を使用して、渡す関数の型を指定してください。次の例では、WelcomeScreen()
コンポーザブルが定義されていて、name
文字列と () -> Unit
型の onStartClicked()
関数という 2 つの入力パラメータを受け入れています。つまり、この関数は入力を受け取らず(矢印の前の空のかっこ)、戻り値がありません(矢印の後の Unit
)。この関数型 () -> Unit
に一致する関数を使用して、この Button
の onClick
ハンドラを設定できます。ボタンがクリックされると、onStartClicked()
関数が呼び出されます。
@Composable
fun WelcomeScreen(name: String, onStartClicked: () -> Unit) {
Column {
Text(text = "Welcome $name!")
Button(
onClick = onStartClicked
) {
Text("Start")
}
}
}
ラムダをコンポーザブルに渡すパターンは、WelcomeScreen()
コンポーザブルを異なるシナリオで再利用できるため便利なパターンです。ユーザー名とボタンの onClick
動作は引数として渡されるため、毎回変えることができます。
自分のコードに戻り、このヒントを活用して、レモネードを作る手順の残りのステップを追加しましょう。
レモンを絞る回数をランダムにするカスタム ロジックの追加方法についてさらに手引きが必要な場合は、この手順に戻ってください。
絞るロジックを追加する
これでアプリのベースは作成できました。画像をタップすると、次のステップに進みます。ここでは、レモネードを作るためにレモンを複数回絞る必要があるという動作を追加します。レモンを絞る、つまりタップする必要がある回数は、2 から 4 までの乱数にする必要があります。この乱数は、木から新しいレモンを選ぶたびに変わる番号になります。
ここで作業の参考となるように、いくつか質問をします。
- Kotlin では、どのようにして乱数を生成しますか。
- コードのどの時点で乱数を生成する必要がありますか。
- 次のステップに進む前に、レモンが必要な回数分タップされたことを確認するには、どうすればよいですか。
- 画面が再描画されるたびにデータがリセットされないようにするため、
remember
コンポーザブルに変数を保存する必要はありますか。
この変更の実装が完了したら、アプリを実行してください。次のステップに進むにはレモンの画像を複数回タップする必要があり、必要なタップ回数がそれぞれで 2 から 4 までの乱数になることを確認します。レモンの画像を 1 回タップするだけでレモネードの入ったガラスが表示される場合は、コードに戻って足らない部分を見つけ出し、もう一度試しください。
アプリを完成させる方法について追加の手引きが必要な場合は、この手順に戻ってください。
アプリを完成させる
完了まであと少しです。最後に細かな修正を加えて、アプリを仕上げましょう。
完成したアプリのスクリーンショットを再度示します。
- テキストと画像を画面の上下左右で中央になるように配置します。
- テキストのフォントサイズを
18sp
に設定します。 - テキストと画像の間に
16dp
のスペースを追加します。 - 画像の周りに
2dp
の細い枠線を追加し、4dp
の少し丸まった角にします。枠線は、赤が105
、緑が205
、青が216
の RGB 色値です。枠線の追加方法の例については、Google で検索してください。または、枠線に関するドキュメントを確認してください。
これらの変更が完了したら、アプリを実行し、最終的なスクリーンショットと比較して、一致していることを確認します。
良いコーディング習慣として、コードに戻ってコメントを追加し、誰がコードを読んでもあなたの思考プロセスを簡単に理解できるようにしてください。また、ファイルの先頭にある、コード内で使用されていない import 文を削除してください。コードが Kotlin スタイルガイドに従っていることを確認します。このような努力により、コードの可読性が上がり、メンテナンスが容易になります。
よくできました。Lemonade アプリの実装という素晴らしい成果をあげました。解決すべき部分がたくさんある難しいアプリでした。自分へのご褒美として、さわやかなレモネードを飲みましょう。乾杯!
6. 解答コードを取得する
解答コードをダウンロード:
または、GitHub リポジトリのクローンを作成してコードを入手することもできます。
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-lemonade.git
アプリを実装する方法は複数あるため、解答コードと正確に一致している必要はありません。
Lemonade アプリの GitHub リポジトリでコードを見ることもできます。