1. 事前準備
「Relay」這個工具包可讓團隊在 Figma 中設計 UI 元件,並直接在 Jetpack Compose 專案中使用。這個程式庫無需繁瑣的設計規格和品質確保週期,可協助團隊快速提供優質的 Android UI。
在本程式碼研究室中,您將瞭解如何將 Relay UI 套件整合至 Compose 開發程序。重點在於整合技術,而非端對端工作流程。如要瞭解 Relay 的一般工作流程,請參閱 Relay 基本教學課程。
必要條件
- 有 Compose 的基本經驗。如果您尚未完成,請完成「Jetpack Compose 基本概念」程式碼研究室。
- 具備 Kotlin 語法的經驗。
課程內容
- 如何匯入 UI 套件。
- 如何整合 UI 套件與導覽和資料架構。
- 如何使用控制器邏輯納入 UI 套件。
- 如何將 Figma 樣式對應至 Compose 主題。
- 如何在產生的程式碼中,將 UI 套件替換為現有可組合項。
建構項目
- 以設計人員提供的 Relay 套件為基礎,設計出逼真的應用程式設計。這款應用程式稱為 Reflect,這是每日追蹤應用程式,可促進正念和良好習慣。當中包含多種追蹤工具,以及用來新增及管理的 UI。這個應用程式的內容如下圖所示:
軟硬體需求
- Android Studio:Electric Eel 以上版本
- 免費的 Figma 帳戶及個人存取權杖
2. 做好準備
取得程式碼
如要取得本程式碼研究室的程式碼,請執行下列任一操作:
- 從 GitHub 複製
relay-codelabs
存放區:
$ git clone https://github.com/googlecodelabs/relay-codelabs
- 前往 GitHub 上的
relay-codelabs
存放區,選取所需分支版本,然後按一下「Code」>「Code」>「」下載 ZIP 檔案並將下載的 ZIP 檔案解壓縮。
無論是哪一種情況,main
分支版本都含有範例程式碼,end
分支版本則包含解決方案程式碼。
安裝 Relay for Android Studio 外掛程式
如果您尚未安裝 Relay for Android Studio 外掛程式,請按照下列步驟操作:
- 在 Android Studio 中,依序按一下「Settings」(設定) > 外掛程式。
- 在文字方塊中輸入
Relay for Android Studio
。 - 在搜尋結果顯示的擴充功能上,按一下「安裝」。
- 如果看到「第三方外掛程式隱私權注意事項」對話方塊,請按一下「接受」。
- 按一下 [確定] > 重新啟動。
- 如果看到「確認離開」對話方塊,請按一下「結束」。
將 Android Studio 連結至 Figma
Relay 使用 Figma API 擷取 UI 套件。如要使用,您需要擁有免費的 Figma 帳戶和個人存取權杖,因此請參閱「需求條件」一節。
如果您尚未將 Android Studio 連線至 Figma,請按照下列步驟操作:
- 在 Figma 帳戶中,按一下頁面頂端的個人資料圖示,然後選取「Settings」。
- 在「Personal access token」區段的文字方塊中輸入權杖的說明,然後按下
Enter
鍵 (macOS 為return
鍵)。系統會產生權杖。 - 按一下「Copy this token」。
- 在 Android Studio 中,選取工具>Relay 設定。畫面上會顯示「Relay 設定」對話方塊。
- 在「Figma Access Token」文字方塊中貼上存取權杖,然後按一下「OK」。環境已設定完畢。
3. 查看應用程式的設計
對於 Reflect 應用程式,我們與設計人員合作,協助我們定義應用程式的色彩、字體排版、版面配置和行為。我們建立這些設計時,會依照 Material Design 3 慣例,讓應用程式能與 Material Design 元件和主題完美搭配運作。
查看主畫面
主畫面會顯示使用者定義的追蹤器清單。它也提供變更活動日和建立其他追蹤器的預設用途。
在 Figma 中,我們的設計人員將此畫面分為多個元件、定義其 API,並使用 Relay for Figma 外掛程式封裝。這些元件封裝完成後,即可匯入 Android Studio 專案。
查看新增/編輯畫面
新增/編輯畫面可讓使用者新增或編輯追蹤器。顯示的表單因追蹤器類型而略有不同。
同樣地,這個畫面也可以分割成多個封裝元件。
查看主題
這項設計的顏色和字體排版是以 Material Design 3 符記名稱為基礎,實作為 Figma 樣式。這可以改善與 Compose 主題和 Material Design 元件的互通性。
4. 匯入 UI 套件
取得 Figma 來源的連結
您需要先將設計來源上傳至 Figma,才能將 UI 套件匯入專案。
如要取得 Figma 來源的連結,請按照下列步驟操作:
- 在 Figma 中,按一下「Import file」,然後選取
CompleteAppCodelab
專案資料夾中的ReflectDesign.fig
檔案。 - 在檔案上按一下滑鼠右鍵,然後選取「複製連結」。您將在下一節中用到。
將 UI 套件匯入專案
- 在 Android Studio 中開啟
./CompleteAppCodelab
專案。 - 按一下「檔案」>新增 >匯入 UI 套件。系統會隨即顯示「Import UI Packages」對話方塊。
- 在「Figma 來源網址」文字方塊中,貼上您在上一節複製的網址。
- 在「App theme」文字方塊中輸入
com.google.relay.example.reflect.ui.theme.ReflectTheme
。確保產生的預覽畫面使用自訂主題。 - 按一下「繼續」,您會看到檔案 UI 套件的預覽畫面。
- 按一下「建立」,套件會匯入您的專案。
- 前往「Project」分頁,然後按一下
ui-packages
資料夾旁邊的展開箭頭。
- 按一下其中一個套件資料夾旁邊的「展開」箭頭
,您會發現其中含有
JSON
來源檔案和素材資源依附元件。 - 開啟
JSON
來源檔案。Relay 模組會顯示套件及其 API 的預覽畫面。
建構並產生程式碼
- 在 Android Studio 頂端,按一下「Make project」圖示
。為每個套件產生的程式碼會加入
java/com.google.relay.example.reflect
資料夾。產生的可組合項包含來自 Figma 設計的所有版面配置和樣式資訊。 - 開啟
com/google/relay/example/reflect/range/Range.kt
檔案。 - 請注意,系統會為每個元件變化版本建立 Compose 預覽。如有需要,請按一下「Split」,讓程式碼和預覽窗格彼此相鄰。
5. 整合元件
在本節中,您將進一步瞭解為切換智慧手環產生的代碼。
- 在 Android Studio 中開啟
com/google/relay/example/reflect/switch/Switch.kt
檔案。
Switch.kt (已產生)
/**
* This composable was generated from the UI Package 'switch'.
* Generated code; don't edit directly.
*/
@Composable
fun Switch(
modifier: Modifier = Modifier,
isChecked: Boolean = false,
emoji: String = "",
title: String = ""
) {
TopLevel(modifier = modifier) {
if (isChecked) {
ActiveOverlay(modifier = Modifier.rowWeight(1.0f).columnWeight(1.0f)) {}
}
TopLevelSynth(modifier = Modifier.rowWeight(1.0f)) {
Label(modifier = Modifier.rowWeight(1.0f)) {
Emoji(emoji = emoji)
Title(
title = title,
modifier = Modifier.rowWeight(1.0f)
)
}
Checkmark {
if (isChecked) {
Icon()
}
}
}
}
}
- 請留意以下事項:
- 系統會產生 Figma 設計中的所有「版面配置」和「樣式」。
- 子元素會分為不同的可組合元件。
- 系統會為所有設計變化版本產生可組合項預覽。
- 顏色和字體排版樣式是硬式編碼。您之後就會修正這個問題。
插入追蹤器
- 在 Android Studio 中開啟
java/com/google/relay/example/reflect/ui/components/TrackerControl.kt
檔案。這個檔案為習慣追蹤器提供資料和互動邏輯。 - 在模擬器中建構並執行應用程式。這個元件目前會輸出追蹤器模型的原始資料。
- 將
com.google.relay.example.reflect.switch.Switch
套件匯入至檔案中。 - 將
Text(text = trackerData.tracker.toString())
替換為在trackerData.tracker.type
欄位上透視的when
區塊。 - 在
when
區塊的主體中,當類型為TrackerType.BOOLEAN
時,呼叫Switch()
Composable
函式。
您的程式碼應如下所示:
TrackerControl.kt
// TODO: replace with Relay tracker components
when (trackerData.tracker.type) {
TrackerType.BOOLEAN ->
Switch(
title = trackerData.tracker.name,
emoji = trackerData.tracker.emoji
)
else ->
Text(trackerData.tracker.toString())
}
- 重建專案。現在首頁會根據即時資料設計正確顯示切換追蹤器。
6. 新增狀態和互動
UI 套件為無狀態,顯示內容是傳入參數的簡單結果。但真正的應用程式需要互動和狀態。互動處理常式就像任何其他參數一樣,傳遞至產生的可組合項,但您會保留這些處理常式操控的狀態?您如何避免將相同的處理常式傳送至每個執行個體?如何將套件的組合擷取為可重複使用的可組合函式?在這種情況下,我們建議您將產生的套件納入自訂 Composable
函式中。
將 UI 套件納入控制器 Composable
函式中
只要將 UI 套件納入控制器 Composable
函式中,即可自訂呈現方式或商業邏輯,並視需要管理本機狀態。設計人員仍可在 Figma 中免費更新原始 UI 套件,不必更新包裝函式程式碼。
如要建立切換追蹤器的控制器,請按照下列步驟操作:
- 在 Android Studio 中開啟
java/com/google/relay/example/reflect/ui/components/SwitchControl.kt
檔案。 - 在
SwitchControl()
Composable
函式中,傳入下列參數:
trackerData
:TrackerData
物件modifier
:裝飾器物件onLongClick
:一種互動回呼,可啟用長按追蹤器以編輯和刪除
- 插入
Switch()
函式並傳入combinedClickable
修飾符,即可處理點擊和長按。 - 將
TrackerData
物件中的值傳遞至Switch()
函式,包括isToggled()
方法。
完成的 SwitchControl()
函式如下列程式碼片段所示:
SwitchControl.kt
package com.google.relay.example.reflect.ui.components
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.google.relay.example.reflect.model.Tracker
import com.google.relay.example.reflect.model.TrackerData
import com.google.relay.example.reflect.model.TrackerType
import com.google.relay.example.reflect.switch.Switch
/*
* A component for controlling switch-type trackers.
*
* SwitchControl is responsible for providing interaction and state management to the stateless
* composable [Switch] generated by Relay. [onLongClick] provides a way for callers to supplement
* the control's intrinsic interactions with, for example, a context menu.
*/
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun SwitchControl(
trackerData: TrackerData,
modifier: Modifier = Modifier,
onLongClick: (() -> Unit)? = null,
) {
Switch(
modifier
.clip(shape = RoundedCornerShape(size = 32.dp))
.combinedClickable(onLongClick = onLongClick) {
trackerData.toggle()
},
emoji = trackerData.tracker.emoji,
title = trackerData.tracker.name,
isChecked = trackerData.isToggled(),
)
}
@Preview
@Composable
fun SwitchControllerPreview() {
val data = TrackerData(
Tracker(
emoji = "🍕",
name = "Ate Pizza",
type = TrackerType.BOOLEAN
)
)
SwitchControl(data)
}
- 在
TrackerControl.kt
檔案中,移除Switch
匯入作業,然後將Switch()
函式替換為對SwitchControl()
函式的呼叫。 - 新增
TrackerType.RANGE
和TrackerType.COUNT
列舉常數的案例。
完成的 when
區塊如下列程式碼片段所示:
TrackerControl.kt
when (trackerData.tracker.type) {
TrackerType.BOOLEAN ->
SwitchControl(
trackerData = trackerData,
onLongClick = { expanded = true },
)
TrackerType.RANGE ->
RangeControl(
trackerData = trackerData,
onLongClick = { expanded = true },
)
TrackerType.COUNT ->
ValueControl(
trackerData = trackerData,
onLongClick = { expanded = true },
)
}
- 重建專案。您現在可以顯示追蹤器並進行互動。主畫面已完成設定。
7. 對應現有元件
Relay 可讓開發人員自訂產生的程式碼,將 UI 套件替換為現有可組合項。這是輸出立即可用的元件,或甚至自訂設計系統的好方法。
對應文字欄位
下圖為「新增/編輯追蹤程式」對話方塊中 Tracker Settings
元件的設計:
我們的設計人員在設計中使用了 ReflectTextField
,我們已在當中建構以 Material Design 3 文字欄位為基礎的程式碼。Figma 本身不支援文字欄位,因此 Relay 產生的預設程式碼只看起來像是設計。也不是功能控制項
如何測試 TrackerSettings
的目前實作:
- 在 Android Studio 中,在模擬器中建構並執行應用程式。
- 在追蹤器資料列上長按,然後選取「編輯」。
- 輕觸
Title
文字欄位,並請注意該欄位不會回應互動。
如要取代這個元素的實際實作方式,您需要有兩個項目:文字欄位 UI 套件和對應檔案。幸好我們的設計人員已將我們的設計系統元件封裝至 Figma,並在設計中針對 Tracker Settings
使用文字欄位元件。在預設情況下,系統會產生這個巢狀套件做為依附元件,但您可以使用元件對應來替換。
建立對應檔案
Relay for Android Studio 外掛程式提供建立元件對應檔的捷徑。
如要建立對應檔案,請按照下列步驟操作:
- 在 Android Studio 中的
text_field
UI 套件上按一下滑鼠右鍵,然後選取「產生對應檔案」。
- 系統會隨即顯示對應檔案對話方塊。輸入下列選項:
- 在「目標可組合項」中,選取「使用現有可組合項」並輸入
com.google.relay.example.reflect.ui.components.ReflectTextField
- 在「產生的檔案」中,勾選「產生實作項目」,然後取消勾選「產生 Compose 預覽」。
- 按一下「產生對應檔案」。隨即會產生下列對應檔案:
text_field.json
{
"target": "ReflectTextField",
"package": "com.google.relay.example.reflect.ui.components",
"generateImplementation": true,
"generatePreviews": false,
}
元件對應檔案會識別 Compose 類別目標和套件,以及選用的 fieldMapping
物件集合。這些欄位對應可讓您將套件參數轉換為預期的 Compose 參數。在這個範例中,API 相同,因此您只需指定目標類別。
- 重建專案。
- 在
trackersettings/
TrackerSettings.kt
檔案中,找到產生的TitleFieldStyleFilledStateEnabledTextConfigurationsInputText()
可組合函式,並注意其中含有產生的ReflectTextField
元件。
TrackerSettings.kt (已產生)
@Composable
fun TitleFieldStyleFilledStateEnabledTextConfigurationsInputText(
onTitleChanged: (String) -> Unit,
title: String,
modifier: Modifier = Modifier
) {
ReflectTextField(
onChange = onTitleChanged,
labelText = "Title",
leadingIcon = "search",
trailingIcon = "cancel",
supportingText = "Supporting text",
inputText = title,
state = State.Enabled,
textConfigurations = TextConfigurations.InputText,
modifier = modifier.fillMaxWidth(1.0f).requiredHeight(56.0.dp)
)
}
- 重建專案。您現在可以操作智慧手環設定欄位。編輯畫面已完成。
8. 對應至 Compose 主題
根據預設,Relay 會產生顏色和字體排版的常值。這可確保翻譯準確度,但會使元件無法使用 Compose 主題設定系統。以深色模式檢視應用程式時,這很明顯:
日導覽元件幾乎看不見,而且顏色不正確。如要修正這個問題,請使用 Relay 中的樣式對應功能,將 Figma 樣式連結至所產生程式碼中的 Compose 主題權杖。這樣可提升 Relay 和 Material Design 3 元件之間的視覺一致性,並且支援深色模式。
建立樣式對應檔案
- 在 Android Studio 中,前往
src/main/ui-package-resources
目錄,然後建立名為style-mappings
的新目錄。在該目錄中,建立包含下列程式碼的figma_styles.json
檔案:
figma_styles.json
{
"figma": {
"colors": {
"Reflect Light/background": "md.sys.color.background",
"Reflect Dark/background": "md.sys.color.background",
"Reflect Light/on-background": "md.sys.color.on-background",
"Reflect Dark/on-background": "md.sys.color.on-background",
"Reflect Light/surface": "md.sys.color.surface",
"Reflect Dark/surface": "md.sys.color.surface",
"Reflect Light/on-surface": "md.sys.color.on-surface",
"Reflect Dark/on-surface": "md.sys.color.on-surface",
"Reflect Light/surface-variant": "md.sys.color.surface-variant",
"Reflect Dark/surface-variant": "md.sys.color.surface-variant",
"Reflect Light/on-surface-variant": "md.sys.color.on-surface-variant",
"Reflect Dark/on-surface-variant": "md.sys.color.on-surface-variant",
"Reflect Light/primary": "md.sys.color.primary",
"Reflect Dark/primary": "md.sys.color.primary",
"Reflect Light/on-primary": "md.sys.color.on-primary",
"Reflect Dark/on-primary": "md.sys.color.on-primary",
"Reflect Light/primary-container": "md.sys.color.primary-container",
"Reflect Dark/primary-container": "md.sys.color.primary-container",
"Reflect Light/on-primary-container": "md.sys.color.on-primary-container",
"Reflect Dark/on-primary-container": "md.sys.color.on-primary-container",
"Reflect Light/secondary-container": "md.sys.color.secondary-container",
"Reflect Dark/secondary-container": "md.sys.color.secondary-container",
"Reflect Light/on-secondary-container": "md.sys.color.on-secondary-container",
"Reflect Dark/on-secondary-container": "md.sys.color.on-secondary-container",
"Reflect Light/outline": "md.sys.color.outline",
"Reflect Dark/outline": "md.sys.color.outline",
"Reflect Light/error": "md.sys.color.error",
"Reflect Dark/error": "md.sys.color.error"
},
"typography": {
"symbols": {
"Reflect/headline/large": "md.sys.typescale.headline-large",
"Reflect/headline/medium": "md.sys.typescale.headline-medium",
"Reflect/headline/small": "md.sys.typescale.headline-small",
"Reflect/title/large": "md.sys.typescale.title-large",
"Reflect/title/medium": "md.sys.typescale.title-medium",
"Reflect/title/small": "md.sys.typescale.title-small",
"Reflect/body/large": "md.sys.typescale.body-large",
"Reflect/body/medium": "md.sys.typescale.body-medium",
"Reflect/body/small": "md.sys.typescale.body-small",
"Reflect/label/large": "md.sys.typescale.label-large",
"Reflect/label/medium": "md.sys.typescale.label-medium",
"Reflect/label/small": "md.sys.typescale.label-small"
},
"subproperties": {
"fontFamily": "font",
"fontWeight": "weight",
"fontSize": "size",
"letterSpacing": "tracking",
"lineHeightPx": "line-height"
}
}
},
"compose": {
"colors": {
"md.sys.color.background": "MaterialTheme.colorScheme.background",
"md.sys.color.error": "MaterialTheme.colorScheme.error",
"md.sys.color.error-container": "MaterialTheme.colorScheme.errorContainer",
"md.sys.color.inverse-on-surface": "MaterialTheme.colorScheme.inverseOnSurface",
"md.sys.color.inverse-surface": "MaterialTheme.colorScheme.inverseSurface",
"md.sys.color.on-background": "MaterialTheme.colorScheme.onBackground",
"md.sys.color.on-error": "MaterialTheme.colorScheme.onError",
"md.sys.color.on-error-container": "MaterialTheme.colorScheme.onErrorContainer",
"md.sys.color.on-primary": "MaterialTheme.colorScheme.onPrimary",
"md.sys.color.on-primary-container": "MaterialTheme.colorScheme.onPrimaryContainer",
"md.sys.color.on-secondary": "MaterialTheme.colorScheme.onSecondary",
"md.sys.color.on-secondary-container": "MaterialTheme.colorScheme.onSecondaryContainer",
"md.sys.color.on-surface": "MaterialTheme.colorScheme.onSurface",
"md.sys.color.on-surface-variant": "MaterialTheme.colorScheme.onSurfaceVariant",
"md.sys.color.on-tertiary": "MaterialTheme.colorScheme.onTertiary",
"md.sys.color.on-tertiary-container": "MaterialTheme.colorScheme.onTertiaryContainer",
"md.sys.color.outline": "MaterialTheme.colorScheme.outline",
"md.sys.color.primary": "MaterialTheme.colorScheme.primary",
"md.sys.color.primary-container": "MaterialTheme.colorScheme.primaryContainer",
"md.sys.color.secondary": "MaterialTheme.colorScheme.secondary",
"md.sys.color.secondary-container": "MaterialTheme.colorScheme.secondaryContainer",
"md.sys.color.surface": "MaterialTheme.colorScheme.surface",
"md.sys.color.surface-variant": "MaterialTheme.colorScheme.surfaceVariant",
"md.sys.color.tertiary": "MaterialTheme.colorScheme.tertiary",
"md.sys.color.tertiary-container": "MaterialTheme.colorScheme.tertiaryContainer"
},
"typography": {
"symbols": {
"md.sys.typescale.display-large": "MaterialTheme.typography.displayLarge",
"md.sys.typescale.display-medium": "MaterialTheme.typography.displayMedium",
"md.sys.typescale.display-small": "MaterialTheme.typography.displaySmall",
"md.sys.typescale.headline-large": "MaterialTheme.typography.headlineLarge",
"md.sys.typescale.headline-medium": "MaterialTheme.typography.headlineMedium",
"md.sys.typescale.headline-small": "MaterialTheme.typography.headlineSmall",
"md.sys.typescale.title-large": "MaterialTheme.typography.titleLarge",
"md.sys.typescale.title-medium": "MaterialTheme.typography.titleMedium",
"md.sys.typescale.title-small": "MaterialTheme.typography.titleSmall",
"md.sys.typescale.body-large": "MaterialTheme.typography.bodyLarge",
"md.sys.typescale.body-medium": "MaterialTheme.typography.bodyMedium",
"md.sys.typescale.body-small": "MaterialTheme.typography.bodySmall",
"md.sys.typescale.label-large": "MaterialTheme.typography.labelLarge",
"md.sys.typescale.label-medium": "MaterialTheme.typography.labelMedium",
"md.sys.typescale.label-small": "MaterialTheme.typography.labelSmall"
},
"subproperties": {
"font": "fontFamily",
"weight": "fontWeight",
"size": "fontSize",
"tracking": "letterSpacing",
"line-height": "lineHeight"
}
},
"options": {
"packages": {
"MaterialTheme": "androidx.compose.material3"
}
}
}
}
主題對應檔結構包含兩個頂層物件:figma
和 compose
。在這些物件中,顏色和類型定義是透過中繼符記連結兩種環境。這樣就能將多個 Figma 樣式對應至單一 Compose 主題項目,這在支援淺色和深色主題時相當實用。
- 查看對應檔案,特別是如何將 Figma 的字體排版屬性重新對應至 Compose 的預期。
重新匯入 UI 套件
建立對應檔案後,您需要將所有 UI 套件重新匯入專案中,因為未提供對應檔案,因此在初次匯入時,系統會捨棄所有 Figma 樣式值。
如要重新匯入 UI 套件,請按照下列步驟操作:
- 在 Android Studio 中,依序按一下「File」(檔案) >「新增 >匯入 UI 套件。系統會隨即顯示「Import UI Packages」對話方塊。
- 在「Figma 來源網址」文字方塊中,輸入 Figma 來源檔案的網址。
- 選取「Translate Figma style to Compose theme」核取方塊。
- 選取「匯入自訂設定」。按一下資料夾圖示,然後選取你剛才建立的檔案:
src/main/ui-package-resources/style-mappings/figma_styles.json
。 - 按一下「繼續」,您會看到檔案 UI 套件的預覽畫面。
- 按一下「建立」,套件會匯入您的專案。
- 重新建立專案,然後開啟
switch/Switch.kt
檔案來查看產生的程式碼。
Switch.kt (已產生)
@Composable
fun ActiveOverlay(
modifier: Modifier = Modifier,
content: @Composable RelayContainerScope.() -> Unit
) {
RelayContainer(
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
isStructured = false,
radius = 32.0,
content = content,
modifier = modifier.fillMaxWidth(1.0f).fillMaxHeight(1.0f)
)
}
- 留意在 Compose 主題物件中,
backgroundColor
參數如何設為MaterialTheme.colorScheme.surfaceVariant
欄位。 - 執行專案,並在模擬器中切換為深色模式。系統已正確套用主題並修正視覺錯誤。
9. 恭喜
恭喜!您已瞭解如何將 Relay 整合至 Compose 應用程式中!