Jetpack Compose 架构分层

本页面概述了构成 Jetpack Compose 的架构分层,以及指导此设计的核心原则。

Jetpack Compose 并非一个单一的整体项目;它由多个模块组装而成,共同构成了一个完整的技术栈。了解构成 Jetpack Compose 的不同模块可帮助您

  • 使用适当的抽象级别来构建您的应用或库
  • 了解何时可以“下钻”到更低的级别以获得更多控制或自定义能力
  • 最大限度地减少依赖项

分层

Jetpack Compose 的主要分层包括:

图 1. Jetpack Compose 的主要分层。

每个分层都构建在较低级别之上,将功能组合起来以创建更高级别的组件。每个分层都基于较低分层的公共 API 构建,以验证模块边界并使您能够在需要时替换任何分层。接下来,我们从下到上逐一考察这些分层。

运行时
此模块提供了 Compose 运行时的基础知识,例如 remembermutableStateOf@Composable 注解和 SideEffect。如果您只需要 Compose 的树管理功能,而不需要其界面,则可以考虑直接在此分层之上构建。
界面
界面分层由多个模块(ui-textui-graphicsui-tooling 等)组成。这些模块实现了界面工具包的基础知识,例如 LayoutNodeModifier、输入处理程序、自定义布局和绘图。如果您只需要界面工具包的基本概念,则可以考虑在此分层之上构建。
基础
此模块为 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 更复杂,但提供了更多控制。请选择最适合您需求的抽象级别。

自定义

如果需要自定义组件,将更高级别的组件从小构建块组装起来会更容易。例如,考虑 Material 层提供的 实现Button

@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,当按钮启用或禁用时更改内容的不透明度

  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 的 当前内容不透明度 和当前文本样式概念。但是,它将 Material Surface 替换为 Row,并对其进行样式化以实现所需外观。

如果您根本不想使用 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 示例