Dice Roller アプリに画像を追加する

1. 始める前に

この Codelab では、既存の Dice Roller Android アプリにサイコロの画像を追加します。Dice Roller アプリ作成の基本に関する前の Codelab を先に完了しておいてください。

アプリでは、サイコロの出目の値を TextView で表示する代わりに、出目の数字に対応するサイコロの画像が表示されます。これにより、アプリのユーザー エクスペリエンスが大幅に向上します。

c7f0d42525da7431.png

サイコロの画像をダウンロードするためのリンクが用意されており、アプリ内のリソースとして追加できます。使用するサイコロの画像のコードを記述するには、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 アプリを起動する

  1. Android Studio で前の Codelab から Dice Roller アプリを起動し、実行します。解答コードまたは作成したコードを使用できます。

アプリは次のようになります。

2e8416293e597725.png

  1. activity_main.xml を開きます([app] > [res] > [layout] > [activity_main.xml])。Layout Editor が開きます。

TextView を削除する

  1. Layout Editor で、[Component Tree] の TextView を選択します。

a6fc189dac34ee71.png

  1. 右クリックして [Delete] を選択するか、Delete キーを押します。
  2. Button の警告は、ここでは無視してください。これを次のステップで修正します。

レイアウトに ImageView を追加する

  1. ImageView を [Palette] から [Design] ビューにドラッグし、Button の上に配置します。

91f6e2be0a01fbf.png

  1. [Pick a Resource] ダイアログの [Sample data] で [avatars] を選択します。この画像は、次のタスクでサイコロの画像を追加するまで一時的に使用されます。

824493e9927da401.png

  1. [OK] を押します。アプリの [Design] ビューは次のようになります。

f9d5ee87018baee.png

  1. [Component Tree] には 2 つのエラーが表示されます。Button は垂直方向の制約を受けず、ImageView は垂直方向の制約も水平方向の制約も受けません。

b8c3b83124c31ff.png

Button が垂直方向の制約を受けないのは、元々は上部に配置されていた TextView を削除したためです。次に、ImageView を配置して、その下に Button を置きます。

ImageView と Button の位置を決める

Button の位置にかかわらず、画面の垂直方向の中央に ImageView を配置する必要があります。

  1. ImageView に水平方向の制約を追加します。ImageView の左側を親 ConstraintLayout の左端に接続します。
  2. ImageView の右側を親の右端に接続します。これで ImageView が親の内部に水平方向に中央揃えで配置されます。

9848bb6319e11777.png

  1. ImageView に垂直方向の制約を追加するため、ImageView の上部を親の上部に接続します。ImageView が ConstraintLayout の上部までスライドします。

2d8d134e6292d48f.png

  1. Button に垂直方向の制約を追加するため、Button の上部を ImageView の下部に接続します。Button が ImageView の下までスライドします。

b6d3dcee6c7a51fc.png

  1. ここでもう一度 ImageView を選択し、ImageView の下部を親の下部に接続する垂直方向の制約を追加します。ConstraintLayout で ImageView が垂直方向に中央揃えで配置されます。

これで、制約に関する警告は表示されなくなります。

これらの操作をすべて完了すると、[Design] ビューは次のようになります。ImageView が中央に配置され、そのすぐ下に Button が配置されます。

1b05a6d2fd56459f.png

[Component Tree] の ImageView に、コンテンツの説明を ImageView に追加するよう求める警告が表示されます。この警告はここでは気にする必要はありません。Codelab の後半で、表示されているサイコロの画像に基づいて ImageView のコンテンツの説明を設定する手順があります。この変更は Kotlin コードで行います。

3.サイコロの画像を追加する

このタスクでは、サイコロの画像をいくつかダウンロードしてアプリに追加します。

サイコロの画像をダウンロードする

  1. この URL を開いて、サイコロの画像を ZIP ファイル形式でパソコンにダウンロードします。ダウンロードが完了するまで待ってください。
  2. パソコンに保存したファイルを見つけます([ダウンロード] フォルダなど)。
  3. ZIP ファイルをダブルクリックして展開します。これにより、1~6 のサイコロの値を表示する 6 つのサイコロの画像ファイルが入った新しい dice_images フォルダが作成されます。

