将 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(
            containerColor = MaterialTheme.colorScheme.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("")
    var onClick by mutableStateOf({})

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

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

class ViewBindingActivity : ComponentActivity() {

    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.greeting)
            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 中的设计系统将 XML 主题迁移到 Compose

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 动画

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

过去,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 可让您使用常规条件逻辑完全在代码中更改布局,从而更轻松地实现此目的。如需了解详情,请参阅支持不同的屏幕尺寸

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

使用 View 实现嵌套滚动

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

Compose 在 RecyclerView 中的运用

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