做自(zì)由與創造的先行者

Android界面事(shì)件

Android開(kāi)發手冊

“界面事(shì)件”是指應由界面或 ViewModel 在界面層處理(lǐ)的操作(zuò)。最常見的事(shì)件類型是“用(yòng)戶事(shì)件”。用(yòng)戶通過與應用(yòng)互動(例如,點按屏幕或生成手勢)來(lái)生成用(yòng)戶事(shì)件。随後,界面會(huì)使用(yòng) onClick() 監聽器等回調來(lái)使用(yòng)這(zhè)些(xiē)事(shì)件。

關鍵術語:

界面:用(yòng)于處理(lǐ)界面的基于視(shì)圖的代碼或 Compose 代碼。

界面事(shì)件:應在界面層處理(lǐ)的操作(zuò)。

用(yòng)戶事(shì)件:用(yòng)戶在與應用(yòng)互動時(shí)生成的事(shì)件。

ViewModel 通常負責處理(lǐ)特定用(yòng)戶事(shì)件的業務邏輯。例如,用(yòng)戶點擊某個按鈕以刷新部分數據。ViewModel 通常通過公開(kāi)界面可以調用(yòng)的函數來(lái)處理(lǐ)這(zhè)種情況。用(yòng)戶事(shì)件可能(néng)還有界面可以直接處理(lǐ)的界面行爲邏輯。例如轉到(dào)其他(tā)屏幕或顯示 Snackbar。

雖然同一應用(yòng)的業務邏輯在不同移動平台或設備類型上(shàng)保持不變,但(dàn)界面行爲邏輯在實現(xiàn)方面可能(néng)有所不同。界面層頁定義了(le)這(zhè)些(xiē)類型的邏輯,如下(xià)所示:

業務邏輯是指如何處理(lǐ)狀态更改,例如付款或存儲用(yòng)戶偏好(hǎo)設置。網域和(hé)數據層通常負責處理(lǐ)此邏輯。在本指南中,架構組件 ViewModel 類用(yòng)作(zuò)處理(lǐ)業務邏輯的類的特色解決方案。

界面行爲邏輯(即界面邏輯)是指如何顯示狀态更改,例如導航邏輯或如何向用(yòng)戶顯示消息。界面會(huì)處理(lǐ)此邏輯。

注意:本頁中提供的建議(yì)和(hé)最佳實踐可應用(yòng)于廣泛的應用(yòng)。遵循這(zhè)些(xiē)建議(yì)和(hé)最佳實踐可以提升應用(yòng)的可擴展性、質量和(hé)穩健性,并使應用(yòng)更易于測試。不過,您應該将這(zhè)些(xiē)提示視(shì)爲指南,并視(shì)需要進行調整來(lái)滿足您的要求。

界面事(shì)件決策樹

下(xià)圖這(zhè)個決策樹展示了(le)如何尋找處理(lǐ)特定事(shì)件使用(yòng)場景的最佳實踐。本指南的其餘部分将詳細介紹這(zhè)些(xiē)方法。

處理(lǐ)用(yòng)戶事(shì)件

如果用(yòng)戶事(shì)件與修改界面元素的狀态(如可展開(kāi)項的狀态)相關,界面便可以直接處理(lǐ)這(zhè)些(xiē)事(shì)件。如果事(shì)件需要執行業務邏輯(如刷新屏幕上(shàng)的數據),則應用(yòng)由 ViewModel 處理(lǐ)此事(shì)件。

RecyclerView 中的用(yòng)戶事(shì)件

如果操作(zuò)是在界面樹中比較靠下(xià)一層生成的,例如在 RecyclerView 項或自(zì)定義 View 中,ViewModel 應仍是處理(lǐ)用(yòng)戶事(shì)件的操作(zuò)。

例如,假設 NewsActivity 中的所有新聞項都包含一個書簽按鈕。ViewModel 需要知(zhī)道(dào)已添加書簽的新聞項目的 ID。當用(yòng)戶爲新聞内容添加書簽時(shí),RecyclerView 适配器不會(huì)調用(yòng) ViewModel 中已公開(kāi)的 addBookmark(newsId) 函數,該函數需要一個對(duì) ViewModel 的依賴項。取而代之的是,ViewModel 會(huì)公開(kāi)一個名爲 NewsItemUiState 的狀态對(duì)象,其中包含用(yòng)于處理(lǐ)事(shì)件的實現(xiàn):

