---
# System prepended metadata

title: '[ASJ2021] 從零開始學 Android - Unit 1-4: Add a button to an app'
tags: [Android, class]

---

[![hackmd-github-sync-badge](https://hackmd.io/4yxqAJoKSEqJJpHuS4uD3g/badge)](https://hackmd.io/4yxqAJoKSEqJJpHuS4uD3g)
###### tags: `Android` `class`
# [ASJ2021] 從零開始學 Android - Unit 1-4: Add a button to an app

+ 活動首頁：[Android App 開發培訓計劃 2021](https://events.withgoogle.com/android-study-jam-twhk-2021/?fbclid=IwAR0xD3_fgtD-IFMBup5x2Ff7L17KX4PcIwusSBcD9p-ik4OxeyTdGe16JTY)
+ 學習指南：[Android Study Jams 2021 操作手冊](https://docs.google.com/presentation/d/1qq72U1C22zMKRthk0oHPBL2nOjrpot1X8duWPhV0_es/edit?usp=sharing)
+ 學習教材：[Android Basics in Kotlin](https://developer.android.com/courses/android-basics-kotlin/course)

---

## 學習目標

#### Unit 1: Kotlin basics for Android 

+ [Add a button to an app](https://developer.android.com/courses/pathways/android-basics-kotlin-four) - 學習如何使用類別 (classes)、物件 (objects)、以及條件式 (conditionals) 來建立互動式的擲骰子 app。

## 1. Classes and object instances in Kotlin 類別與物件實體

在這個章節，我們會實作一個 Dice Roller Android app，在使用者擲骰子後，這個六面骰會隨機出現一面數字。

結果示意圖：

![](https://i.imgur.com/YWj2pvR.png)

接下來，為了熟悉新的程式觀念，我們將會使用線上的 Kotlin 開發工具 [kotlinplayground](https://developer.android.com/training/kotlinplayground) 來練習，稍後再將輸出結果匯入 Android Studio 介面使用。

學習目標：

+ 如何用程式生成隨機的數字來模擬擲骰子
+ 如何透過程式碼建立一個 Dice 類別，並具備變數和方法
+ 如何建立一個類別的物件實體，定義其變數，以及呼叫方法

### Roll random numbers 建立隨機數字

1. 開啟 [kotlinplayground](https://developer.android.com/training/kotlinplayground)，和先前章節一樣，寫下入口函式

```kotlin=
fun main() {

}
```

2. 建立 random function

資料型別除了數字 `Int` 和字串 `String` 以外，還有像是代表數字起始範圍的 `IntRange`，用來代表骰子可能的值，寫法為 `1..6` 用兩個點連接，數字範圍是從 `1` 到 `6`：

```kotlin=
fun main() {
	val dicRange = 1..6	// Kotlin range
    // 等同於 val diceRange: IntRange = 1..6
}
```

> 在程式中建立數字或字串時，不需特別定義 IntRange、Int 或 String 等型別，因為系統通常可直接判別資料型態。

3. 在 main() 定義一個變數 randomNumber
4. 對 dicRange 使用 random()，隨機產生一個數字並賦值給變數 randomNumber

```kotlin=
fun main() {
	val dicRange = 1..6
    val randomNumber = dicRange.random() // 隨機產生一個數字
    // 等同於 val randomNumber = (1..6).random()
}
```

4. 用 println() 印出結果

```kotlin=
fun main() {
	val dicRange = 1..6
    val randomNumber = dicRange.random()
    println("Random Number: ${randomNumber}") // 印出結果
}
// 印出 Random Number: 1~6 隨機數字
```

### Create a Dice class 建立一個骰子類別

試著想像有一個六面骰，每面具備相同特性。接下來我們將透過程式碼，描繪能夠骰出一個隨機數字的藍圖，這個藍圖在程式中被稱作類別（class），這段過程又叫做封裝（encapsulation）。

根據類別，我們可以實作出骰子的物件實體（object instances）。舉例來說，我們可以建立十二面骰或是四面骰。

實作步驟如下：

1. 定義骰子類別，命名慣例為駝峰式（Camel case），例如：`Dice`、`CustomerRecord`

```kotlin=
// 入口函式
fun main() {

}

// 骰子類別
class Dice {

}
```

2. 在 Dice class 裡面宣告一個變數 sides 代表骰子面數

```kotlin=
// 骰子類別
class Dice {
  val sides = 6
}
```

3. 根據 Dice class，在 main() 函式中建立一個名為 myFirstDice 的物件實體

```kotlin=
fun main() {
	val myFirstDice = Dice()
}

class Dice {
    var sides = 6
}
```

4. 這樣就建立了 myFirstDice 這個物件，並具備 class Dice 的特性，可透過 println() 印出 myFirstDice.sides，代表骰子物件的面數：

```kotlin=
fun main() {
    val myFirstDice = Dice()
    println(myFirstDice.sides)  // 印出 6
}

class Dice {
    var sides = 6
}
```

5. 建立擲骰子這個動作，可用方法 fun roll() 來表示：

```kotlin=
class Dice {
    var sides = 6

    fun roll() {

    }
}
```

6. 在 fun roll() 中建立一個隨機變數 randomNumber，範圍是 1 到 6：

```kotlin=
fun roll() {
     val randomNumber = (1..6).random()
     println(randomNumber)
}
```

7. 在 main() 入口函式中呼叫 roll() 方法

```kotlin=
myFirstDice.roll()
```

8. 完成後程式碼如下：

```kotlin=
fun main() {
  val myFirstDice = Dice()
  println(myFirstDice.sides)	// 印出骰子面數為 6
  myFirstDice.roll()	
}

class Dice {
  var sides = 6
    
  fun roll() {
  val randomNumber = (1..6).random()
    println(randomNumber)      // 印出隨機 1~6 的數字
  }
}
// 6
// 4
```

### Return your dice roll's value 回傳骰子值

1. 宣告一個變數 diceRoll 來存取 roll() 的回傳值

```kotlin=
val diceRoll = myFirstDice.roll()
```

在先前章節中，我們學到輸入的參數要先宣告型別；而同理回傳值也是相同道理，寫法如下：

2. 這裡要加上 roll() 回傳值型別，randomNumber 為數字 Int

```kotlin=
fun roll(): Int { ...
```

3. 執行程式碼，會出現以下錯誤訊息：

```
A 'return' expression required in a function with a block body ('{...}')
```

4. 這是因為 roll() 函式並沒有真的回傳一個型別為數字的值，只需把 println() 改為 return 回傳即可：

```kotlin=
fun roll(): Int {
     val randomNumber = (1..6).random()
     return randomNumber
}
```

5. 接著修改 main() 程式碼如下：

```kotlin=
fun main() {
	val myFirstDice = Dice()
    val diceRoll = myFirstDice.roll()	
    println("Your ${myFirstDice.sides} sided dice rolled ${diceRoll}!")
}

class Dice {
    var sides = 6
    
    fun roll(): Int { 
     val randomNumber = (1..6).random()
     return randomNumber	// 回傳值
	}
}
// 印出 Your 6 sided dice rolled 2!
```

### Change the number of sides on your Dice 修改骰子面數

因為骰子不一定是六面，可能會有各種形狀和面數的骰子。

1. 我們可以用 sides 這個變數來表示最大面數，也就是修改 `1..6` 這段 hard code，讓程式碼更有彈性

```kotlin=
val randomNumber = (1..sides).random()
```
2. 修改

```kotlin=
fun main() {
	val myFirstDice = Dice()
    val diceRoll = myFirstDice.roll()	
    // 六面骰子
    println("Your ${myFirstDice.sides} sided dice rolled ${diceRoll}!")
    
    // 改成二十面骰子
    myFirstDice.sides = 20
    println("Your ${myFirstDice.sides} sided dice rolled ${myFirstDice.roll()}!")
}

class Dice {
    var sides = 6
    
    fun roll(): Int { 
      val randomNumber = (1..sides).random()
      return randomNumber	// 回傳值
	}
}

// Your 6 sided dice rolled 3!
// Your 20 sided dice rolled 17!
```

### Customize your dice 客製化骰子

在真實世界中，通常無法直接去改變骰子面數：因此在物件導向的程式語言，我們也不會直接去改變現存的 Dice 物件實體，而是會建立一個符合需求的 new dice 物件實體。

1. 透過修改 Dice class 定義，使其能夠自訂面數的值 numSides

```kotlin=
class Dice(val numSides: Int) {
   // ...
}
```

2. 用 numSides 來取代原有的 sides 變數，修改後如下

```kotlin=
class Dice(val numSides: Int) {
    
    fun roll(): Int { 
     val randomNumber = (1..numSides).random()
     return randomNumber
	}
}
```

3. 這時執行程式碼會出現錯誤，因為我們還沒修改 main()，必須要傳入參數到 Dice()

```kotlin=
 val myFirstDice = Dice(6)
```

4. 調整程式碼，把 sides 變數改為 numSides

```kotlin=
fun main() {
	val myFirstDice = Dice(6)
    val diceRoll = myFirstDice.roll()	
    println("Your ${myFirstDice.numSides} sided dice rolled ${diceRoll}!")
}
```

5. 建立第二個骰子 mySecondDice，具有二十面

```kotlin=
val mySecondDice = Dice(20)
```

6. 完成後如下

```kotlin=
fun main() {
	val myFirstDice = Dice(6)   // 六面骰
    val diceRoll = myFirstDice.roll()	
    println("Your ${myFirstDice.numSides} sided dice rolled ${diceRoll}!")
    
    val mySecondDice = Dice(20) // 二十面骰
    println("Your ${mySecondDice.numSides} sided dice rolled  ${mySecondDice.roll()}!")
}

class Dice(val numSides: Int) {
    
    fun roll(): Int { 
     val randomNumber = (1..numSides).random()
     return randomNumber
	}
}

// Your 6 sided dice rolled 4!
// Your 20 sided dice rolled  15!
```

7. 再稍微精簡程式碼，去除多餘的部分，例如直接回傳 randomNumber 的值：

```kotlin=
fun main() {
	val myFirstDice = Dice(6)
    println("Your ${myFirstDice.numSides} sided dice rolled ${myFirstDice.roll()}!")
    val mySecondDice = Dice(20)
    println("Your ${mySecondDice.numSides} sided dice rolled  ${mySecondDice.roll()}!")
}

class Dice(val numSides: Int) {
    
    fun roll(): Int { 
      return (1..numSides).random()
	}
}

// 和修改前的輸出不會有差別
```

### 小結

+ 在 IntRange 數字範圍後使用 random() 函式，可產生一個隨機數字。
  + 例如：`(1..6).random()`
+ 類別（Classes）就像是物件（object）的設計圖，會擁有特性與狀態，可實作變數和函式。
  + 例如：`val mySecondDice = Dice(20)`、`myFirstDice.roll()`
+ 用 class 實例來表現物件，通常會是物理物件，像是骰子。我們可以對這個物件做動作，或是改變及特性。
+ 建立實例時可賦予值。
  + 例如：`class Dice(val numSides: Int)`，在建立實例 `Dice(6)` 時賦值。
+ 函式可以回傳東西，在函式定義時可指定資料型別。
  + 例如：`fun example(): Int { return 5 }`

---

## 2. Create an interactive Dice Roller app 

在這個章節，我們會使用 Android Studio，建立一個 Dice Roller Android app，讓使用者點擊 `Button` 擲骰子，並將結果顯示在螢幕中的 `TextView`。

結果示意圖：

![](https://i.imgur.com/pz9Xc1l.png)

學習目標：

+ 如何新增一個 `Button`，並新增 `Button` 點擊事件
+ 如何開啟和修改 `Activity` 程式碼
+ 如何顯示 `Toast` 訊息
+ 如何在 app 運行時更新 TextView 訊息

### Set up your app 建立新專案

和前面章節一樣，先建立一個新專案：

![](https://i.imgur.com/ruvqntv.png)

### Create the layout for the app 建立佈局

接下來我們需要在 `TextView` 底下新增一個 `Button`，兩者均會是父層 `ConstraintLayout` 底下的子層：

![](https://i.imgur.com/xpnGkR2.png)

1. 從 Palette 選取 Button，並拖曳到畫面中的 TextView 底下
2. 確認 Button 和 TextView 是否均為 ConstraintLayout 的子層
![](https://i.imgur.com/BDINtLG.png)

3. 可以看到 Button 右側出現紅色警告，提示必須設定水平垂直位置，點選 Button 上方的白色圓點，並拖曳到 TextView 底部

![](https://i.imgur.com/DM3ry4E.gif)

4. 這樣可直接設定 Button 的 Top → BottomOf textView 為 0dp

![](https://i.imgur.com/u5sRutU.png)

5. 同理，將 Button 左右兩側的圓點，分別拉到左右兩側的邊界，使 Button 在 ConstraintLayout 的水平中央位置

![](https://i.imgur.com/rs4XWv9.png)

6. 修改 Button 的文字，也就是 text 屬性為 Roll
7. 但此時會發現 Button 右側還有個黃色警告，和前面章節的 TextView 相同，可透過程式來幫我們修正
8. 點擊後下方會開啟 Message 視窗，點選底下的 Fix 修改，跳出抽出字串來源視窗，點選 OK

![](https://i.imgur.com/UJE5T5k.png)

結果如下，text 屬性會變成 `@string/roll`：

![](https://i.imgur.com/YAFI7Ln.png)

9. 編輯 TextView 樣式，調整字體大小，刪去預設的 text 內容，在底下有個板手的 tool text 屬性輸入 1

![](https://i.imgur.com/oYaQ4eb.png)

這個屬性只會在開發時顯示，假裝骰到的數字為 1，目的是方便編輯，不會出現在運行的程式：

![](https://i.imgur.com/lmDq0yA.png)

### Introduction to Activities 互動功能介紹

Andorid 提供了 android.app.Activity 這個類別，讓開發者能夠控制畫面以加速開發。每個 app 至少會有一個或更多的 activities，而最主要或層級最高的被稱作 MainActivity，由專案 template 提供。

在 MainActivity 中的程式碼，需要提供 Activity 的佈局細節，以及該如何產生互動，每個 Activity 都會有個特定目的。

舉例來說，在一個照片圖廊 app，可以有幾種 Activity 形式：

+ Photo Gallery 顯示多張圖片
+ View Photo 顯示單一圖片
+ Edit Photo 編輯圖片功能

![](https://i.imgur.com/DWOZlpY.png)

1. 開啟專案中的 MainActivity.kt 檔，路徑是 app > java > com.example.diceroller > MainActivity.kt，預設程式碼如下：

```kotlin=
package com.example.diceroller

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle

// MainActivity 類別
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        // onCreate(): 第一次運行 app 時執行
        super.onCreate(savedInstanceState)
        // 載入佈局
        setContentView(R.layout.activity_main)
    }
}
```

透過設定允許自動 imports 套件功能，可以省下手動加入 import 的時間，即使沒有使用了也會自動移除該程式碼。

設定步驟如下：

+ 在 Android Studio 上方狀態列，點選 File > Other Setting > Setting for New Projects
+ 接著點選 Other Settings > Auto Import，看到 Java 和 Kotlin 兩區塊，將下方兩個選項都打勾：
  + Add unambiguous imports on the fly 自動匯入
  + Optimize imports on the fly  移除任何未使用的程式碼

![](https://i.imgur.com/abLngFt.png)

### Make the Button interactive 切換按鈕功能

接下來我們要實作按鈕的切換功能，點擊後會出現訊息：

![](https://i.imgur.com/GL5BlnC.png)

步驟如下：

1. 在 fun Create() 裡面加上 findViewById，用來找到 Button 物件，這個物件的 resouce ID 是 R.id.button，把 Button 的參考值（reference）存到變數 rollButton：

```kotlin=
override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)
   setContentView(R.layout.activity_main)

   // 取得 Button 參考值並存入變數
   val rollButton: Button = findViewById(R.id.button)
}
```

當我們指派物件到一個變數時，Kotlin 並不會複製完整的物件，而是儲存物件的參考值，就像身分證用來識別一個人，但身分證並不是那個人本身。

2. 這時會發現 Button 是紅色的，因為還沒引入 Button 使用

![](https://i.imgur.com/3WpKken.png)

3. 可以手動 import 或點選 Button 後按下 Option+Enter(Mac)或Alt+Enter(Windows) 引入

![](https://i.imgur.com/Z7EAbZN.png)

4. 使用 rollButton 物件，並使用 setOnClickListener{...} 方法來監聽點擊事件

```kotlin=
rollButton.setOnClickListener {
  // do something
}
```

![](https://i.imgur.com/xbC64oE.png)

5. 透過呼叫 Toast.makeText 來印出文字 `"Dice Rolled!"`，再呼叫 toast.show() 顯示，Toast 物件同樣要引入使用

```kotlin=
val toast = Toast.makeText(this, "Dice Rolled!", Toast.LENGTH_SHORT)
toast.show()

// 上面兩行也可省去宣告變數，直接精簡成一行
Toast.makeText(this, "Dice Rolled!", Toast.LENGTH_SHORT).show()
```

完成程式碼如下：

```kotlin=
package com.example.diceroller

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button  // 引入 Button
import android.widget.Toast   // 引入 Toast

class MainActivity : AppCompatActivity() {

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    val rollButton: Button = findViewById(R.id.button)
    // 對 rollButoton 進行監聽點擊事件
    rollButton.setOnClickListener {
      // 點擊後要執行的事情：印出文字
      Toast.makeText(this, "Dice Rolled!", Toast.LENGTH_SHORT).show()
    }
  }
}
```

點選 Button 結果如下：

> 這裡有稍微卡住，需注意版本問題可能會無法顯示 Toast 內容！後面實作還是能正常顯示~_~

![](https://i.imgur.com/AHRPKU8.png)

當 Button 被點擊時，應該要更新 TextView，而不是顯示固定內容，要修改的地方如下：

1. 切換到 activity_main.xml，點選 TextView，記下 id 屬性的值：textView
2. 回到 MainActivity.kt，刪去剛才的 Toast 程式碼

```kotlin=
rollButton.setOnClickListener {
  
}
```
3. 同樣使用 findViewById 來找到 TextView，宣告一個變數 resultTextView 來儲存 TextView

```kotlin=
val resultTextView: TextView = findViewById(R.id.textView)
```

4. 設定這個變數 text 為 6

```kotlin=
resultTextView.text = "6"
```

完成後的程式碼：

```kotlin=
package com.example.diceroller

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.TextView

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val rollButton: Button = findViewById(R.id.button)
        rollButton.setOnClickListener {
            val resultTextView: TextView = findViewById(R.id.textView)
            resultTextView.text = "6"
        }
    }
}
```

運行結果如下：

![](https://i.imgur.com/8UivnLi.png)

### Add the dice roll logic 加上擲骰子邏輯

接著要加上我們在 Kotlin Playground 實作的擲骰子邏輯。

1. 在 MainActivity class 中，建立 Dice class，裡面會有 fun roll() 方法

```kotlin=
class Dice(val numSides: Int) {

   fun roll(): Int {
       return (1..numSides).random()
   }
}
```

會看到 numSides 底下有波浪號，如果將 numSides 設為 private（私有），numSides 這個變數就只能在 Dice class 裡面使用：相對於 public，我們會在下一個章節會提到。

![](https://i.imgur.com/Ql62kSP.png)

2. 將點擊事件要執行的內容改為 rollDice()

```kotlin=
rollButton.setOnClickListener {
   rollDice()
}
```

3. 接著要實作 rollDice() 方法

```kotlin=
private fun rollDice() {
    TODO("Not yet implemented")
}
```

4. 在 rollDice() 中刪去 TODO()，建立新的 Dice 物件實體，以及擲骰子邏輯

```kotlin=
private fun rollDice() {
    val dice = Dice(6)          // 建立一個六面骰
    val diceRoll = dice.roll()  // 呼叫 roll() 來搖骰，並用變數儲存結果
    val resultTextView: TextView = findViewById(R.id.textView)  // 找到 TextView
    resultTextView.text = diceRoll.toString() // 把 diceRoll 的值轉成字串，並賦值給 textView 
}
```

### 精簡程式碼與加上註解

+ Reformat Code：選取所有程式碼，並點選狀態列的 Code > Reformat Code，可以格式化程式碼，去除不必要的空格。

完成的程式碼如下：

```kotlin=
package com.example.diceroller

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.TextView

/**
 * activity：讓使用者能夠擲骰子和看到結果
 */
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val rollButton: Button = findViewById(R.id.button)
        rollButton.setOnClickListener { rollDice() }
    }

    /**
     * 擲骰子和更新畫面上的結果
     */
    private fun rollDice() {
        // 建立句有六面的 Dice 物件，並呼叫 class 裡面的 roll() 方法
        val dice = Dice(6)
        val diceRoll = dice.roll()

        // 將擲骰子結果更新到畫面
        val resultTextView: TextView = findViewById(R.id.textView)
        resultTextView.text = diceRoll.toString()
    }

    /**
     * Dice 會有固定的面數值
     */
    class Dice(val numSides: Int) {

        // 回傳一個隨機數字，介於 1 到 numSides 之間
        fun roll(): Int {
            return (1..numSides).random()
        }
    }
}
```

運行結果，點選 ROLL 會隨機出現數字 1~6：

![](https://i.imgur.com/dL8dE1q.png)

### 小結

+ 透過 Layout Editor 新增一個 Button。
+ 修改 MainActivity.kt clas，新增 app 互動關係。
+ 彈出短暫的 Toast 訊息，可用來驗證程式邏輯。
+ 在 Button 監聽點擊事件，點擊時會執行 setOnClickListener() 裡面的行為。
+ 在 app 運行時，可透過元素呼叫方法來更新畫面，
+ 撰寫註解、統一程式碼風格均有助於多人協作。

---

## Add conditional behavior in Kotlin 新增條件狀態

我們能根據條件判斷，指定 app 做出對應的動作：而在 Kotlin 中的邏輯判斷使用的語法為 if、else 以及 when，當條件判斷的布林值為 true 時，才會執行大括弧內的程式碼：

```kotlin=
if (condition-is-true) {
    execute-this-code
}
```

以下方程式碼為例，假設有個變數為 4，經條件判斷後會執行 `num == 4` 內的程式碼：

```kotlin=
fun main() {
    val num = 4
    if (num > 4) {
        println("The variable is greater than 4")
    } else if (num == 4) {
        println("The variable is equal to 4")
    } else {
        println("The variable is less than 4")
    }
}

// The variable is equal to 4
```

### Create the Lucky Dice Roll game 建立擲幸運骰子遊戲

1. 接續上一章節的程式碼，我們要進一步新增功能

```kotlin=
fun main() {
    val myFirstDice = Dice(6)
    val diceRoll = myFirstDice.roll()
    println("Your ${myFirstDice.numSides} sided dice rolled ${diceRoll}!")
}

class Dice (val numSides: Int) {

    fun roll(): Int {
        return (1..numSides).random()
    }
}
```

2. 宣告變數 luckyNumber，用 if / else 條件判斷是否有出現幸運數字

```kotlin=
fun main() {
    val myFirstDice = Dice(6)
    val rollResult = myFirstDice.roll()
    val luckyNumber = 4

    if (rollResult == luckyNumber) {
        println("You win!")
    } else {
        println("You didn't win, try again!")
    }
}

class Dice (val numSides: Int) {

    fun roll(): Int {
        return (1..numSides).random()
    }
}
```

3. 或使用多個 else if 來對應所有可能結果，但注意開頭和結尾的 if 和 else 只能各一個

```kotlin=
fun main() {
    val myFirstDice = Dice(6)
    val rollResult = myFirstDice.roll()
    val luckyNumber = 4

    if (rollResult == luckyNumber) {
        println("You win!")
    } else if (rollResult == 1) {
        println("So sorry! You rolled a 1. Try again!")
    } else if (rollResult == 2) {
        println("Sadly, you rolled a 2. Try again!")
    } else if (rollResult == 3) {
        println("Unfortunately, you rolled a 3. Try again!")
    } else if (rollResult == 4) {
        println("No luck! You rolled a 4. Try again!")
    } else if (rollResult == 5) {
        println("Don't cry! You rolled a 5. Try again!")
    } else {
        println("Apologies! you rolled a 6. Try again!")
    }
}

class Dice (val numSides: Int) {

    fun roll(): Int {
        return (1..numSides).random()
    }
}
```

### Use a when statement 使用 when 判斷

透過上方程式碼，可以發現一旦有更多結果，會使程式碼更加冗長，這時可以透過 when 判斷句來解決。

1. 把剛才的 if / else 判斷改成 when

```kotlin=
fun main() {
    val myFirstDice = Dice(6)
    val rollResult = myFirstDice.roll()
    val luckyNumber = 4

    when (rollResult) {
      // do something
    }
}
```

2. 變數加箭頭，代表當 rollResult 和 luckyNumber 的值相同時，就執行後面的程式碼

```kotlin=
when (rollResult) {
  luckyNumber -> println("You win!")
}
```

3. 用相同的形式，完成當結果為 1-6 的程式碼，但是當骰到幸運數字 4 時，可以發現印出的字串會是 "You won!"

```kotlin=
fun main() {
    val myFirstDice = Dice(6)
    val rollResult = myFirstDice.roll()
    val luckyNumber = 4

    when (rollResult) {
        luckyNumber -> println("You won!")
        1 -> println("So sorry! You rolled a 1. Try again!")
        2 -> println("Sadly, you rolled a 2. Try again!")
        3 -> println("Unfortunately, you rolled a 3. Try again!")
        4 -> println("No luck! You rolled a 4. Try again!")
        5 -> println("Don't cry! You rolled a 5. Try again!")
        6 -> println("Apologies! you rolled a 6. Try again!")
    }
}

class Dice(val numSides: Int) {
    fun roll(): Int {
        return (1..numSides).random()
    }
}
```

### 小結

+ 使用 if 條件判斷是否要執行某些指令。例如：骰中幸運數字就印出中獎訊息。
+ Boolean 布林值型別的 true 和 false 可用來判斷結果。
+ 使用運算子比較值：大於 `>`、小於 `<`、等於 `==`。
+ 可使用 else if 進行多種條件判斷。例如：針對不同骰子結果進出對應訊息。而最後的 else 會包含所有未被指定的結果。
+ 使用 when 以 compact form 形式比較變數的值。

if-else 條件判斷：

```kotlin=
if (condition-is-true) {
  execute-this-code
} else if (condition-is-true) {
  execute-this-code
} else {
  execute-this-code
}
```

when 條件判斷：

```kotlin=
when (variable) {
  matches-value -> execute-this-code
  matches-value -> execute-this-code
  ...
}
```

## Add images to the Dice Roller app 新增圖片

除了用 TextView 顯示骰子的值，接下來我們要搭配剛才的 when 條件句，並使用圖片來顯示結果：

![](https://i.imgur.com/sfvGcXx.png)

學習目標：

+ 如何在程式運動時更新 ImageView
+ 如何透過 when 判斷句，自訂 app 顯示畫面結果

### Update the layout for the app 更新佈局

接著把 TextView 換成 ImageView，使用圖片來顯示結果。

1. 從元件樹移除 TextView，並從 Palette 新增一個 ImageView
2. 在視窗選擇 avatars，稍後我們會換成骰子的圖片，點選 OK 建立
![](https://i.imgur.com/ilAAjHN.png)

3. 調整 ImageView 水平垂直位置

![](https://i.imgur.com/6vmnne8.png)

### Add the dice images 新增骰子圖片

1. 從這裡 [URL](https://github.com/google-developer-training/android-basics-kotlin-dice-roller-with-images-app-solution/raw/master/dice_images.zip) 下載圖片檔，包含骰子數 1~6 共六張圖片
2. 點選 View > Tool Windows > Resource Manager，再點選 + 符號下拉選單，開啟 Import Drawables

![](https://i.imgur.com/csJ1Oo6.png)

3. 選取剛才六張圖片匯入，圖片會出現在 Resource Manager，路徑是 app>res>drawable

這些圖片的 resource 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
```

### Use the dice images 使用骰子圖

接下來要替換掉剛才的 avatar 範例圖。

1. 點選 ImageView，找到屬性欄位的 tools srcCompat 屬性，改成 dice_1 並點選 OK，會發現圖片佔滿整個螢幕

![](https://i.imgur.com/kcSW1lU.png)

2. 調整 ImageView 的寬高，找到屬性視窗的  layout_width 和 layout_height 屬性，現在的值為 wrap_content
3. 將寬高改為固定值，分別是 height: 200dp 和 width: 160dp

![](https://i.imgur.com/2JyGOvC.png)

4. 修改 Button 的 top 距離為 16dp

![](https://i.imgur.com/XlmXorH.png)

這時如果想運行程式，會看到 Build failed 錯誤訊息：

![](https://i.imgur.com/bn9QvJ8.png)


這是因為我們剛才把 TextView 從佈局中移除，但 MainActivity.kt 還沒有修改：

![](https://i.imgur.com/e7YEsFF.png)

5. 移除有使用到 textView 的兩行程式碼
6. 匯入 ImageView，測試點擊後圖片是否有變成 dice_2

```kotlin=
    private fun rollDice() {
        // 建立句有六面的 Dice 物件，並呼叫 class 裡面的 roll() 方法
        val dice = Dice(6)
        val diceRoll = dice.roll()
        // 更新骰子圖片
        val diceImage: ImageView = findViewById(R.id.imageView)
        // 先固定為 dice_2 測試結果
        diceImage.setImageResource(R.drawable.dice_2)
    }

```

7. 點擊 Button 時，骰子圖片會變成兩點 dice_2

### Display the correct dice image based on the dice roll 設定對應的骰子圖片

我們可以透過 pseudocode（虛擬碼）來模擬程式邏輯：

+ if 骰到數字 1 時，顯示 dice_1 圖片
+ if 骰到數字 2 時，顯示 dice_2 圖片
+ etc...

轉換成 if / else 語法如下：

```kotlin=
if (diceRoll == 1) {
   diceImage.setImageResource(R.drawable.dice_1)
} else if (diceRoll == 2) {
   diceImage.setImageResource(R.drawable.dice_2)
}
 ...
```

或是更精簡的 when 語法：

```kotlin=
when (diceRoll) {
   1 -> diceImage.setImageResource(R.drawable.dice_1)
   2 -> diceImage.setImageResource(R.drawable.dice_2)
   ...
```

1. 修改後程式碼如下

```kotlin=
private fun rollDice() {
    // 建立句有六面的 Dice 物件，並呼叫 class 裡面的 roll() 方法
    val dice = Dice(6)
    val diceRoll = dice.roll()
    // 更新骰子圖片
    val diceImage: ImageView = findViewById(R.id.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)
    }
}
```

運行程式結果，擲骰的數字會對應到圖片：

![](https://i.imgur.com/wLipaVq.gif)

2. 上方程式碼其實可以再精簡，透過宣告 drawableResource 變數來替換掉重複的 diceImage.setImageResource：

```kotlin=
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 底下出現紅色波浪的錯誤訊息：

![](https://i.imgur.com/2e5nsI1.png)

3. 把數字 6 改為 else，即可包括非 1~5 的結果：

```kotlin=
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)
```

4. 隨著每次的 diceRoll 結果，更新 ImageView 的內容敘述

```kotlin=
diceImage.contentDescription = diceRoll.toString()
```

### 設定初始圖片

在 onCreate() 中加上 rollDice()，當程式初始化時就會先擲一次骰子

```kotlin=
    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()
    }
```

參考完整程式碼：[GitHub Repo](https://github.com/heidiliu2020/android-basics-kotlin-dice-roller-app)

### 小結

+ 使用 setImageResource() 修改 ImageView 顯示的圖片。
+ 使用 if / else 或 when 條件判斷句，根據不同結果執行對應的程式碼。