建構 Compose UI

在 Compose 中,使用者介面無法變更,即後無法更新 繪製。您可以控制的是 UI 狀態,每當 UI 變更,Compose 會重新建立 已變更。可組合函式可接受 並公開事件,例如 TextField 會接受值並公開 回呼 onValueChange,要求回呼處理常式變更 值。

var name by remember { mutableStateOf("") }
OutlinedTextField(
    value = name,
    onValueChange = { name = it },
    label = { Text("Name") }
)

由於可組合函式接受狀態並公開事件,因此單向資料流 最適合搭配 Jetpack Compose 使用本指南著重介紹 Compose 的單向資料流模式、如何實作事件及 狀態容器,以及如何在 Compose 中使用 ViewModel。

單向資料流程

單向資料流 (UDF) 是狀態向下流動的設計模式 事件數量和事件都會增加只要跟隨單向資料流,您就能分離 這些可組合函式會在應用程式儲存的部分顯示狀態的 UI 狀態 以及變更狀態

使用單向資料流的應用程式 UI 更新迴圈如下所示:

  • 事件:部分 UI 產生事件並向上傳遞,例如 傳遞至 ViewModel 處理的按鈕點選動作;或者是 例如指出使用者工作階段 已過期。
  • 更新狀態:事件處理常式可能會變更狀態。
  • 顯示狀態:狀態容器向下傳遞狀態,然後在 UI 中顯示。 基礎架構

圖 1.單向資料流。

使用 Jetpack Compose 時遵循這個模式可提供幾個優點:

  • 可測試性:從使用者介面分離狀態,更容易 分別進行測試
  • 狀態封裝:由於狀態只能在一個位置更新, 可組合函式狀態只會有一個可靠資料來源, 您可能會因為狀態不一致而產生錯誤
  • UI 一致性: 使用可觀測的狀態容器,例如 StateFlowLiveData

Jetpack Compose 的單向資料流

基於狀態和事件的可組合函式運作。舉例來說,TextField 僅 當 value 參數更新且會顯示 onValueChange 時 回呼:要求將值變更為新事件的事件。撰寫 將 State 物件定義為值容器,並變更狀態值 才會觸發重組程序您可以將狀態保留在 remember { mutableStateOf(value) }rememberSaveable { mutableStateOf(value),取決於您需要的時間長度 請記住 值

TextField 可組合值的類型為 String,因此可以 從任何位置存取:透過硬式編碼值、從 ViewModel,或從 父項可組合函式。您不一定要將其保存在 State 物件中,但您需要 ,在呼叫 onValueChange 時更新值。

,瞭解如何調查及移除這項存取權。

定義可組合函式的參數

定義可組合函式的狀態參數時,應注意下列事項 幾個關鍵問題:

  • 可組合項的可重複使用或靈活性如何?
  • 狀態參數對這個可組合函式的效能有何影響?

為了鼓勵分離和重複使用,每個可組合項應盡可能減少 資訊例如,建構可組合元件時 新聞文章的標題,最好只傳入 而不是整篇新聞報導:

@Composable
fun Header(title: String, subtitle: String) {
    // Recomposes when title or subtitle have changed.
}

@Composable
fun Header(news: News) {
    // Recomposes when a new instance of News is passed in.
}

有時候,使用個別參數也可以提升成效,例如 News 包含多個資訊,不只是 titlesubtitle,時 新的 News 執行個體會傳遞至 Header(news),可組合函式會將 重組。titlesubtitle

請仔細考量您傳入的參數數量。讓函式具有 參數過多會降低函式的人體工學,因此在本例中 最好是將這些畫面組合成一個類別

Compose 中的事件

應用程式的所有輸入內容都應以事件表示:輕觸、文字變更、 甚至是計時器或其他最新動態當這些事件變更 UI 狀態時 ViewModel 應該是處理這些程式碼並更新 UI 狀態的那個。

UI 層一律不得變更事件處理常式以外的狀態,因為 可能會在應用程式中造成不一致和錯誤。

最好傳遞狀態和事件處理常式 lambda 的不可變值。這個 的好處如下:

  • 提高可重複使用性。
  • 請確保 UI 不會直接變更狀態值。
  • 避免並行問題,因為您可以確保狀態不會 已從另一個執行緒變動。
  • 通常,您可以降低程式碼的複雜度。

舉例來說,接受 String 和 lambda 做為參數的可組合項可以 非常容易重複使用假設熱門應用程式 則應用程式中一律顯示文字並提供返回按鈕。您可以定義 較通用的 MyAppTopAppBar 可組合函式,可接收文字和背面 按鈕控點做為參數:

@Composable
fun MyAppTopAppBar(topAppBarText: String, onBackPressed: () -> Unit) {
    TopAppBar(
        title = {
            Text(
                text = topAppBarText,
                textAlign = TextAlign.Center,
                modifier = Modifier
                    .fillMaxSize()
                    .wrapContentSize(Alignment.Center)
            )
        },
        navigationIcon = {
            IconButton(onClick = onBackPressed) {
                Icon(
                    Icons.Filled.ArrowBack,
                    contentDescription = localizedString
                )
            }
        },
        // ...
    )
}

範例:ViewModel、狀態和事件

使用 ViewModelmutableStateOf 也可以引入單向資料 流程。

  • 系統會透過可觀測的狀態容器 (例如 StateFlowLiveData) 公開 UI 狀態。
  • ViewModel 會處理來自 UI 或其他應用程式層的事件 並根據事件更新狀態容器。

舉例來說,如要實作登入畫面,請輕觸「登入」按鈕 ,應用程式應會顯示進度旋轉圖示和網路呼叫。如果 登入成功,然後您的應用程式會切換到其他畫面;在 應用程式顯示 Snackbar 錯誤。以下說明如何建立螢幕狀態的模型 以及事件:

畫面共有四種狀態:

  • 已登出:使用者尚未登入。
  • 進行中:應用程式目前嘗試讓使用者透過 以及執行網路呼叫
  • 錯誤:登入時發生錯誤。
  • 已登入:使用者已登入。

您可以模擬這些狀態,做為密封類別。ViewModel 會將狀態顯示為 State、設定初始狀態,並視需要更新狀態。 ViewModel 也會公開 onSignIn() 方法,處理登入事件。

class MyViewModel : ViewModel() {
    private val _uiState = mutableStateOf<UiState>(UiState.SignedOut)
    val uiState: State<UiState>
        get() = _uiState

    // ...
}

除了 mutableStateOf API 之外,Compose 也提供 適用於 LiveDataFlow 和 的擴充功能 Observable 註冊為事件監聽器並將值顯示為狀態。

class MyViewModel : ViewModel() {
    private val _uiState = MutableLiveData<UiState>(UiState.SignedOut)
    val uiState: LiveData<UiState>
        get() = _uiState

    // ...
}

@Composable
fun MyComposable(viewModel: MyViewModel) {
    val uiState = viewModel.uiState.observeAsState()
    // ...
}

瞭解詳情

如要進一步瞭解 Jetpack Compose 中的架構,請參閱下列資源:

範例