data class NewsItemUiState(

val title: String,

val body: String,

val bookmarked: Boolean = false,

val publicationDate: String,

val onBookmark: () -> Unit

)

class LatestNewsViewModel(

private val formatDateUseCase: FormatDateUseCase,

private val repository: NewsRepository

)

val newsListUiItems = repository.latestNews.map { news ->

NewsItemUiState(

title = news.title,

body = news.body,

bookmarked = news.bookmarked,

publicationDate = formatDateUseCase(news.publicationDate),

// Business logic is passed as a lambda function that the

// UI calls on click events.

onBookmark = {

repository.addBookmark(news.id)

}

)

}

}

這(zhè)樣,RecyclerView 适配器就會(huì)僅使用(yòng)它需要的數據:NewsItemUiState 對(duì)象列表。該适配器無法訪問整個 ViewModel,因此不太可能(néng)濫用(yòng) ViewModel 公開(kāi)的功能(néng)。如果僅允許 activity 類使用(yòng) ViewModel,即表示職責已分開(kāi)。這(zhè)樣可确保界面專屬對(duì)象(如視(shì)圖或 RecyclerView 适配器)不會(huì)直接與 ViewModel 互動。

警告:将 ViewModel 傳入 RecyclerView 适配器是一種不妥的做法,因爲它會(huì)将适配器與 ViewModel 類緊密耦合。

注意:另一種常見方案是讓 RecyclerView 适配器具有用(yòng)于用(yòng)戶操作(zuò)的 Callback 接口。在這(zhè)種情況下(xià),activity 或 fragment 可以處理(lǐ)綁定,并直接從(cóng)回調接口調用(yòng) ViewModel 函數。

用(yòng)戶事(shì)件函數的命名慣例

在本指南中,用(yòng)于處理(lǐ)用(yòng)戶事(shì)件的 ViewModel 函數根據其處理(lǐ)的操作(zuò)(例如,addBookmark(id) 或 logIn(username, password))以動詞命名。

處理(lǐ) ViewModel 事(shì)件

源自(zì) ViewModel 的界面操作(zuò)(ViewModel 事(shì)件)應始終引發界面狀态更新。這(zhè)符合單向數據流的原則。讓事(shì)件在配置更改後可重現(xiàn),并保證界面操作(zuò)不會(huì)丢失。如果您使用(yòng)已保存的狀态模塊,則還可以讓事(shì)件在進程終止後可重現(xiàn)(可選操作(zuò))。

将界面操作(zuò)映射到(dào)界面狀态并不總是一個簡單的過程,但(dàn)确實可以簡化邏輯。例如,您不單單要想辦法确定如何将界面導航到(dào)特定屏幕,還需要進一步思考如何在界面狀态中表示該用(yòng)戶流。換句話(huà)說:不需要考慮界面需要執行哪些(xiē)操作(zuò),而是要思考這(zhè)些(xiē)操作(zuò)會(huì)對(duì)界面狀态造成什(shén)麽影響。

要點:ViewModel 事(shì)件應始終會(huì)引發界面狀态更新。

例如,要考慮在用(yòng)戶登錄時(shí)從(cóng)登錄屏幕切換到(dào)主屏幕的情況。您可以在界面狀态中進行如下(xià)建模:

data class LoginUiState(

val isLoading: Boolean = false,

val errorMessage: String? = null,

val isUserLoggedIn: Boolean = false

)

使用(yòng)事(shì)件可能(néng)會(huì)觸發狀态更新

使用(yòng)界面中的某些(xiē) ViewModel 事(shì)件可能(néng)會(huì)引發其他(tā)界面狀态更新。例如,當屏幕上(shàng)顯示瞬時(shí)消息以告知(zhī)用(yòng)戶發生的情況時(shí),界面需要通知(zhī) ViewModel 以在消息顯示于屏幕上(shàng)時(shí)觸發另一狀态更新。用(yòng)戶處理(lǐ)消息(通過關閉消息或超時(shí))後發生的事(shì)件可被視(shì)爲“用(yòng)戶輸入”,因此 ViewModel 應該知(zhī)道(dào)這(zhè)一點。在這(zhè)種情況下(xià),界面狀态可按以下(xià)方式建模:

// Models the UI state for the Latest news screen.

data class LatestNewsUiState(

val news: List = emptyList(),

val isLoading: Boolean = false,

val userMessage: String? = null

)

導航事(shì)件

