1. 事前準備
在這個課程中,您已學到如何在應用程式中加入按鈕,以及如何修改應用程式來回應按鈕點選動作。接下來就要建構應用程式,練習運用所學知識。
您將建立名為 Lemonade 的應用程式。首先,請詳閱 Lemonade 應用程式的需求條件,瞭解應用程式應有的外觀和行為模式。如果您想挑戰自我,可以直接開始建構應用程式。假如遇到困難,閱讀後續章節即可取得更多提示與指引,瞭解如何分析並逐步解決問題。
請依照自己的步調完成這個練習。建構應用程式功能的各個部分時可以花些時間仔細思考。雖然我們最後會提供 Lemonade 應用程式的解決方案程式碼,但建議您先自行嘗試建構應用程式,再查看解決方案。提醒您,我們提供的解決方案並非建構 Lemonade 應用程式的唯一方法,您可以用其他方式建構應用程式,只要符合應用程式需求條件即可。
必要條件
- 能利用文字和圖片可組合函式,在 Compose 中建立簡易 UI 版面配置
- 能建構可回應按鈕點選動作的互動式應用程式
- 對組成和重新組成有基本瞭解
- 熟悉 Kotlin 程式設計語言的基本概念,包括函式、變數、條件式與 lambda
軟硬體需求
- 已安裝 Android Studio 且具備網路連線能力的電腦。
2. 應用程式總覽
您將協助我們實際製作數位檸檬汁!這項練習的目標,是要建立簡易的互動式應用程式,可讓您在畫面上輕觸圖片來擠出檸檬汁,直到裝滿一杯檸檬汁為止。這可以視為某種比喻,或是趣味的消遣活動!
以下說明應用程式的運作方式:
- 使用者首次啟動應用程式時,會看到一棵檸檬樹。畫面上會顯示一個標籤,提示使用者輕觸檸檬樹圖片,從樹上「選取」一顆檸檬。
- 使用者輕觸檸檬樹後,畫面上會顯示一顆檸檬,並提示使用者輕觸檸檬來「擠」檸檬汁。使用者需要輕觸檸檬多次才能擠出檸檬汁,而輕觸次數取決於系統隨機產生的數值。這個值介於 2 到 4,每次都不一定。
- 輕觸檸檬的次數達到要求後,畫面上就會出現一杯新鮮的檸檬汁!系統會要求使用者輕觸杯子來「飲用」檸檬汁。
- 使用者輕觸裝著檸檬汁的杯子後,畫面上會顯示空杯,並要求使用者輕觸空杯來重新開始。
- 使用者輕觸空杯後,就會看到檸檬樹,並可以重新開始整個流程,再多擠一些檸檬汁!
以下提供較大的螢幕截圖,方便您瞭解應用程式的外觀:
針對每個檸檬汁製作步驟,畫面上會顯示不同的圖片和文字標籤,應用程式回應點選動作的方式也不盡相同。舉例來說,當使用者輕觸檸檬樹時,應用程式會顯示檸檬。
您的任務是建構應用程式的 UI 版面配置及實作相關邏輯,讓使用者順利完成所有製作檸檬汁的步驟。
3. 開始操作
建立專案
在 Android Studio 中,使用「Empty Activity」範本建立新專案,並加入下列詳細資料:
- 名稱:Lemonade
- 套件名稱:com.example.lemonade
- 最低 SDK:24
您成功建立應用程式,也建構專案後,請繼續進行下一部分。
新增圖片
請先取得要用於 Lemonade 應用程式的四個向量可繪項目檔案。
如何取得檔案:
- 下載應用程式的圖片 ZIP 檔案。
- 按兩下 ZIP 檔案,系統隨即會將圖片解壓縮至資料夾。
- 將圖片新增到應用程式的
drawable
資料夾。如果忘記如何執行這個步驟,請參閱「建立互動式的 Dice Roller 應用程式」程式碼研究室。
專案資料夾應如以下螢幕截圖所示,lemon_drink.xml
、lemon_restart.xml
、lemon_squeeze.xml
和 lemon_tree.xml
素材資源現在會顯示在「res」>「drawable」目錄中:
- 按兩下向量可繪項目檔案,即可預覽圖片。
- 選取「Design」窗格 (而非「Code」或「Split」檢視畫面),則可查看圖片的完整寬度檢視畫面。
在應用程式中加入圖片檔案後,您可以在程式碼中參照這些檔案。比方說,如果向量可繪項目檔案的名稱為 lemon_tree.xml
,您就能在 Kotlin 程式碼中以資源 ID 參照該可繪項目,資源 ID 的格式為 R.drawable.lemon_tree
。
新增字串資源
在專案的「res」>「value」>「string.xml」檔案中,加入以下字串:
Tap the lemon tree to select a lemon
Keep tapping the lemon to squeeze it
Tap the lemonade to drink it
Tap the empty glass to start again
此外,專案中還需要下列字串。這些字串不會顯示在畫面上的使用者介面中,但可做為應用程式圖片的內容說明,描述圖片的內容為何。請將這些額外字串加入應用程式的 strings.xml
檔案:
Lemon tree
Lemon
Glass of lemonade
Empty glass
如果您不記得如何在應用程式中宣告字串資源,請參閱「建立互動式的 Dice Roller 應用程式」程式碼研究室或「字串」一節。請為每個字串資源提供適當的 ID 名稱,描述資源所含的值。舉例來說,如果是 "Lemon"
字串,您可以在 strings.xml
檔案中使用 ID 名稱 lemon_content_description
宣告該字串,然後在程式碼中使用資源 ID R.string.lemon_content_description
進行參照。
檸檬汁製作步驟
您現在具備實作應用程式所需的字串資源和圖片素材資源。以下概要說明應用程式的每個步驟,以及畫面上顯示的內容:
步驟 1:
- 文字:
Tap the lemon tree to select a lemon
- 圖片:檸檬樹 (
lemon_tree.xml
)
步驟 2:
- 文字:
Keep tapping the lemon to squeeze it
- 圖片:檸檬 (
lemon_squeeze.xml
)
步驟 3:
- 文字:
Tap the lemonade to drink it
- 圖片:一杯裝滿的檸檬汁 (
lemon_drink.xml
)
步驟 4:
- 文字:
Tap the empty glass to start again
- 圖片:空杯 (
lemon_restart.xml
)
改善應用程式外觀
如要讓您的應用程式畫面如這些螢幕截圖所示,您還必須針對應用程式外觀進行幾項調整:
- 將文字的字型大小增加到大於預設字型大小,例如
18sp
。 - 在文字標籤和下方圖片之間加入額外空間,避免兩者太靠近,例如
16dp
。 - 為按鈕加上強調色和些微的圓角效果,讓使用者知道可以輕觸圖片。
如果您想挑戰自我,可以根據應用程式運作方式的說明,自行建構應用程式的其餘部分。如需詳細指引,請繼續進行下一個部分。
4. 規劃應用程式的建構方式
建構應用程式時,建議您先打造出應用程式的基礎可運作版本,然後逐步新增更多功能,直到加入所需的全部功能。請找出您能先建構的一部分端對端功能。
請注意,Lemonade 應用程式的一大重點在於步驟之間的轉換,且每個步驟要顯示不同的圖片和文字標籤。您可以先忽略擠檸檬狀態的特殊行為,等建構完應用程式的基礎後再新增這項功能。
以下為建構應用程式的概略步驟,供您參考:
- 為製作檸檬汁的第一個步驟建構 UI 版面配置,提示使用者從樹上選取一顆檸檬。您可以暫時忽略圖片邊框,之後再新增這個視覺細節。
- 在應用程式中實作相關行為,讓應用程式在使用者輕觸檸檬樹時顯示檸檬圖示和對應的文字標籤。這涵蓋製作檸檬汁的前兩個步驟。
- 新增程式碼,讓應用程式在每次使用者輕觸圖片時顯示其餘的檸檬汁製作步驟。這時只要輕觸檸檬,顯示圖片就可能變成一杯檸檬汁。
- 為擠檸檬的步驟新增自訂行為,讓使用者需要「擠檸檬」或輕觸檸檬特定次數,該次數值為介於 2 到 4 的隨機數字。
- 進行任何其他必要的視覺調整來完成應用程式,例如變更字型大小、為圖片加上邊框,讓應用程式看起來更美觀。請確認應用程式採用良好的程式設計做法,例如遵循 Kotlin 程式設計樣式規範,並在程式碼中加入註解。
如果您能利用上述概略步驟實作 Lemonade 應用程式,請自行建構應用程式。如果需要這五個步驟的額外指引,請繼續瀏覽下一節。
5. 實作應用程式
建構 UI 版面配置
首先請修改應用程式,讓應用程式畫面中央顯示檸檬樹圖片和對應的文字標籤 (Tap the lemon tree to select a lemon
)。文字和下方圖片之間應有 16dp
的空間。
如有需要,您可以使用 MainActivity.kt
檔案中的以下範例程式碼:
package com.example.lemonade
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.example.lemonade.ui.theme.LemonadeTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
LemonadeTheme {
LemonApp()
}
}
}
}
@Composable
fun LemonApp() {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Text(text = "Hello there!")
}
}
@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
LemonadeTheme {
LemonApp()
}
}
這個程式碼類似 Android Studio 自動產生的程式碼,但定義的並非 Greeting()
可組合函式,而是不預期有參數的 LemonApp()
可組合函式。DefaultPreview()
可組合函式也會更新為使用 LemonApp()
可組合函式,方便您輕鬆預覽程式碼。
在 Android Studio 中輸入這個程式碼後,請修改 LemonApp()
可組合函式,函式中應包含應用程式的內容。以下提供幾個引導問題協助您思考:
- 您會使用哪些可組合函式?
- 有標準的 Compose 版面配置元件可協助您將可組合函式安排至所需位置嗎?
實作這個步驟,讓應用程式在啟動時顯示檸檬樹和文字標籤。在 Android Studio 中預覽可組合項目,在修改程式碼的同時查看 UI 的呈現效果。執行應用程式,確保應用程式的外觀看起來如上方螢幕截圖所示。
如要進一步瞭解如何新增使用者輕觸圖片時的應用程式行為,請在完成後回來查看下方的操作說明。
新增點選行為
接下來,您必須新增程式碼,讓應用程式在使用者輕觸檸檬樹圖示後顯示檸檬圖示和文字標籤 Keep tapping the lemon to squeeze it
。換句話說,使用者輕觸檸檬樹的動作,會促使系統變更畫面上的文字和圖片。
在本課程的先前部分中,您已瞭解如何建立可點選的按鈕。在 Lemonade 應用程式中,雖然沒有 Button
可組合函式,但您可以將任何可組合函式 (不限於按鈕) 變為可點選狀態,只要對其指定 clickable
修飾符即可。如需範例,請參閱「clickable」說明文件頁面。
使用者點選圖片後應出現什麼變化?用於實作這個行為的程式碼有點複雜,因此接下來要先回顧一款熟悉的應用程式。
查看 Dice Roller 應用程式
請回顧 Dice Roller 應用程式的程式碼,觀察該應用程式如何根據擲骰結果的值顯示不同的骰子圖片:
Dice Roller 應用程式中的 MainActivity.kt
...
@Composable
fun DiceWithButtonAndImage(modifier: Modifier = Modifier) {
var result by remember { mutableStateOf(1) }
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
}
Column(modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally) {
Image(painter = painterResource(id = imageResource), contentDescription = result.toString())
Button(onClick = { result = (1..6).random() }) {
Text(stringResource(id = R.string.roll))
}
}
}
...
請思考下列有關 Dice Roller 應用程式程式碼的問題:
- 哪個變數的值會決定出適當的骰子顯示圖片?
- 哪個使用者動作會導致變數改變?
DiceWithButtonAndImage()
可組合函式會將最新擲骰結果儲存在 result
變數中,定義該變數的是以下這行程式碼中的 remember
可組合函式和 mutableStateOf()
函式:
var result by remember { mutableStateOf(1) }
當 result
變數更新為新的值時,Compose 會觸發 DiceWithButtonAndImage()
可組合函式的重新組成作業,也就是說,系統會再次執行該可組合函式。每次重新組成時,系統都會記下 result
值,因此再次執行 DiceWithButtonAndImage()
可組合函式時會使用最新的 result
值。該可組合函式會針對 result
變數的值使用 when
陳述式,決定要顯示的新可繪製資源 ID,並讓 Image
可組合函式顯示該資源。
在 Lemonade 應用程式中運用所學
現在請針對 Lemonade 應用程式思考類似的問題:
- 有什麼變數可用來決定畫面上應顯示的文字和圖片嗎?請在程式碼中定義該變數。
- 您可以利用 Kotlin 條件式,讓應用程式根據變數的值做出不同行為嗎?如果可以,請在程式碼中編寫該條件陳述式。
- 哪個使用者動作會導致變數改變?請找出變更變數的適當程式碼位置,並於該處新增用來更新變數的程式碼。
這個部分可能不容易實作,而且您必須在程式碼的多處進行變更,才能讓程式碼正常運作。如果應用程式無法立即如預期運作,請別氣餒。別忘了,您可以採用多種方法正確實作這項行為。
完成後,請執行應用程式,確認應用程式是否正常運作。應用程式啟動時,應會顯示檸檬樹圖片和對應的文字標籤。輕觸一下檸檬樹圖片後,文字標籤應會更新,並顯示檸檬圖片。目前輕觸檸檬圖片應該不會有任何變化。
新增其餘步驟
現在應用程式可以顯示兩個檸檬汁製作步驟了!此時,LemonApp()
可組合函式可能會如以下程式碼片段所示。程式碼稍有差異也沒關係,只要應用程式中的行為相同即可。
MainActivity.kt
...
@Composable
fun LemonApp() {
// Current step the app is displaying (remember allows the state to be retained
// across recompositions).
var currentStep by remember { mutableStateOf(1) }
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
when (currentStep) {
1 -> {
Column (
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxSize()
){
Text(text = stringResource(R.string.lemon_select))
Spacer(modifier = Modifier.height(32.dp))
Image(
painter = painterResource(R.drawable.lemon_tree),
contentDescription = stringResource(R.string.lemon_tree_content_description),
modifier = Modifier
.wrapContentSize()
.clickable {
currentStep = 2
}
)
}
}
2 -> {
Column (
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxSize()
){
Text(text = stringResource(R.string.lemon_squeeze))
Spacer(modifier = Modifier.height(32
.dp))
Image(
painter = painterResource(R.drawable.lemon_squeeze),
contentDescription = stringResource(R.string.lemon_content_description),
modifier = Modifier.wrapContentSize()
)
}
}
}
}
}
...
接下來要新增其餘的檸檬汁製作步驟。使用者輕觸一下圖片後,應會進入下一個檸檬汁製作步驟,畫面上的文字和圖片也會更新。您需要變更程式碼,讓程式碼更能靈活處理應用程式的所有步驟,而不只是前兩個步驟。
如要讓點選圖片時產生不同的行為,您需要自訂可點選行為。更具體來說,點選圖片時執行的 lambda 必須知道要進入哪個步驟。
您可能開始注意到,每個檸檬汁製作步驟都有重複的應用程式程式碼。針對前一個程式碼片段中的 when
陳述式,情況 1
與情況 2
的程式碼非常相似,只有些微差異。您可以建立一個名為 LemonTextAndImage()
之類的新可組合函式,在 UI 中的圖片上方顯示文字 (如果這樣做有幫助的話)。建立可使用輸入參數的新可組合函式後,您就可以在多個情況下重複使用該函式,只需變更傳入的輸入內容即可。請自行找出應使用的輸入參數。建立這個可組合函式後,請更新現有程式碼,以在相關位置呼叫這個新函式。
使用 LemonTextAndImage()
等獨立可組合函式的另一個好處,是讓程式碼更有條理且完善。呼叫 LemonTextAndImage()
時,您可以確認文字和圖片都更新為新的值。如果不使用這個函式,很容易會不小心發生失誤,導致系統在更新文字標籤後顯示錯誤圖片。
再提供一項提示:您甚至可以將 lambda 函式傳入可組合函式。請務必使用函式類型標記,指定要傳入的函式類型。以下範例定義了 WelcomeScreen()
可組合函式,該函式可接受兩個輸入參數:name
字串和 () -> Unit
類型的 onStartClicked()
函式。這表示該函式不會接受輸入內容 (箭頭前方的空白括號),且沒有回傳值 (箭頭後方的 Unit
)。任何函式只要符合該函式類型 () -> Unit
,都可用來設定此 Button
的 onClick
處理常式。當使用者點選按鈕時,系統會呼叫 onStartClicked()
函式。
@Composable
fun WelcomeScreen(name: String, onStartClicked: () -> Unit) {
Column {
Text(text = "Welcome $name!")
Button(
onClick = onStartClicked
) {
Text("Start")
}
}
}
將 lambda 傳入可組合函式是實用的模式,因為這樣就能在不同情況下重複使用 WelcomeScreen()
。使用者名稱和按鈕的 onClick
行為是以引數的形式傳入,因此每次都可能有所不同。
瞭解這些額外知識後,請在應用程式程式碼中新增剩餘的檸檬汁製作步驟。
如要進一步瞭解如何針對隨機的擠檸檬次數新增自訂邏輯,請返回查看下方的操作說明。
新增擠檸檬邏輯
太棒了!您已建構出應用程式的雛形。使用者只要輕觸圖片,應該就能接續下一個步驟。接著要來新增相關行為,讓使用者必須擠檸檬多次來製作檸檬汁。所需的擠檸檬或輕觸次數應為介於 2 至 4 的隨機數字。每次使用者從樹上選取一顆新檸檬時,隨機產生的數字會有所不同。
以下提供幾個問題協助您進行思考:
- 如何在 Kotlin 中產生隨機數字?
- 應該在程式碼的哪個部分產生隨機數字?
- 如何確保使用者在輕觸檸檬的次數達到要求後,才進入下一個步驟?
- 需要透過
remember
可組合函式儲存變數,避免在每次重新繪製畫面時重設資料嗎?
實作這項變更後,請執行應用程式,確認需要輕觸檸檬多次才能進入下一個步驟,且每次要求的輕觸次數皆為介於 2 至 4 的隨機數字。如果輕觸檸檬圖片一次後即顯示一杯檸檬汁,請回到程式碼中找出遺漏的項目,然後再試一次。
如要進一步瞭解如何為應用程式進行最終調整,請回來查看下方的操作說明。
為應用程式進行最終調整
就快完成了!只要最後加一些細節,改善應用程式外觀即可。
提醒您,應用程式的最終外觀會如以下螢幕截圖所示:
- 將畫面中的文字和圖片垂直及水平置中。
- 將文字的字型大小設為
18sp
。 - 在文字和圖片之間加入
16dp
的空間。 - 為圖片加上
2dp
的細邊框,並帶有4dp
的些微圓角效果。邊框的 RGB 顏色值為紅色105
、綠色205
和藍色216
。如需加上邊框的操作說明範例,可以透過 Google 進行搜尋,或是參閱「邊框」說明文件。
完成上述變更後,請執行應用程式,並與最終螢幕截圖進行比較,確保兩者相符。
寫程式時,請養成在程式碼中新增註解的好習慣,讓查看您程式碼的人更輕鬆瞭解您的思考過程。請移除檔案頂端未用於程式碼的匯入陳述式,並確認程式碼符合 Kotlin 樣式指南中的規範。這些做法可讓其他人更容易理解您的程式碼,並讓程式碼更易於維護!
非常好!您已成功實作 Lemonade 應用程式!這個應用程式很有挑戰性,有很多地方需要理解。現在來喝一杯新鮮的檸檬汁,好好犒賞自己吧。乾杯!
6. 取得解決方案程式碼
下載解決方案程式碼:
或者,您也可以複製 GitHub 存放區的程式碼:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-lemonade.git
提醒您,實作這個應用程式的方法有很多種,因此您的程式碼不必與解決方案程式碼完全一致。
您也可以瀏覽 Lemonade 應用程式 GitHub 存放區中的程式碼。