将 Compose 与现有界面集成

使用集合让一切井井有条 根据您的偏好保存内容并对其进行分类。

如果您的应用界面是基于 View 系统,您可能不想一次全部重写整个界面。本页将帮助您向现有界面中添加新的 Compose 元素。

迁移共享界面

如果您要逐步迁移到 Compose,可能需要在 Compose 和 View 系统中都使用共享界面元素。例如,如果您的应用具有自定义 CallToActionButton 组件,您可能需要在 Compose 和基于 View 的屏幕中都使用它。

在 Compose 中,共享界面元素成为可在整个应用中重复使用的可组合项,无论元素是采用 XML 进行的样式设计还是一个自定义视图。例如,您将为自定义号召性用语 Button 组件创建 CallToActionButton 可组合项。

为了在基于 View 的屏幕中使用可组合项,您需要创建一个从 AbstractComposeView 扩展的自定义视图封装容器。在该容器被替换的 Content 可组合项中,将您创建的可组合项封装在 Compose 主题中,如下例所示:

@Composable
fun CallToActionButton(
    text: String,
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
) {
    Button(
        colors = ButtonDefaults.buttonColors(
            backgroundColor = MaterialTheme.colors.secondary
        ),
        onClick = onClick,
        modifier = modifier,
    ) {
        Text(text)
    }
}

class CallToActionViewButton @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyle: Int = 0
) : AbstractComposeView(context, attrs, defStyle) {

    var text by mutableStateOf<String>("")
    var onClick by mutableStateOf<() -> Unit>({})

    @Composable
    override fun Content() {
        YourAppTheme {
            CallToActionButton(text, onClick)
        }
    }
}

请注意,可组合项参数在自定义视图中会成为可变变量。这会使自定义 CallToActionViewButton 视图在使用视图绑定等功能时变得可膨胀且可以使用,像传统视图一样。请参见下面的示例:

class ExampleActivity : Activity() {

    private lateinit var binding: ActivityExampleBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityExampleBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.callToAction.apply {
            text = getString(R.string.something)
            onClick = { /* Do something */ }
        }
    }
}

如果自定义组件包含可变状态,请参阅状态可信来源部分。

主题

Material Design 是推荐用于为 Android 应用设置主题的设计系统。

对于基于 View 的应用,可以使用三个 Material 版本:

  • 使用 AppCompat 库(即 Theme.AppCompat.*)的 Material Design 1
  • 使用 MDC-Android 库(即 Theme.MaterialComponents.*)的 Material Design 2
  • 使用 MDC-Android 库(即 Theme.Material3.*)的 Material Design 3

对于 Compose 应用,可以使用两个 Material 版本:

  • 使用 Compose Material 库(即 androidx.compose.material.MaterialTheme)的 Material Design 2
  • 使用 Compose Material 3 库(即 androidx.compose.material3.MaterialTheme)的 Material Design 3

如果应用的设计系统符合要求,建议您使用最新版本 - Material 3。View 和 Compose 都有相应的迁移指南:

在 Compose 中创建新界面时,无论您使用的是哪个版本的 Material Design,都请确保先应用 MaterialTheme,然后再应用任何从 Compose Material 库发出界面的可组合项。Material 组件(ButtonText 等)依赖于现有的 MaterialTheme,如果没有 MaterialTheme,这些组件的行为将处于未定义状态。

所有 Jetpack Compose 示例都使用基于 MaterialTheme 构建的自定义 Compose 主题。

如需了解更多信息,请参阅 Compose 中的设计系统

多个可信来源

现有应用可能包含大量 View 主题和样式。在现有应用中引入 Compose 时,您需要迁移主题才能对任意 Compose 界面使用 MaterialTheme。这意味着应用的主题将会有 2 个可信来源:基于 View 的主题以及 Compose 主题。样式上的任何更改都需要在多处实施。

如果您计划将应用完全迁移到 Compose,最终还是要针对现有主题创建 Compose 版本。问题在于,在开发过程中创建 Compose 主题的时间越早,开发中需要进行的维护就越多。

MDC-Android Compose Theme Adapter

如果您在 Android 应用中使用 MDC-Android 库,则可借助 AppCompat Compose Theme Adapter 库,在可组合项中轻松地重复使用基于 View 的现有 XML 主题的颜色、排版和形状主题。

如果您使用的是 Material 3,请使用 Mdc3Theme 可组合项:

import com.google.android.material.composethemeadapter3.Mdc3Theme

class ExampleActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
        setContent {
            // Use Mdc3Theme instead of M3 MaterialTheme
            // Color scheme, typography, and shapes have been read from the
            // View-based theme used in this Activity
            Mdc3Theme {
                ExampleComposable(/*...*/)
            }
        }
    }
}

如果您使用的是 Material 2,请使用 MdcTheme 可组合项:

import com.google.android.material.composethemeadapter.MdcTheme

class ExampleActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
        setContent {
            // Use MdcTheme instead of M2 MaterialTheme
            // Colors, typography, and shapes have been read from the
            // View-based theme used in this Activity
            MdcTheme {
                ExampleComposable(/*...*/)
            }
        }
    }
}

如需了解更多信息,请参阅 MDC-Android Compose Theme Adapter 库文档

AppCompat Compose Theme Adapter

借助 AppCompat Compose Theme Adapter 库,您可以在 Jetpack Compose 中轻松地重复使用 AppCompat XML 主题。它会使用上下文主题中的颜色和排版值创建 M2 MaterialTheme

class ExampleActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            AppCompatTheme {
                // Colors and typography have been read from the
                // View-based theme used in this Activity
                // Shapes are the default for M2 as this didn't exist in M1
                ExampleComposable(/*...*/)
            }
        }
    }
}

默认组件样式

