1. 始める前に
この Codelab では、既存の Dice Roller Android アプリにサイコロの画像を追加します。Dice Roller アプリ作成の基本に関する前の Codelab を先に完了しておいてください。
アプリでは、サイコロの出目の値を TextView
で表示する代わりに、出目の数字に対応するサイコロの画像が表示されます。これにより、アプリのユーザー エクスペリエンスが大幅に向上します。
サイコロの画像をダウンロードするためのリンクが用意されており、アプリ内のリソースとして追加できます。使用するサイコロの画像のコードを記述するには、Kotlin で when
ステートメントを使用します。
前提条件
- インタラクティブな Dice Roller アプリを作成する Codelab を完了している。
- 制御フロー ステートメント(
if / else
、when
ステートメント)を記述できる。 - ユーザー入力に基づいてアプリの UI を更新(
MainActivity.kt
ファイルを変更)できる。 Button.
にクリック リスナーを追加できる。- Android アプリに画像リソースを追加できる。
学習内容
- アプリの実行時に
ImageView
を更新する方法。 - さまざまな条件に従ってアプリの動作をカスタマイズする方法(
when
ステートメントを使用)。
作成するアプリの概要
- サイコロを振るための
Button
を備え、画面上の画像を更新する Dice Roller Android アプリ。
必要なもの
- Android Studio がインストールされているパソコン
- サイコロの画像をダウンロードするためのインターネット接続。
2. アプリのレイアウトを更新する
このタスクでは、レイアウト内の TextView
を、サイコロの出目の画像を表示する ImageView
に置き換えます。
Dice Roller アプリを起動する
- Android Studio で前の Codelab から Dice Roller アプリを起動し、実行します。解答コードまたは作成したコードを使用できます。
アプリは次のようになります。
activity_main.xml
を開きます([app] > [res] > [layout] > [activity_main.xml])。Layout Editor が開きます。
TextView を削除する
- Layout Editor で、[Component Tree] の
TextView
を選択します。
- 右クリックして [Delete] を選択するか、
Delete
キーを押します。 Button
の警告は、ここでは無視してください。これを次のステップで修正します。
レイアウトに ImageView を追加する
ImageView
を [Palette] から [Design] ビューにドラッグし、Button
の上に配置します。
- [Pick a Resource] ダイアログの [Sample data] で [avatars] を選択します。この画像は、次のタスクでサイコロの画像を追加するまで一時的に使用されます。
- [OK] を押します。アプリの [Design] ビューは次のようになります。
- [Component Tree] には 2 つのエラーが表示されます。
Button
は垂直方向の制約を受けず、ImageView
は垂直方向の制約も水平方向の制約も受けません。
Button
が垂直方向の制約を受けないのは、元々は上部に配置されていた TextView
を削除したためです。次に、ImageView
を配置して、その下に Button
を置きます。
ImageView と Button の位置を決める
Button
の位置にかかわらず、画面の垂直方向の中央に ImageView
を配置する必要があります。
ImageView
に水平方向の制約を追加します。ImageView
の左側を親ConstraintLayout
の左端に接続します。ImageView
の右側を親の右端に接続します。これでImageView
が親の内部に水平方向に中央揃えで配置されます。
ImageView
に垂直方向の制約を追加するため、ImageView
の上部を親の上部に接続します。ImageView
がConstraintLayout
の上部までスライドします。
Button
に垂直方向の制約を追加するため、Button
の上部をImageView
の下部に接続します。Button
がImageView
の下までスライドします。
- ここでもう一度
ImageView
を選択し、ImageView
の下部を親の下部に接続する垂直方向の制約を追加します。ConstraintLayout
でImageView
が垂直方向に中央揃えで配置されます。
これで、制約に関する警告は表示されなくなります。
これらの操作をすべて完了すると、[Design] ビューは次のようになります。ImageView
が中央に配置され、そのすぐ下に Button
が配置されます。
[Component Tree] の ImageView
に、コンテンツの説明を ImageView
に追加するよう求める警告が表示されます。この警告はここでは気にする必要はありません。Codelab の後半で、表示されているサイコロの画像に基づいて ImageView
のコンテンツの説明を設定する手順があります。この変更は Kotlin コードで行います。
3.サイコロの画像を追加する
このタスクでは、サイコロの画像をいくつかダウンロードしてアプリに追加します。
サイコロの画像をダウンロードする
- この URL を開いて、サイコロの画像を ZIP ファイル形式でパソコンにダウンロードします。ダウンロードが完了するまで待ってください。
- パソコンに保存したファイルを見つけます([ダウンロード] フォルダなど)。
- ZIP ファイルをダブルクリックして展開します。これにより、1~6 のサイコロの値を表示する 6 つのサイコロの画像ファイルが入った新しい
dice_images
フォルダが作成されます。
アプリにサイコロの画像を追加する
- Android Studio のメニューで、[View] > [Tool Windows] > [Resource Manager] をクリックするか、[Project] ウィンドウの左側にある [Resource Manager] タブをクリックします。
- [Resource Manager] の下にある [+] をクリックして、[Import Drawables] を選択します。すると、ファイル ブラウザが開きます。
- 6 つあるサイコロの画像ファイルを選択します。1 つ目のファイルを選択してから、
Shift
キーを押しながら他のファイルを選択します。 - [Open] をクリックします。
- [Next]、[Import] の順にクリックし、これらの 6 つのリソースをインポートすることを確定します。
- ファイルが正常にインポートされると、アプリの [Resource Manager]([app] > [res] > [drawable])に 6 つの画像が表示されます。
お疲れさまでした。次のタスクでは、これらの画像をアプリで使用します。
重要: - これらの画像は、Kotlin のコードで次のリソース ID を通じて参照できます。
R.drawable.dice_1
R.drawable.dice_2
R.drawable.dice_3
R.drawable.dice_4
R.drawable.dice_5
R.drawable.dice_6
4. サイコロの画像を使用する
サンプルのアバター画像を置き換える
- [Design Editor] で
ImageView
を選択します。 - [Declared Attributes] の [Attributes] で、アバター画像に設定されているツール srcCompat 属性を見つけます。
ツールの srcCompat 属性では、Android Studio の [Design] ビュー内で指定された画像のみが使用されることに注意してください。画像はアプリを作成するときにのみデベロッパーに表示され、エミュレータまたはデバイス上で実際にアプリを実行するときには表示されません。
- アバターの小さなプレビューをクリックします。この
ImageView
に使用する新しいリソースを選択するためのダイアログが開きます。
- [
dice_1
] ドローアブルを選択して、[OK] をクリックします。
すると、ImageView
が画面全体に表示されます。
次に、Button
が隠れないように ImageView
の幅と高さを調整します。
- [Constraints Widget] の [Attributes] ウィンドウで、layout_width 属性と layout_height 属性を見つけます。現在、これらの属性は wrap_content に設定されています。つまり、
ImageView
の高さと幅が、その中のコンテンツ(ソース画像)と一致します。 ImageView
に固定幅 160 dp、固定高さ 200 dp を設定します。Enter キーを押します。
ImageView
がずっと小さくなりました。
Button
が画像と近すぎるようです。
- [Constraint Widget] でボタンの上余白を 16 dp に設定します。
Design ビューが更新されると、アプリの外観が良くなります。
ボタンをクリックしたときにサイコロの画像を変更する
レイアウトは修正されていますが、サイコロの画像を使用するには MainActivity
クラスを更新する必要があります。
この時点では、アプリの MainActivity.kt
ファイルでエラーが発生しています。アプリを実行しようとすると、次のビルドエラーが表示されます。
これは、レイアウトから削除した TextView
をコードが引き続き参照しているためです。
MainActivity.kt
を開きます([app] > [java] > [com.example.diceroller] > [MainActivity.kt])。
コードは R.id.textView
を参照していますが、Android Studio は認識できません。
rollDice()
メソッドの中で、TextView
を参照しているコードをすべて選択して削除します。
// Update the TextView with the dice roll
val resultTextView: TextView = findViewById(R.id.textView)
resultTextView.text = dice.roll().toString()
rollDice()
の中で、ImageView
タイプのdiceImage
という名前の新しい変数を作成します。この変数にレイアウトのImageView
を割り当てます。findViewById()
メソッドを使用して、ImageView
のリソース ID であるR.id.imageView
を入力引数として渡します。
val diceImage: ImageView = findViewById(R.id.imageView)
ImageView
の正確なリソース ID を調べるには、[Attributes] ウィンドウの上部にある [id] を確認します。
Kotlin コードでこのリソース ID を参照するときは、まったく同じ綴りになるように(小文字の i、大文字の V、スペースなし)入力してください。入力内容が異なる場合、Android Studio にエラーが表示されます。
- 以下のコード行を追加して、ボタンがクリックされたときに
ImageView
を正しく更新できるかどうかをテストします。サイコロの出目は必ずしも「2」とは限りませんが、ここはテストなのでdice_2
の画像を使用します。
diceImage.setImageResource(R.drawable.dice_2)
このコードは、ImageView
で setImageResource()
メソッドを呼び出し、dice_2
画像のリソース ID を渡します。これにより、画面上の ImageView
が更新されて dice_2
画像が表示されます。
rollDice() メソッドは次のようになります。
private fun rollDice() {
val dice = Dice(6)
val diceRoll = dice.roll()
val diceImage: ImageView = findViewById(R.id.imageView)
diceImage.setImageResource(R.drawable.dice_2)
}
- アプリを実行し、エラーを伴わずに正常に完了することを確認します。アプリを起動すると、空白の画面が表示されます([Roll] ボタンのみが表示される)。
ボタンをタップすると、値が 2 のサイコロの画像が表示されます。
ボタンをタップして画像を変更できました。もう少しで完了です。
5. サイコロの出目に適したサイコロの画像を表示する
サイコロの出目は常に 2 になるとは限りません。Codelab のサイコロの出目に対応する条件付き動作を追加するで学習した制御フローロジックを使用して、サイコロのランダムな出目に応じて適切なサイコロの画像が画面に表示されるようにします。
コードの入力を始める前に、起こる動作を説明する疑似コードを記述して、アプリの動作に関するコンセプトを考えます。次に例を示します。
サイコロの出目が 1 であれば、dice_1
の画像を表示する。
サイコロの出目が 2 であれば、dice_2
の画像を表示する。
以降も同様。
上記の擬似コードは、Kotlin の if / else
ステートメントを使用して、サイコロの出目の値に基づいて記述できます。
if (diceRoll == 1) {
diceImage.setImageResource(R.drawable.dice_1)
} else if (diceRoll == 2) {
diceImage.setImageResource(R.drawable.dice_2)
}
...
しかし、各ケースを if / else
で記述するのは、繰り返しが多すぎます。when
ステートメントを使用すると、同じロジックを簡単に記述できます。使用するコードが少なく、より簡潔です。このアプローチをアプリで使用します。
when (diceRoll) {
1 -> diceImage.setImageResource(R.drawable.dice_1)
2 -> diceImage.setImageResource(R.drawable.dice_2)
...
rollDice() メソッドを更新する
rollDice()
メソッドで、画像リソース ID を毎回dice_2
イメージに設定するコード行を削除します。
diceImage.setImageResource(R.drawable.dice_2)
diceRoll
の値に基づいてImageView
を更新するwhen
ステートメントに置き換えます。
when (diceRoll) {
1 -> diceImage.setImageResource(R.drawable.dice_1)
2 -> diceImage.setImageResource(R.drawable.dice_2)
3 -> diceImage.setImageResource(R.drawable.dice_3)
4 -> diceImage.setImageResource(R.drawable.dice_4)
5 -> diceImage.setImageResource(R.drawable.dice_5)
6 -> diceImage.setImageResource(R.drawable.dice_6)
}
変更が完了すると、rollDice()
メソッドは次のようになります。
private fun rollDice() {
val dice = Dice(6)
val diceRoll = dice.roll()
val diceImage: ImageView = findViewById(R.id.imageView)
when (diceRoll) {
1 -> diceImage.setImageResource(R.drawable.dice_1)
2 -> diceImage.setImageResource(R.drawable.dice_2)
3 -> diceImage.setImageResource(R.drawable.dice_3)
4 -> diceImage.setImageResource(R.drawable.dice_4)
5 -> diceImage.setImageResource(R.drawable.dice_5)
6 -> diceImage.setImageResource(R.drawable.dice_6)
}
}
- アプリを実行します。[Roll] ボタンをクリックすると、サイコロの画像が 2 以外の値に変わります。つまり、正常に動作しています。
コードを最適化する
コードをさらに簡潔にしたい場合は、以下に説明するようにコードを変更できます。この変更によりアプリの動作に目に見える変化は生じませんが、コードが短くなり、繰り返し作業が減ります。
現行の when ステートメントでは diceImage.setImageResource()
が 6 回呼び出されています。
when (diceRoll) {
1 -> diceImage.setImageResource(R.drawable.dice_1)
2 -> diceImage.setImageResource(R.drawable.dice_2)
3 -> diceImage.setImageResource(R.drawable.dice_3)
4 -> diceImage.setImageResource(R.drawable.dice_4)
5 -> diceImage.setImageResource(R.drawable.dice_5)
6 -> diceImage.setImageResource(R.drawable.dice_6)
}
それぞれのケースで変更されるのは、使用するリソース ID だけです。つまり、使用するリソース ID を格納するための変数を作成できます。次に、コード内で diceImage.setImageResource()
を 1 回だけ呼び出し、正しいリソース ID を渡すだけで変更が完了します。
- 上記のコードを、次のコードに置き換えます。
val drawableResource = when (diceRoll) {
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
6 -> R.drawable.dice_6
}
diceImage.setImageResource(drawableResource)
ここでの新しいコンセプトは、when
式自体も値を返せるということです。この新しいコード スニペットでは、when
式が正しいリソース ID を返すと、その ID が drawableResource
変数に格納されます。その上で、その変数を使用して、表示される画像リソースを更新できます。
when
に赤い下線が引かれています。カーソルを合わせると、「'when' expression must be exhaustive, add necessary 'else' branch」というエラー メッセージが表示されます。
このエラーは、when
式の値が drawableResource
に割り当てられているために、when
がすべてのケースをカバーしなければならないことが原因で発生します。12 面のサイコロに変わっても常に 1 つの値を返すように、考えられるすべてのケースを処理する必要があります。Android Studio は else
ブランチを追加することを提案しています。このエラーを修正するには、6
のケースを else
に変更します。1
から 5
までのケースの処理は同様ですが、6
を含む他のすべてのケースは else
によって処理されます。
val drawableResource = when (diceRoll) {
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
}
diceImage.setImageResource(drawableResource)
- アプリを実行して、正常に動作することを確認します。テストを十分に行って、サイコロの画像が 1~6 のすべての数字で表示されることを確認してください。
ImageView に適切なコンテンツの説明を設定する
出目の数字を画像に置き換えたので、スクリーン リーダーは出目の数字を判別できなくなります。この問題を解決するには、画像リソースを更新してから、ImageView
のコンテンツの説明を更新します。スクリーン リーダーが説明できるように、コンテンツの説明には ImageView
に表示されるテキストの説明を指定する必要があります。
diceImage.contentDescription = diceRoll.toString()
スクリーン リーダーはこのコンテンツ説明を読み上げることができ、出目が「6」の画像が画面に表示されていれば、コンテンツの説明を「6」と読み上げます。
6. 適切なコーディング慣習を採用する
実用的な起動体験を構築する
初めてアプリを開いたときに、アプリには [Roll] ボタン以外に何も表示されず、ユーザーに奇妙な印象を与えます。ユーザーは次の動作を予測できない可能性があり、アプリを初めて起動して Activity
を作成するときには、ランダムな出目が表示されるよう UI を変更します。このようにすると、ユーザーは、[Roll] ボタンをタップするとサイコロが振られることを容易に認識できます。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val rollButton: Button = findViewById(R.id.button)
rollButton.setOnClickListener { rollDice() }
// Do a dice roll when the app starts
rollDice()
}
コードにコメントを追加する
作成したコードの機能や意図を説明するために、コードにコメントを追加します。
これらの変更をすべて行った後、rollDice()
メソッドは次のようになります。
/**
* Roll the dice and update the screen with the result.
*/
private fun rollDice() {
// Create new Dice object with 6 sides and roll the dice
val dice = Dice(6)
val diceRoll = dice.roll()
// Find the ImageView in the layout
val diceImage: ImageView = findViewById(R.id.imageView)
// Determine which drawable resource ID to use based on the dice roll
val drawableResource = when (diceRoll) {
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
}
// Update the ImageView with the correct drawable resource ID
diceImage.setImageResource(drawableResource)
// Update the content description
diceImage.contentDescription = diceRoll.toString()
}
完全な MainActivity.kt
ファイルについては、次のステップでリンクされている GitHub の解答コードをご覧ください。
Dice Roller の作成が完了しました。このアプリを友だちとプレイして、ゲームナイトを楽しめます。
7. 解答コード
この Codelab の解答コードは、以下に示すプロジェクトとモジュールにあります。
この Codelab のコードを取得して Android Studio で開く手順は以下のとおりです。
コードを取得する
- 指定された URL をクリックします。プロジェクトの GitHub ページがブラウザで開きます。
- プロジェクトの GitHub ページで、[Code] ボタンをクリックすると、ダイアログが表示されます。
- ダイアログで、[Download ZIP] をクリックして、プロジェクトをパソコンに保存します。ダウンロードが完了するまで待ってください。
- パソコンに保存したファイルを見つけます([ダウンロード] フォルダなど)。
- ZIP ファイルをダブルクリックして展開します。プロジェクト ファイルが入った新しいフォルダが作成されます。
Android Studio でプロジェクトを開く
- Android Studio を起動します。
- [Welcome to Android Studio] ウィンドウで [Open an existing Android Studio project] をクリックします。
注: Android Studio がすでに開いている場合は、メニューから [File] > [New] > [Import Project] を選択します。
- [Import Project] ダイアログで、展開したプロジェクト フォルダがある場所([ダウンロード] フォルダなど)に移動します。
- そのプロジェクト フォルダをダブルクリックします。
- Android Studio でプロジェクトが開くまで待ちます。
- 実行ボタン をクリックし、アプリをビルドして実行します。正常にビルドされたことを確認します。
- [Project] ツール ウィンドウでプロジェクト ファイルを見て、アプリがどのように設定されているかを確認します。
8. 概要
setImageResource()
を使用して、ImageView
に表示される画像を変更します。if / else
式やwhen
式などの制御フロー ステートメントを使用して、アプリ内のさまざまなケースを処理します。たとえば、状況に応じて異なる画像を表示します。
9. 詳細
10. 自習用練習問題
次のことを行います。
- アプリにサイコロをもう 1 つ追加して、[Roll] ボタンで出目が 2 つ得られるようにします。レイアウトには
ImageViews
がいくつ必要でしょうか。MainActivity.kt
コードにはどのような影響があるでしょうか。
確認:
完成したアプリはエラーを伴わずに実行でき、2 つのサイコロが表示される必要があります。