做自由与创造的先行者

Android界面事件

Android开发手册

“界面事件”是指应由界面或 ViewModel 在界面层处理的操作。最常见的事件类型是“用户事件”。用户通过与应用互动(例如,点按屏幕或生成手势)来生成用户事件。随后,界面会使用 onClick() 监听器等回调来使用这些事件。

关键术语:

界面:用于处理界面的基于视图的代码或 Compose 代码。

界面事件:应在界面层处理的操作。

用户事件:用户在与应用互动时生成的事件。

ViewModel 通常负责处理特定用户事件的业务逻辑。例如,用户点击某个按钮以刷新部分数据。ViewModel 通常通过公开界面可以调用的函数来处理这种情况。用户事件可能还有界面可以直接处理的界面行为逻辑。例如转到其他屏幕或显示 Snackbar。

虽然同一应用的业务逻辑在不同移动平台或设备类型上保持不变,但界面行为逻辑在实现方面可能有所不同。界面层页定义了这些类型的逻辑,如下所示:

业务逻辑是指如何处理状态更改,例如付款或存储用户偏好设置。网域和数据层通常负责处理此逻辑。在本指南中,架构组件 ViewModel 类用作处理业务逻辑的类的特色解决方案。

界面行为逻辑(即界面逻辑)是指如何显示状态更改,例如导航逻辑或如何向用户显示消息。界面会处理此逻辑。

注意:本页中提供的建议和最佳实践可应用于广泛的应用。遵循这些建议和最佳实践可以提升应用的可扩展性、质量和稳健性,并使应用更易于测试。不过,您应该将这些提示视为指南,并视需要进行调整来满足您的要求。

界面事件决策树

下图这个决策树展示了如何寻找处理特定事件使用场景的最佳实践。本指南的其余部分将详细介绍这些方法。

处理用户事件

如果用户事件与修改界面元素的状态(如可展开项的状态)相关,界面便可以直接处理这些事件。如果事件需要执行业务逻辑(如刷新屏幕上的数据),则应用由 ViewModel 处理此事件。

RecyclerView 中的用户事件

如果操作是在界面树中比较靠下一层生成的,例如在 RecyclerView 项或自定义 View 中,ViewModel 应仍是处理用户事件的操作。

例如,假设 NewsActivity 中的所有新闻项都包含一个书签按钮。ViewModel 需要知道已添加书签的新闻项目的 ID。当用户为新闻内容添加书签时,RecyclerView 适配器不会调用 ViewModel 中已公开的 addBookmark(newsId) 函数,该函数需要一个对 ViewModel 的依赖项。取而代之的是,ViewModel 会公开一个名为 NewsItemUiState 的状态对象,其中包含用于处理事件的实现:

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)

}

)

}

}

这样,RecyclerView 适配器就会仅使用它需要的数据:NewsItemUiState 对象列表。该适配器无法访问整个 ViewModel,因此不太可能滥用 ViewModel 公开的功能。如果仅允许 activity 类使用 ViewModel,即表示职责已分开。这样可确保界面专属对象(如视图或 RecyclerView 适配器)不会直接与 ViewModel 互动。

警告:将 ViewModel 传入 RecyclerView 适配器是一种不妥的做法,因为它会将适配器与 ViewModel 类紧密耦合。

注意:另一种常见方案是让 RecyclerView 适配器具有用于用户操作的 Callback 接口。在这种情况下,activity 或 fragment 可以处理绑定,并直接从回调接口调用 ViewModel 函数。

用户事件函数的命名惯例

在本指南中,用于处理用户事件的 ViewModel 函数根据其处理的操作(例如,addBookmark(id) 或 logIn(username, password))以动词命名。

处理 ViewModel 事件

源自 ViewModel 的界面操作(ViewModel 事件)应始终引发界面状态更新。这符合单向数据流的原则。让事件在配置更改后可重现,并保证界面操作不会丢失。如果您使用已保存的状态模块,则还可以让事件在进程终止后可重现(可选操作)。