MDC-Android 和 AppCompat Compose 主题适配器库不会读取任何由主题定义的默认 widget 样式。这是因为 Compose 没有默认可组合项的概念。

如需了解更多信息,请参阅 Compose 中的自定义设计系统

主题叠加层

将基于 View 的屏幕迁移到 Compose 时,请注意 android:theme 属性的用法。您可能需要在 Compose 界面树的相应部分添加新的 MaterialTheme

WindowInsets 和 IME 动画

从 Compose 1.2.0 开始,您可以在布局中使用修饰符处理 WindowInsets。IME 动画也受支持。

class ExampleActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        WindowCompat.setDecorFitsSystemWindows(window, false)

        setContent {
            MaterialTheme {
              MyScreen()
            }
        }
    }
}

@Composable
fun MyScreen() {
    Box {
        LazyColumn(
            modifier = Modifier
                .fillMaxSize() // fill the entire window
                .imePadding() // padding for the bottom for the IME
                .imeNestedScroll(), // scroll IME at the bottom
            content = { }
        )
        FloatingActionButton(
            modifier = Modifier
                .align(Alignment.BottomEnd)
                .padding(16.dp) // normal 16dp of padding for FABs
                .navigationBarsPadding() // padding for navigation bar
                .imePadding(), // padding for when IME appears
            onClick = { }
        ) {
            Icon( /* ... */)
        }
    }
}

展示界面元素上下滚动以便为键盘腾出空间的动画

图 2. IME 动画

如需了解详情,请参阅 accompanists-insets 库文档

优先考虑将状态与呈现分开

过去,View 是有状态的。View 管理的字段用于描述要显示的内容以及显示方式。将 View 转换为 Compose 时,需要将正在渲染的数据隔离开以实现单向数据流,状态提升中对此进行了详细说明。

例如,View 具有 visibility 属性,用于描述该 View 是可见、不可见还是已消失。这是 View 固有的属性。虽然其他代码可能会改变 View 的可见性,但只有 View 本身知道它当前的真实可见性。用于确保 View 可见的逻辑很容易出错,并且通常与 View 本身相关联。

相比之下,Compose 通过使用 Kotlin 中的条件逻辑,可以轻松显示完全不同的可组合项:

if (showCautionIcon) {
    CautionIcon(/* ... */)
}

根据设计,CautionIcon 不需要知道或关心其显示的原因,也没有 visibility 的概念:它要么在组合中,要么不在。

通过将状态管理与内容呈现逻辑完全分开,您能够以状态转换的形式更自由地更改将内容显示到界面的方式。能够在需要时提升状态还会提高可组合项的可重用性,因为状态所有权更灵活。

提升封装组件和可重用组件

View 元素通常对自己所处位置有所感知:在 ActivityDialogFragment 内或另一个 View 层次结构中的某个位置。由于 View 通常是从静态布局文件膨胀而来,因此其整体结构往往非常严格。这会使耦合更紧密,并且使 View 更难以更改或重复使用。

例如,自定义 View 可能假定它具有某种类型的子视图(具有特定 ID),并直接根据某项操作更改其属性。这使得这些 View 元素紧密耦合在一起:如果自定义 View 找不到子级,则可能会发生崩溃或损坏;而如果没有自定义 View 父级,子级可能会无法重复使用。

这在具有可重用可组合项的 Compose 中则不是什么问题。父级可以轻松指定状态和回调,因此可以编写可重用可组合项,而不必知道它们具体将被用在哪里。

var isEnabled by rememberSaveable { mutableStateOf(false) }

Column {
    ImageWithEnabledOverlay(isEnabled)
    ControlPanelWithToggle(
        isEnabled = isEnabled,
        onEnabledChanged = { isEnabled = it }
    )
}

在上面的示例中,所有三个部分封装程度更高,但耦合程度更低:

  • ImageWithEnabledOverlay 只需知道当前的 isEnabled 状态,而不需知道 ControlPanelWithToggle 的存在,甚至不需要知道如何控制它。

  • ControlPanelWithToggle 不知道 ImageWithEnabledOverlay 的存在。isEnabled 可能以零种、一种或多种方式显示,而 ControlPanelWithToggle 无需更改。

  • 对父级而言,ImageWithEnabledOverlayControlPanelWithToggle 的嵌套深度无关紧要。这些子项的目的可能是为变化添加动画效果、换出内容或将内容传递给其他子项。

此模式称为“控制反转”,CompositionLocal 文档中对此做了更详细的介绍。

处理屏幕尺寸的变化

针对不同尺寸的窗口提供不同的资源是创建自适应 View 布局的主要方式之一。虽然在确定屏幕级别的布局时仍可选择使用限定资源,但 Compose 可让您使用常规条件逻辑完全在代码中更改布局,从而更轻松地实现此目的。使用 BoxWithConstraints 等工具,可以根据各元素可使用的空间来确定布局,使用限定资源却无法做到这一点:

@Composable
fun MyComposable() {
    BoxWithConstraints {
        if (minWidth < 480.dp) {
            /* Show grid with 4 columns */
        } else if (minWidth < 720.dp) {
            /* Show grid with 8 columns */
        } else {
            /* Show grid with 12 columns */
        }
    }
}

如需了解 Compose 提供了哪些技术来构建自适应界面,请参阅构建自适应布局

使用 View 实现嵌套滚动

如需详细了解如何在可滚动的 View 元素与可滚动的可组合项之间实现嵌套滚动互操作(相互嵌套),请仔细阅读嵌套滚动互操作性

Compose 在 RecyclerView 中的运用

自 RecyclerView 版本 1.3.0-alpha02 发布以来,RecyclerView 中的可组合项性能一直非常出色。请确保您使用的 RecyclerView 版本不低于 1.3.0-alpha02,以便切实感受这些好处。