使用(yòng)事(shì)件可能(néng)會(huì)觸發狀态更新部分詳細介紹了(le)如何使用(yòng)界面狀态在屏幕上(shàng)顯示用(yòng)戶消息。導航事(shì)件也(yě)是 Android 應用(yòng)中的一種常見事(shì)件類型。

如果因用(yòng)戶點按某個按鈕而在界面中觸發了(le)該事(shì)件,界面便會(huì)通過以下(xià)方式處理(lǐ)該事(shì)件:調用(yòng)導航控制器,或将該事(shì)件公開(kāi)給調用(yòng)方可組合項。

目的地保留在返回堆棧中時(shí)的導航事(shì)件

如果 ViewModel 設置了(le)某種狀态,使其生成從(cóng)屏幕 A 到(dào)屏幕 B 的導航事(shì)件,并且屏幕 A 保留在導航返回堆棧中,您可能(néng)需要其他(tā)邏輯,以免繼續自(zì)動進入屏幕 B。爲實現(xiàn)這(zhè)一點,您必須設置其他(tā)狀态,以指示界面是否應該考慮前往其他(tā)屏幕。通常,該狀态會(huì)保留在界面中,因爲導航邏輯與界面有關,而與 ViewModel 無關。爲了(le)說明(míng)這(zhè)一點,我們來(lái)看(kàn)以下(xià)用(yòng)例。

假設您已進入應用(yòng)的注冊流程。在“出生日期”驗證屏幕中,如果用(yòng)戶輸入某個日期,當用(yòng)戶點按“繼續”按鈕時(shí),ViewModel 會(huì)驗證該日期。ViewModel 會(huì)将相應驗證邏輯委托給數據層。如果日期有效,用(yòng)戶會(huì)進入下(xià)一個屏幕。作(zuò)爲一項額外(wài)功能(néng),用(yòng)戶可以在不同的注冊屏幕之間來(lái)回切換,以便在想要更改某些(xiē)數據時(shí)能(néng)夠進行所需的操作(zuò)。因此,注冊流程中的所有目的地都保留在同一個返回堆棧中。

其他(tā)用(yòng)例

如果您認爲界面事(shì)件用(yòng)例無法通過界面狀态更新得以解決,可能(néng)需要重新考慮數據在應用(yòng)中的流動方式。請(qǐng)考慮以下(xià)原則:

每個類都應各司其職,不能(néng)越界。界面負責屏幕專屬行爲邏輯,例如導航調用(yòng)、點擊事(shì)件以及獲取權限請(qǐng)求。ViewModel 包含業務邏輯,并将結果從(cóng)層次結構的較低(dī)層轉換爲界面狀态。

考慮事(shì)件的發起點。請(qǐng)遵循本指南開(kāi)頭介紹的決策樹,并讓每個類各司其職。例如,如果事(shì)件源自(zì)界面并導緻出現(xiàn)導航事(shì)件,則必須在界面中處理(lǐ)該事(shì)件。某些(xiē)邏輯可能(néng)會(huì)委托給 ViewModel,但(dàn)事(shì)件的處理(lǐ)無法完全委托給 ViewModel。

如果事(shì)件有多個使用(yòng)方,則當您對(duì)某個事(shì)件會(huì)被使用(yòng)多次而感到(dào)擔憂時(shí),可能(néng)需要重新考慮您的應用(yòng)架構。 同時(shí)有多個使用(yòng)方會(huì)導緻“恰好(hǎo)交付一次”協定變得非常難以保證,因此複雜(zá)性和(hé)細微行爲的數量也(yě)會(huì)急劇(jù)增加。如果您遇到(dào)此問題,不妨考慮提升這(zhè)些(xiē)問題在界面樹上(shàng)的層級;您可能(néng)需要在層次結構中較高(gāo)層級設定其他(tā)實體。

考慮何時(shí)需要使用(yòng)狀态。在某些(xiē)情況下(xià),您可能(néng)不想在應用(yòng)處于後台時(shí)保留使用(yòng)狀态(例如顯示 Toast)。在這(zhè)些(xiē)情況下(xià),請(qǐng)考慮在界面位于前台時(shí)使用(yòng)狀态。

網站(zhàn)建設開(kāi)發|APP設計(jì)開(kāi)發|小(xiǎo)程序建設開(kāi)發
下(xià)一篇:Android狀态容器和(hé)界面狀态
上(shàng)一篇:Android界面層