建立互動式的 Dice Roller 應用程式

1. 事前準備

在這個程式碼研究室中,您將建立互動式 Dice Roller 應用程式,讓使用者輕觸 Button 可組合項來擲骰子。擲骰子結果會透過 Image 可組合項顯示在螢幕上。

您可使用 Jetpack Compose 搭配 Kotlin 來建構應用程式的版面配置,然後編寫商業邏輯來處理使用者輕觸 Button 可組合項時觸發的行為。

必要條件

  • 能夠在 Android Studio 中建立及執行基本的 Compose 應用程式。
  • 熟悉在應用程式中使用 Text 可組合項的方式。
  • 瞭解如何將文字擷取為字串資源,以利翻譯應用程式並重複使用字串。
  • Kotlin 程式設計基本概念。

課程內容

  • 如何使用 Compose 將 Button 可組合項新增至 Android 應用程式。
  • 如何使用 Compose 將行為新增至 Android 應用程式中的 Button 可組合項。
  • 如何開啟及修改 Android 應用程式的 Activity 程式碼。

建構項目

  • 名為 Dice Roller 的互動式 Android 應用程式,可讓使用者擲骰子並顯示結果。

事前準備

  • 已安裝 Android Studio 的電腦。

完成本程式碼研究室後,應用程式會如下所示:

3e9a9f44c6c84634.png

2. 建立基準

建立專案

  1. 在 Android Studio 中,依序選取「File」>「New」>「New Project」
  2. 在「New Project」對話方塊中,選取「Empty Activity」,然後按一下「Next」

39373040e14f9c59.png

  1. 在「Name」欄位中輸入 Dice Roller
  2. 在「Minimum SDK」欄位中,從選單中選取最低 API 級別 24 (Nougat),然後按一下「Finish」

8fd6db761068ca04.png

3. 建立版面配置基礎架構

預覽專案

如何預覽專案:

  • 在「Split」或「Design」窗格中,按一下「Build & Refresh」

9f1e18365da2f79c.png

現在「Design」窗格中應該會顯示預覽畫面。如果預覽畫面看起來很小,請不用擔心,因為預覽畫面會隨著您修改版面配置而變化。

b5c9dece74200185.png

重組程式碼範例

您必須變更部分已產生的程式碼,讓結果與 Dice Roller 應用程式的主題更為相似。

如最終應用程式的螢幕截圖所示,當中會有一張骰子圖片和一個用來擲骰子的按鈕。您將建構可組合函式來反映這個架構。

如何重組程式碼範例:

  1. 移除 GreetingPreview() 函式。
  2. 建立含有 @Composable 註解的 DiceWithButtonAndImage() 函式。

這個可組合函式代表版面配置的 UI 元件,同時也保留了按鈕點選和圖片顯示邏輯。

  1. 移除 Greeting(name: String, modifier: Modifier = Modifier) 函式。
  2. 建立含有 @Preview@Composable 註解的 DiceRollerApp() 函式。

這個應用程式僅由一個按鈕和圖片組成,因此您可以把這個可組合函式視為應用程式本身;所以也稱之為 DiceRollerApp() 函式。

MainActivity.kt

@Preview
@Composable
fun DiceRollerApp() {

}

@Composable
fun DiceWithButtonAndImage() {

}

由於您移除了 Greeting() 函式,DiceRollerTheme() lambda 主體中對 Greeting("Android") 的呼叫會以紅色標示;這是因為編譯器再也找不到該函式的參照。

  1. 刪除在 onCreate() 方法中 setContent{} lambda 內的所有程式碼。
  2. setContent{} lambda 主體中,呼叫 DiceRollerTheme{} lambda,然後在 DiceRollerTheme{} lambda 中呼叫 DiceRollerApp() 函式。

MainActivity.kt

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
        DiceRollerTheme {
            DiceRollerApp()
        }
    }
}
  1. DiceRollerApp() 函式中呼叫 DiceWithButtonAndImage() 函式。