将界面操作映射到界面状态并不总是一个简单的过程,但确实可以简化逻辑。例如,您不单单要想办法确定如何将界面导航到特定屏幕,还需要进一步思考如何在界面状态中表示该用户流。换句话说:不需要考虑界面需要执行哪些操作,而是要思考这些操作会对界面状态造成什么影响。

要点:ViewModel 事件应始终会引发界面状态更新。

例如,要考虑在用户登录时从登录屏幕切换到主屏幕的情况。您可以在界面状态中进行如下建模:

data class LoginUiState(

val isLoading: Boolean = false,

val errorMessage: String? = null,

val isUserLoggedIn: Boolean = false

)

使用事件可能会触发状态更新

使用界面中的某些 ViewModel 事件可能会引发其他界面状态更新。例如,当屏幕上显示瞬时消息以告知用户发生的情况时,界面需要通知 ViewModel 以在消息显示于屏幕上时触发另一状态更新。用户处理消息(通过关闭消息或超时)后发生的事件可被视为“用户输入”,因此 ViewModel 应该知道这一点。在这种情况下,界面状态可按以下方式建模:

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

data class LatestNewsUiState(

val news: List = emptyList(),

val isLoading: Boolean = false,

val userMessage: String? = null

)

导航事件

使用事件可能会触发状态更新部分详细介绍了如何使用界面状态在屏幕上显示用户消息。导航事件也是 Android 应用中的一种常见事件类型。

如果因用户点按某个按钮而在界面中触发了该事件,界面便会通过以下方式处理该事件:调用导航控制器,或将该事件公开给调用方可组合项。

目的地保留在返回堆栈中时的导航事件

如果 ViewModel 设置了某种状态,使其生成从屏幕 A 到屏幕 B 的导航事件,并且屏幕 A 保留在导航返回堆栈中,您可能需要其他逻辑,以免继续自动进入屏幕 B。为实现这一点,您必须设置其他状态,以指示界面是否应该考虑前往其他屏幕。通常,该状态会保留在界面中,因为导航逻辑与界面有关,而与 ViewModel 无关。为了说明这一点,我们来看以下用例。

假设您已进入应用的注册流程。在“出生日期”验证屏幕中,如果用户输入某个日期,当用户点按“继续”按钮时,ViewModel 会验证该日期。ViewModel 会将相应验证逻辑委托给数据层。如果日期有效,用户会进入下一个屏幕。作为一项额外功能,用户可以在不同的注册屏幕之间来回切换,以便在想要更改某些数据时能够进行所需的操作。因此,注册流程中的所有目的地都保留在同一个返回堆栈中。

其他用例

如果您认为界面事件用例无法通过界面状态更新得以解决,可能需要重新考虑数据在应用中的流动方式。请考虑以下原则:

每个类都应各司其职,不能越界。界面负责屏幕专属行为逻辑,例如导航调用、点击事件以及获取权限请求。ViewModel 包含业务逻辑,并将结果从层次结构的较低层转换为界面状态。

考虑事件的发起点。请遵循本指南开头介绍的决策树,并让每个类各司其职。例如,如果事件源自界面并导致出现导航事件,则必须在界面中处理该事件。某些逻辑可能会委托给 ViewModel,但事件的处理无法完全委托给 ViewModel。

如果事件有多个使用方,则当您对某个事件会被使用多次而感到担忧时,可能需要重新考虑您的应用架构。 同时有多个使用方会导致“恰好交付一次”协定变得非常难以保证,因此复杂性和细微行为的数量也会急剧增加。如果您遇到此问题,不妨考虑提升这些问题在界面树上的层级;您可能需要在层次结构中较高层级设定其他实体。

考虑何时需要使用状态。在某些情况下,您可能不想在应用处于后台时保留使用状态(例如显示 Toast)。在这些情况下,请考虑在界面位于前台时使用状态。

网站建设开发|APP设计开发|小程序建设开发
下一篇:Android状态容器和界面状态
上一篇:Android界面层