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、特定手势的识别等。如果您需要创建自己的设计系统,则可以考虑在此层上构建。
Material
此模块为 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 由 Material 层提供

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

一个 Button 由 4 个组件组装而成

  1. 一个 Material 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 替换了 Material 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 示例,了解如何构建自定义设计系统。