MainActivity.kt

@Preview
@Composable
fun DiceRollerApp() {
    DiceWithButtonAndImage()
}

新增修飾符

Compose 會使用 Modifier 物件,這類物件是一組元素,用來修飾或修改 Compose UI 元素的行為。您可以藉此設定 Dice Roller 應用程式元件的 UI 元件樣式。

如何新增修飾符:

  1. 修改 DiceWithButtonAndImage() 函式,以接受 Modifier 類型的 modifier 引數,並指派預設值 Modifier

MainActivity.kt

@Composable
fun DiceWithButtonAndImage(modifier: Modifier = Modifier) {
}

先前的程式碼片段可能會讓您有點混淆,因此接下來我們會詳細說明。這個函式允許傳入 modifier 參數。modifier 參數的預設值是 Modifier 物件,因此 = Modifier 為方法簽章的一部分。有了預設參數值,日後凡是呼叫這個方法的使用者,都能決定是否要傳遞參數值。他們只要傳遞自己的 Modifier 物件,就可以自訂 UI 的行為和修飾設定。而如果選擇不傳遞 Modifier 物件,則系統會假設參數為預設值,也就是原本無修飾的 Modifier 物件。您可以將此做法套用至任何參數。如要進一步瞭解預設引數,請參閱「Default arguments」。

  1. 現在 DiceWithButtonAndImage() 可組合函式含有修飾符參數,請在呼叫該可組合函式時傳遞修飾符。由於 DiceWithButtonAndImage() 函式的方法簽章已變更,因此呼叫函式時必須傳入包含目標修飾設定的 Modifier 物件。Modifier 類別主要用於修飾或新增行為至 DiceRollerApp() 函式中的可組合項。在本範例中,需要將一些重要的修飾設定新增到傳遞至 DiceWithButtonAndImage() 函式的 Modifier 物件。

您可能會好奇,為何在有預設值的情況下還需要傳遞 Modifier 引數。這是因為可組合項可能會「重新組合」,基本上代表 @Composable 方法中的程式碼區塊會再次執行。如果程式碼區塊中建立了 Modifier 物件,系統可能會重新建立這個物件,導致效率不彰。本程式碼研究室會在後續章節介紹重新組合的概念。

MainActivity.kt

DiceWithButtonAndImage(modifier = Modifier)
  1. fillMaxSize() 方法鏈結至 Modifier 物件上,讓版面配置填滿整個螢幕。

這個方法會指定元件應填滿可用空間。在這個程式碼研究室中,您先前看到了 Dice Roller 應用程式最後呈現的 UI 螢幕截圖。值得一提的特色是,骰子和按鈕位於畫面中央。wrapContentSize() 方法會指定可用空間至少應與其所含元件一樣大。不過,由於使用的是 fillMaxSize() 方法,因此如果版面配置中的元件小於可用空間,您可以將 Alignment 物件傳遞至 wrapContentSize() 方法,藉此指定元件在可用空間中對齊的方式。

MainActivity.kt

DiceWithButtonAndImage(modifier = Modifier
    .fillMaxSize()
)
  1. wrapContentSize() 方法鏈結至 Modifier 物件,然後傳遞 Alignment.Center 做為引數,讓元件出現在畫面中心。Alignment.Center 會指定元件同時在垂直和水平方向上置中。

MainActivity.kt

DiceWithButtonAndImage(modifier = Modifier
    .fillMaxSize()
    .wrapContentSize(Alignment.Center)
)

4. 建立垂直版面配置

在 Compose 中,垂直版面配置是使用 Column() 函式建立。

Column() 函式是一種可組合的版面配置,可將子元素以垂直順序排列。在預期的應用程式設計中,您可以看到骰子圖片顯示在擲骰子按鈕的垂直上方。

7d70bb14948e3cc1.png