43c95351759ada02.png

アプリにサイコロの画像を追加する

  1. Android Studio のメニューで、[View] > [Tool Windows] > [Resource Manager] をクリックするか、[Project] ウィンドウの左側にある [Resource Manager] タブをクリックします。
  2. [Resource Manager] の下にある [+] をクリックして、[Import Drawables] を選択します。すると、ファイル ブラウザが開きます。

67186ea5d631bc8a.png

  1. 6 つあるサイコロの画像ファイルを選択します。1 つ目のファイルを選択してから、Shift キーを押しながら他のファイルを選択します。
  2. [Open] をクリックします。
  3. [Next]、[Import] の順にクリックし、これらの 6 つのリソースをインポートすることを確定します。

a45dff94a19e2722.png

a7ad66d623ac73c2.png

  1. ファイルが正常にインポートされると、アプリの [Resource Manager]([app] > [res] > [drawable])に 6 つの画像が表示されます。

ab68f82b385fc83e.png

お疲れさまでした。次のタスクでは、これらの画像をアプリで使用します。

重要: - これらの画像は、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. サイコロの画像を使用する

サンプルのアバター画像を置き換える

  1. [Design Editor] で ImageView を選択します。
  2. [Declared Attributes] の [Attributes] で、アバター画像に設定されているツール srcCompat 属性を見つけます。

ツールの srcCompat 属性では、Android Studio の [Design] ビュー内で指定された画像のみが使用されることに注意してください。画像はアプリを作成するときにのみデベロッパーに表示され、エミュレータまたはデバイス上で実際にアプリを実行するときには表示されません。

  1. アバターの小さなプレビューをクリックします。この ImageView に使用する新しいリソースを選択するためのダイアログが開きます。

d8a26941179b3bdf.png

  1. [dice_1] ドローアブルを選択して、[OK] をクリックします。

すると、ImageView が画面全体に表示されます。

1072e9fdd637afd9.png

次に、Button が隠れないように ImageView の幅と高さを調整します。

  1. [Constraints Widget] の [Attributes] ウィンドウで、layout_width 属性と layout_height 属性を見つけます。現在、これらの属性は wrap_content に設定されています。つまり、ImageView の高さと幅が、その中のコンテンツ(ソース画像)と一致します。
  2. ImageView に固定幅 160 dp、固定高さ 200 dp を設定します。Enter キーを押します。

ImageView がずっと小さくなりました。

9579582d8775e688.png

Button が画像と近すぎるようです。

  1. [Constraint Widget] でボタンの上余白を 16 dp に設定します。

8c647d6ae28ef3a6.png

Design ビューが更新されると、アプリの外観が良くなります。

b53f7379bfba8c27.png

ボタンをクリックしたときにサイコロの画像を変更する

レイアウトは修正されていますが、サイコロの画像を使用するには MainActivity クラスを更新する必要があります。

この時点では、アプリの MainActivity.kt ファイルでエラーが発生しています。アプリを実行しようとすると、次のビルドエラーが表示されます。

aaecce207cb5fc7.png

これは、レイアウトから削除した TextView をコードが引き続き参照しているためです。

  1. MainActivity.kt を開きます([app] > [java] > [com.example.diceroller] > [MainActivity.kt])。

コードは R.id.textView を参照していますが、Android Studio は認識できません。

3a923aa53fc3ba8a.png

  1. rollDice() メソッドの中で、TextView を参照しているコードをすべて選択して削除します。
// Update the TextView with the dice roll
val resultTextView: TextView = findViewById(R.id.textView)
resultTextView.text = dice.roll().toString()
  1. rollDice() の中で、ImageView タイプの diceImage という名前の新しい変数を作成します。この変数にレイアウトの ImageView を割り当てます。findViewById() メソッドを使用して、ImageView のリソース ID である R.id.imageView を入力引数として渡します。
val diceImage: ImageView = findViewById(R.id.imageView)

ImageView の正確なリソース ID を調べるには、[Attributes] ウィンドウの上部にある [id] を確認します。

