Jetpack Compose 架构分层

此页面提供 Jetpack Compose 构成的架构层的概述以及指导此设计的核心原则。

Jetpack Compose 不是单个的单体项目;它由多个模块组成,这些模块组合在一起形成一个完整的堆栈。了解构成 Jetpack Compose 的不同模块,使您能够

  • 使用适当的抽象级别构建您的应用或库
  • 了解何时可以“下降”到更低级别以获得更多控制或自定义
  • 最小化您的依赖项

Jetpack Compose 的主要层是

图 1. Jetpack Compose 的主要层。

每个层都建立在较低级别之上,结合功能以创建更高级别的组件。每个层都建立在较低层的公共 API 之上,以验证模块边界并使您能够在需要时替换任何层。让我们从下往上检查这些层。

运行时
此模块提供了 Compose 运行时的基础知识,例如 remembermutableStateOf@Composable 注解和 SideEffect。如果您只需要 Compose 的树管理功能,而不是其 UI,则可以考虑直接在此层上构建。
UI
UI 层由多个模块组成(ui-textui-graphicsui-tooling 等)。这些模块实现了 UI 工具包的基础知识,例如 LayoutNodeModifier、输入处理程序、自定义布局和绘图。如果您只需要 UI 工具包的基本概念,则可以考虑在此层上构建。
基础
此模块为 Compose UI 提供了与设计系统无关的构建块,例如 RowColumnLazyColumn、特定手势的识别等。您可以考虑在此基础层上构建以创建自己的设计系统。
材质
此模块为 Compose UI 提供了 Material Design 系统的实现,提供了主题系统、样式化组件、波纹指示、图标。在您的应用中使用 Material Design 时,请在此层上构建。

设计原则

Jetpack Compose 的指导原则之一是提供可以组合(或组合)在一起的小型、专注的功能块,而不是几个大型的单体组件。这种方法有很多优点。

控制

更高级别的组件往往为您做更多的事情,但限制了您可以直接控制的量。如果您需要更多控制,您可以“下降”以使用更低级别的组件。

例如,如果要为组件的颜色设置动画,可以使用 animateColorAsState API

val color = animateColorAsState(if (condition) Color.Green else Color.Red)

但是,如果您需要组件始终以灰色开始,则无法使用此 API。相反,您可以下降以使用更低级别的 Animatable API

val color = remember { Animatable(Color.Gray) }
LaunchedEffect(condition) {
    color.animateTo(if (condition) Color.Green else Color.Red)
}

更高级别的 animateColorAsState API 本身就是建立在更低级别的 Animatable API 之上的。使用较低级别的 API 更加复杂,但提供了更多控制。选择最适合您需求的抽象级别。

自定义

从较小的构建块组装更高级别的组件,使得在需要时自定义组件变得更加容易。例如,考虑材质层提供的 实现 Button

@Composable
fun Button(
    // …
    content: @Composable RowScope.() -> Unit
) {
    Surface(/* … */) {
        CompositionLocalProvider(/* … */) { // set LocalContentAlpha
            ProvideTextStyle(MaterialTheme.typography.button) {
                Row(
                    // …
                    content = content
                )
            }
        }
    }
}

一个 Button 由 4 个组件组成

  1. 一个材质 Surface 提供背景、形状、点击处理等。

  2. 一个 CompositionLocalProvider 在按钮启用或禁用时更改内容的 alpha 值。

  3. 一个 ProvideTextStyle 设置要使用的默认文本样式。

  4. 一个 Row 为按钮的内容提供默认布局策略。

我们省略了一些参数和注释以使结构更清晰,但整个组件只有大约 40 行代码,因为它只是将这 4 个组件组合在一起以实现按钮。像 Button 这样的组件会对它们公开的参数有自己的看法,在启用常见自定义与参数激增之间取得平衡,参数激增会使组件更难使用。例如,Material 组件提供了 Material Design 系统中指定的自定义,从而可以轻松遵循 Material Design 原则。

但是,如果您希望进行超出组件参数的自定义,则可以“下降”一个级别并派生一个组件。例如,Material Design 指定按钮应具有纯色背景。如果您需要渐变背景,则 Button 参数不支持此选项。在这种情况下,您可以使用 Material Button 实现作为参考并构建自己的组件

@Composable
fun GradientButton(
    // …
    background: List<Color>,
    modifier: Modifier = Modifier,
    content: @Composable RowScope.() -> Unit
) {
    Row(
        // …
        modifier = modifier
            .clickable(onClick = {})
            .background(
                Brush.horizontalGradient(background)
            )
    ) {
        CompositionLocalProvider(/* … */) { // set material LocalContentAlpha
            ProvideTextStyle(MaterialTheme.typography.button) {
                content()
            }
        }
    }
}

上述实现继续使用 Material 层的组件,例如 Material 的 当前内容 alpha 和当前文本样式的概念。但是,它用 Row 替换了材质 Surface 并对其进行样式设置以达到所需的显示效果。

如果您根本不想使用 Material 概念,例如如果您正在构建自己的定制设计系统,则可以下降到仅使用基础层组件

@Composable
fun BespokeButton(
    // …
    backgroundColor: Color,
    modifier: Modifier = Modifier,
    content: @Composable RowScope.() -> Unit
) {
    Row(
        // …
        modifier = modifier
            .clickable(onClick = {})
            .background(backgroundColor)
    ) {
        // No Material components used
        content()
    }
}

Jetpack Compose 为最高级别的组件保留最简单的名称。例如,androidx.compose.material.Text 建立在 androidx.compose.foundation.text.BasicText 之上。如果您希望替换更高级别,这使得您可以提供自己的实现并使用最容易发现的名称。

选择正确的抽象

Compose 的构建分层、可重用组件的理念意味着您不应始终使用较低级别的构建块。许多更高级别的组件不仅提供更多功能,而且通常实现最佳实践,例如支持辅助功能。

例如,如果要向自定义组件添加手势支持,您可以使用 Modifier.pointerInput 从头开始构建,但还有其他建立在此基础之上的更高级别的组件可能提供更好的起点,例如 Modifier.draggableModifier.scrollableModifier.swipeable

通常,最好在提供您所需功能的最高级别组件上构建,以便从它们包含的最佳实践中获益。

了解更多

有关构建自定义设计系统的示例,请参阅 Jetsnack 示例