如何建立垂直版面配置:

  1. DiceWithButtonAndImage() 函式中新增 Column() 函式。
  1. modifier 引數從 DiceWithButtonAndImage() 方法簽章傳遞至 Column() 的修飾符引數。

modifier 引數可確保 Column() 函式中的可組合項符合 modifier 例項上呼叫的限制。

  1. horizontalAlignment 引數傳遞至 Column() 函式,然後將引數設為 Alignment.CenterHorizontally 的值。

這能確保該欄內的子項會依據畫面寬度,在裝置螢幕上置中。

MainActivity.kt

fun DiceWithButtonAndImage(modifier: Modifier = Modifier) {
    Column (
        modifier = modifier,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {}
}

5. 新增按鈕

  1. strings.xml 檔案中新增字串,並將字串設為 Roll 值。

res/values/strings.xml

<string name="roll">Roll</string>
  1. Column() 的 lambda 主體中新增 Button() 函式。
  1. MainActivity.kt 檔案中,將 Text() 函式加到函式 lambda 主體中的 Button()
  2. roll 字串的字串資源 ID 傳遞至 stringResource() 函式,並將結果傳遞至 Text 可組合項。

MainActivity.kt

Column(
    modifier = modifier,
    horizontalAlignment = Alignment.CenterHorizontally
) {
    Button(onClick = { /*TODO*/ }) {
        Text(stringResource(R.string.roll))
    }
}

6. 加入圖片

應用程式的另一個基本元件是骰子圖片,會在使用者輕觸「Roll」按鈕後顯示結果。您將透過 Image 可組合項新增圖片,但這需要圖片資源,因此您必須先下載要為這個應用程式提供的一些圖片。

下載骰子圖片

  1. 開啟這個網址,將骰子圖片的 ZIP 檔案下載至電腦,然後等待下載作業完成。

在電腦中找到該檔案,檔案可能位於「Downloads」資料夾。

  1. 將 ZIP 檔案解壓縮以建立新的 dice_images 資料夾,內含顯示骰子點數 1 到 6 的 6 個骰子圖片檔。

新增骰子圖片至應用程式

  1. 在 Android Studio 中,依序按一下「View」>「Tool Windows」>「Resource Manager」
  2. 依序點選「+」>「Import Drawables」,開啟檔案瀏覽器。

12f17d0b37dd97d2.png

  1. 找出並選取六個骰子的圖片資料夾,然後繼續上傳。

上傳的圖片如下所示。

4f66c8187a2c58e2.png

  1. 點選「Next」

688772df9c792264.png

畫面上會出現「Import Drawables」對話方塊,並顯示資源檔案在檔案結構中的位置。

  1. 按一下「Import」,確認要匯入這六張圖片。

圖片應顯示在「Resource Manager」窗格中。

c2f08e5311f9a111.png

大功告成!在下一項工作中,您會在應用程式中使用這些圖片。

新增 Image 可組合函式

骰子圖片應顯示在「Roll」按鈕上方。Compose 原本就會按照順序排列 UI 元件。換句話說,先宣告的可組合函式就會先顯示。這表示第一個宣告會顯示在隨後宣告的可組合項上方或前面。Column 可組合項中的可組合項會於裝置上顯示在彼此的上方/下方。在這個應用程式中,您使用 Column 垂直堆疊可組合項,因此,系統會先顯示 Column() 函式中最先宣告的可組合項,接著才會顯示相同 Column() 函式中隨後宣告的可組合項。

如何新增 Image 可組合項:

  1. Column() 函式主體中,在 Button() 函式的前面建立 Image() 函式。

MainActivity.kt

Column(
    modifier = modifier,
    horizontalAlignment = Alignment.CenterHorizontally
) {
    Image()
    Button(onClick = { /*TODO*/ }) {
      Text(stringResource(R.string.roll))
    }
}
  1. painter 引數傳遞至 Image() 函式,然後為引數指派 painterResource 值,以便接受可繪製資源 ID 引數。目前,請傳遞下列資源 ID:R.drawable.dice_1 引數。

MainActivity.kt

Image(
    painter = painterResource(R.drawable.dice_1)
)
  1. 在應用程式中建立圖片時,應一併提供所謂的「內容說明」。內容說明是 Android 開發作業中相當重要的一環。這些內容說明會分別在相應的 UI 元件中附加,進而提升無障礙功能。如要進一步瞭解內容說明,請參閱「描述各個 UI 元素」。您可以將內容說明做為參數傳遞至圖片。

MainActivity.kt

Image(
    painter = painterResource(R.drawable.dice_1),
    contentDescription = "1"
)

現在已備妥所有必要的 UI 元件。不過,ButtonImage 之間似乎有點擁擠。

54b27140071ac2fa.png

  1. 如要修正這項問題,請在 ImageButton 可組合函式之間新增 Spacer 可組合函式。Spacer 會將 Modifier 做為參數。在此案例中,Image 位於 Button 上方,所以兩者之間必須有垂直空格。因此,您可以將 Modifier 的高度設定為套用至 Spacer。建議將高度設為 16.dp。一般來說,dp 尺寸會以 4.dp 為單位遞增。

MainActivity.kt

Spacer(modifier = Modifier.height(16.dp))
  1. 在「Preview」窗格中,按一下「Build & Refresh」

您應該會看到類似下圖的畫面:

73eea4c166f7e9d2.png

7. 建構擲骰子邏輯

現在您已備妥所有必要可組合項,接著可以修改應用程式,讓使用者能輕觸按鈕來擲骰子。

製作互動式按鈕

  1. Column() 函式前面的 DiceWithButtonAndImage() 函式中,建立一個 result 變數,並將變數設為 1 值。
  2. 請查看 Button 可組合項。您將會發現系統已傳遞 onClick 參數至該組合元件;該參數已設為包含 /*TODO*/ 註解的一對大括號。大括號在這裡是指所謂的 lambda,括號內的區域是 lambda 主體。當函式做為引數傳遞時,也可以稱為「回呼」。

MainActivity.kt

Button(onClick = { /*TODO*/ })

lambda 是一種函式常值,與其他函式類似,但不會以 fun 關鍵字另外宣告,而是以內嵌方式寫入並以運算式的形式傳遞。Button 可組合函式會預期函式以 onClick 參數的形式傳遞。這種情況很適合使用 lambda,您將會在這個部分編寫 lambda 主體。

  1. Button() 函式中,從 onClick 參數 lambda 主體的值移除 /*TODO*/ 註解。
  2. 擲骰子是隨機的。如要在程式碼中反映這個特性,您必須使用正確的語法來產生隨機數值。在 Kotlin 中,您可以針對數字範圍使用 random() 方法。在 onClick lambda 主體中,將 result 變數設為介於 1 到 6 之間的範圍,然後針對該範圍呼叫 random() 方法。請記住,在 Kotlin 中,範圍的指定方式是在範圍中的第一個數字和最後一個數字之間加上兩個半形句號。

MainActivity.kt

fun DiceWithButtonAndImage(modifier: Modifier = Modifier) {
    var result = 1
    Column(
        modifier = modifier,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Image(
            painter = painterResource(R.drawable.dice_1),
            contentDescription = "1"
        )
        Spacer(modifier = Modifier.height(16.dp))
        Button(onClick = { result = (1..6).random() }) {
            Text(stringResource(R.string.roll))
        }
    }
}

現在這個按鈕是可輕觸的,但輕觸按鈕並不會產生任何視覺變化,因為您仍然必須建構這項功能。

新增條件至 Dice Roller 應用程式

在上一節中,您建立了 result 變數,並將其硬式編碼為 1 值。最後,result 變數值會在使用者輕觸「Roll」按鈕時重設,並據此決定應顯示哪一張圖片。

可組合函式預設為無狀態,也就是說,函式本身不會保留任何值,且能隨時由系統重新組合,而這麼做就會重設函式的值。不過,Compose 能協助您輕鬆避免這種情況。透過 remember 可組合項,可組合函式能夠將物件儲存在記憶體中。

  1. result 變數設為 remember 可組合項。

您必須傳遞函式到 remember 可組合項。

  1. remember 可組合項主體傳入 mutableStateOf() 函式,然後向函式傳遞 1 引數。

mutableStateOf() 函式會傳回可觀察物件。稍後,您將進一步瞭解可觀察物件,但基本上,這意味著當 result 變數的值變更時,系統會觸發重組程序、反映結果值,並重新整理 UI。

MainActivity.kt

var result by remember { mutableStateOf(1) }

現在,只要輕觸按鈕,result 變數就會以隨機數值更新。

result 變數現在可用來決定要顯示的圖片。

  1. result 變數例項化的下方,建立一個不可變的 imageResource 變數,將其設為接受 result 變數的 when 陳述式,然後將每個可能結果設為該變數的可繪項目。

MainActivity.kt

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
}
  1. 將傳遞到 Image 可組合項 painterResource 參數的 ID 從 R.drawable.dice_1 可繪項目變更為 imageResource 變數。
  2. result 變數轉換為包含 toString() 的字串,並做為 contentDescription 傳遞,藉此變更 Image 可組合項的 contentDescription 參數,以反映 result 變數的值。