cbfc9d5e01a04e32.png

Kotlin コードでこのリソース ID を参照するときは、まったく同じ綴りになるように(小文字の i、大文字の V、スペースなし)入力してください。入力内容が異なる場合、Android Studio にエラーが表示されます。

  1. 以下のコード行を追加して、ボタンがクリックされたときに 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)
}
  1. アプリを実行し、エラーを伴わずに正常に完了することを確認します。アプリを起動すると、空白の画面が表示されます([Roll] ボタンのみが表示される)。

c29b50554a31d30f.png

ボタンをタップすると、値が 2 のサイコロの画像が表示されます。

7df72d671b22853f.png

ボタンをタップして画像を変更できました。もう少しで完了です。

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() メソッドを更新する

  1. rollDice() メソッドで、画像リソース ID を毎回 dice_2 イメージに設定するコード行を削除します。
diceImage.setImageResource(R.drawable.dice_2)
  1. 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)
   }
}
  1. アプリを実行します。[Roll] ボタンをクリックすると、サイコロの画像が 2 以外の値に変わります。つまり、正常に動作しています。

ec209952f84b81bd.png 32fc8979b1984e00.png

コードを最適化する

コードをさらに簡潔にしたい場合は、以下に説明するようにコードを変更できます。この変更によりアプリの動作に目に見える変化は生じませんが、コードが短くなり、繰り返し作業が減ります。

現行の 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 を渡すだけで変更が完了します。

  1. 上記のコードを、次のコードに置き換えます。
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 変数に格納されます。その上で、その変数を使用して、表示される画像リソースを更新できます。

  1. 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. アプリを実行して、正常に動作することを確認します。テストを十分に行って、サイコロの画像が 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()
}

ec209952f84b81bd.png

コードにコメントを追加する

作成したコードの機能や意図を説明するために、コードにコメントを追加します。

これらの変更をすべて行った後、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 で開く手順は以下のとおりです。

コードを取得する

  1. 指定された URL をクリックします。プロジェクトの GitHub ページがブラウザで開きます。
  2. プロジェクトの GitHub ページで、[Code] ボタンをクリックすると、ダイアログが表示されます。

5b0a76c50478a73f.png

  1. ダイアログで、[Download ZIP] をクリックして、プロジェクトをパソコンに保存します。ダウンロードが完了するまで待ってください。
  2. パソコンに保存したファイルを見つけます([ダウンロード] フォルダなど)。
  3. ZIP ファイルをダブルクリックして展開します。プロジェクト ファイルが入った新しいフォルダが作成されます。

Android Studio でプロジェクトを開く

  1. Android Studio を起動します。
  2. [Welcome to Android Studio] ウィンドウで [Open an existing Android Studio project] をクリックします。

36cc44fcf0f89a1d.png

注: Android Studio がすでに開いている場合は、メニューから [File] > [New] > [Import Project] を選択します。

21f3eec988dcfbe9.png

  1. [Import Project] ダイアログで、展開したプロジェクト フォルダがある場所([ダウンロード] フォルダなど)に移動します。
  2. そのプロジェクト フォルダをダブルクリックします。
  3. Android Studio でプロジェクトが開くまで待ちます。
  4. 実行ボタン 11c34fc5e516fb1c.png をクリックし、アプリをビルドして実行します。正常にビルドされたことを確認します。
  5. [Project] ツール ウィンドウでプロジェクト ファイルを見て、アプリがどのように設定されているかを確認します。

8. 概要

  • setImageResource() を使用して、ImageView に表示される画像を変更します。
  • if / else 式や when 式などの制御フロー ステートメントを使用して、アプリ内のさまざまなケースを処理します。たとえば、状況に応じて異なる画像を表示します。

9. 詳細

10. 自習用練習問題

次のことを行います。

  1. アプリにサイコロをもう 1 つ追加して、[Roll] ボタンで出目が 2 つ得られるようにします。レイアウトには ImageViews がいくつ必要でしょうか。MainActivity.kt コードにはどのような影響があるでしょうか。

確認:

完成したアプリはエラーを伴わずに実行でき、2 つのサイコロが表示される必要があります。