[![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 條件判斷句,根據不同結果執行對應的程式碼。