MainActivity.kt

Image(
   painter = painterResource(imageResource),
   contentDescription = result.toString()
)
  1. 執行應用程式。

您的 Dice Roller 應用程式現在應該可以完整運作!

3e9a9f44c6c84634.png

8. 取得解決方案程式碼

完成程式碼研究室後,如要下載當中用到的程式碼,您可以使用以下 Git 指令:

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-dice-roller.git

另外,您也可以下載存放區為 ZIP 檔案,然後解壓縮並在 Android Studio 中開啟。

如要查看解決方案程式碼,請前往 GitHub

  1. 前往專案所在的 GitHub 存放區頁面。
  2. 驗證分支版本名稱與程式碼研究室中指定的分支版本名稱相符。例如,在下列螢幕截圖中,分支版本名稱為「main」

1e4c0d2c081a8fd2.png

  1. 在專案的 GitHub 頁面中,按一下「Code」按鈕,畫面上會出現彈出式視窗。

1debcf330fd04c7b.png

  1. 在彈出式視窗中,按一下「Download ZIP」按鈕,將專案儲存至電腦。等待下載作業完成。
  2. 在電腦中找到該檔案 (可能位於「下載」資料夾中)。
  3. 按兩下解壓縮 ZIP 檔案。這項操作會建立含有專案檔案的新資料夾。

在 Android Studio 中開啟專案

  1. 啟動 Android Studio。
  2. 在「Welcome to Android Studio」視窗中,按一下「Open」

d8e9dbdeafe9038a.png

注意:如果 Android Studio 已開啟,請改為依序選取「File」>「Open」選單選項。

8d1fda7396afe8e5.png

  1. 在檔案瀏覽器中,前往已解壓縮的專案資料夾所在的位置 (可能位於「Downloads」資料夾中)。
  2. 按兩下該專案資料夾。
  3. 等待 Android Studio 開啟專案。
  4. 按一下「Run」按鈕 8de56cba7583251f.png 即可建構並執行應用程式。請確認應用程式的建構方式符合預期。

9. 結論

您已使用 Compose 建立了 Android 互動式 Dice Roller 應用程式!

摘要

  • 定義可組合函式。
  • 透過組合來建立版面配置。
  • 透過 Button 可組合項建立按鈕。
  • 匯入 drawable 資源。
  • 透過 Image 可組合項顯示圖片。
  • 使用可組合項打造互動式 UI。
  • 透過 remember 可組合項將組合中的物件儲存至記憶體中。
  • 使用 mutableStateOf() 函式重新整理 UI 以建立可觀察物件。

瞭解詳情