为了提高使用 Modifier.clickable 的互动组件的组合性能,我们引入了新的 API。这些 API 可实现更高效的 Indication 实现,例如涟漪效果。
androidx.compose.foundation:foundation:1.7.0+ 和 androidx.compose.material:material-ripple:1.7.0+ 包含以下 API 更改:
已弃用 |
替换 |
|---|---|
|
|
|
改为使用 Material 库中提供的新 注意:在此上下文中,“Material 库”是指 |
|
采用以下任一方式:
|
本页面介绍了行为变更的影响以及迁移到新 API 的说明。
行为变更
以下库版本包含涟漪效果行为变更:
androidx.compose.material:material:1.7.0+androidx.compose.material3:material3:1.3.0+androidx.wear.compose:compose-material:1.4.0+
这些版本的 Material 库不再使用 rememberRipple(),而是使用新的涟漪效果 API。因此,它们不会查询 LocalRippleTheme。
因此,如果您在应用中设置 LocalRippleTheme,Material 组件将不会使用这些值。
以下部分介绍了如何迁移到新 API。
从 rememberRipple 迁移到 ripple
使用 Material 库
如果您使用的是 Material 库,请直接将 rememberRipple() 替换为对相应库中的 ripple() 的调用。此 API 会使用从 Material 主题设置 API 派生的值创建涟漪效果。然后,将返回的对象传递给 Modifier.clickable 和/或其他组件。
例如,以下代码段使用了已废弃的 API:
Box( Modifier.clickable( onClick = {}, interactionSource = remember { MutableInteractionSource() }, indication = rememberRipple() ) ) { // ... }
您应将上述代码段修改为:
@Composable private fun RippleExample() { Box( Modifier.clickable( onClick = {}, interactionSource = remember { MutableInteractionSource() }, indication = ripple() ) ) { // ... } }
请注意,ripple() 不再是可组合函数,不需要记住。它也可以在多个组件中重复使用,类似于修饰符,因此请考虑将涟漪创建提取到顶级值,以节省分配。
实现自定义设计体系
如果您要实现自己的设计体系,并且之前使用 rememberRipple() 以及自定义 RippleTheme 来配置涟漪效果,则应改为提供您自己的涟漪效果 API,该 API 会委托给 material-ripple 中公开的涟漪效果节点 API。然后,您的组件可以使用您自己的涟漪,该涟漪会直接使用您的主题值。如需了解详情,请参阅从 RippleTheme 迁移。
从 RippleTheme 迁移
使用 RippleTheme 为给定组件停用涟漪效果
material 和 material3 库公开了 RippleConfiguration 和 LocalRippleConfiguration,可让您配置子树中涟漪的外观。请注意,RippleConfiguration 和 LocalRippleConfiguration 仅用于按组件自定义。这些 API 不支持全局/主题级自定义;如需详细了解该用例,请参阅使用 RippleTheme 全局更改应用中的所有涟漪效果。
例如,以下代码段使用了已废弃的 API:
private object DisabledRippleTheme : RippleTheme { @Composable override fun defaultColor(): Color = Color.Transparent @Composable override fun rippleAlpha(): RippleAlpha = RippleAlpha(0f, 0f, 0f, 0f) } // ... CompositionLocalProvider(LocalRippleTheme provides DisabledRippleTheme) { Button { // ... } }
您应将上述代码段修改为:
CompositionLocalProvider(LocalRippleConfiguration provides null) { Button { // ... } }
使用 RippleTheme 更改给定组件的涟漪颜色/Alpha 值
如上一部分所述,RippleConfiguration 和 LocalRippleConfiguration 仅用于按组件自定义。
例如,以下代码段使用了已废弃的 API:
private object DisabledRippleThemeColorAndAlpha : RippleTheme { @Composable override fun defaultColor(): Color = Color.Red @Composable override fun rippleAlpha(): RippleAlpha = MyRippleAlpha } // ... CompositionLocalProvider(LocalRippleTheme provides DisabledRippleThemeColorAndAlpha) { Button { // ... } }
您应将上述代码段修改为:
@OptIn(ExperimentalMaterialApi::class) private val MyRippleConfiguration = RippleConfiguration(color = Color.Red, rippleAlpha = MyRippleAlpha) // ... CompositionLocalProvider(LocalRippleConfiguration provides MyRippleConfiguration) { Button { // ... } }
使用 RippleTheme 全局更改应用中的所有涟漪效果
以前,您可以使用 LocalRippleTheme 在主题级定义涟漪效果行为。这实际上是自定义设计体系组合局部变量与涟漪效果之间的集成点。material-ripple 现在公开的是 createRippleModifierNode() 函数,而不是通用主题基元。此函数允许设计体系库创建更高阶的 wrapper 实现,这些实现会查询其主题值,然后将涟漪实现委托给此函数创建的节点。
这样,设计体系就可以直接查询所需内容,并在顶层公开任何所需的用户可配置主题层,而无需遵循 material-ripple 层提供的内容。此项变更还更明确地指出了涟漪效果符合的主题/规范,因为涟漪效果 API 本身定义了该协定,而不是从主题中隐式派生。
如需相关指导,请参阅 Material 库中的涟漪效果 API 实现,并根据您自己的设计体系的需要替换对 Material 组合局部值的调用。
从 Indication 迁移到 IndicationNodeFactory
经过Indication
如果您只是创建 Indication 以便传递,例如创建涟漪效果以传递给 Modifier.clickable 或 Modifier.indication,则无需进行任何更改。IndicationNodeFactory 继承自 Indication,因此所有内容都将继续编译并正常运行。
正在创建“Indication”
如果您要创建自己的 Indication 实现,在大多数情况下,迁移应该很简单。例如,假设有一个 Indication,它会在按下时应用缩放效果:
object ScaleIndication : Indication { @Composable override fun rememberUpdatedInstance(interactionSource: InteractionSource): IndicationInstance { // key the remember against interactionSource, so if it changes we create a new instance val instance = remember(interactionSource) { ScaleIndicationInstance() } LaunchedEffect(interactionSource) { interactionSource.interactions.collectLatest { interaction -> when (interaction) { is PressInteraction.Press -> instance.animateToPressed(interaction.pressPosition) is PressInteraction.Release -> instance.animateToResting() is PressInteraction.Cancel -> instance.animateToResting() } } } return instance } } private class ScaleIndicationInstance : IndicationInstance { var currentPressPosition: Offset = Offset.Zero val animatedScalePercent = Animatable(1f) suspend fun animateToPressed(pressPosition: Offset) { currentPressPosition = pressPosition animatedScalePercent.animateTo(0.9f, spring()) } suspend fun animateToResting() { animatedScalePercent.animateTo(1f, spring()) } override fun ContentDrawScope.drawIndication() { scale( scale = animatedScalePercent.value, pivot = currentPressPosition ) { this@drawIndication.drawContent() } } }
您可以通过以下两个步骤进行迁移:
将
ScaleIndicationInstance迁移为DrawModifierNode。DrawModifierNode的 API Surface 与IndicationInstance非常相似:它公开了一个ContentDrawScope#draw()函数,该函数在功能上等同于IndicationInstance#drawContent()。您需要更改该函数,然后直接在节点内实现collectLatest逻辑,而不是Indication。例如,以下代码段使用了已废弃的 API:
private class ScaleIndicationInstance : IndicationInstance { var currentPressPosition: Offset = Offset.Zero val animatedScalePercent = Animatable(1f) suspend fun animateToPressed(pressPosition: Offset) { currentPressPosition = pressPosition animatedScalePercent.animateTo(0.9f, spring()) } suspend fun animateToResting() { animatedScalePercent.animateTo(1f, spring()) } override fun ContentDrawScope.drawIndication() { scale( scale = animatedScalePercent.value, pivot = currentPressPosition ) { this@drawIndication.drawContent() } } }
您应将上述代码段修改为:
private class ScaleIndicationNode( private val interactionSource: InteractionSource ) : Modifier.Node(), DrawModifierNode { var currentPressPosition: Offset = Offset.Zero val animatedScalePercent = Animatable(1f) private suspend fun animateToPressed(pressPosition: Offset) { currentPressPosition = pressPosition animatedScalePercent.animateTo(0.9f, spring()) } private suspend fun animateToResting() { animatedScalePercent.animateTo(1f, spring()) } override fun onAttach() { coroutineScope.launch { interactionSource.interactions.collectLatest { interaction -> when (interaction) { is PressInteraction.Press -> animateToPressed(interaction.pressPosition) is PressInteraction.Release -> animateToResting() is PressInteraction.Cancel -> animateToResting() } } } } override fun ContentDrawScope.draw() { scale( scale = animatedScalePercent.value, pivot = currentPressPosition ) { this@draw.drawContent() } } }
迁移
ScaleIndication以实现IndicationNodeFactory。由于收集逻辑现在已移至节点中,因此这是一个非常简单的工厂对象,其唯一职责是创建节点实例。例如,以下代码段使用了已废弃的 API:
object ScaleIndication : Indication { @Composable override fun rememberUpdatedInstance(interactionSource: InteractionSource): IndicationInstance { // key the remember against interactionSource, so if it changes we create a new instance val instance = remember(interactionSource) { ScaleIndicationInstance() } LaunchedEffect(interactionSource) { interactionSource.interactions.collectLatest { interaction -> when (interaction) { is PressInteraction.Press -> instance.animateToPressed(interaction.pressPosition) is PressInteraction.Release -> instance.animateToResting() is PressInteraction.Cancel -> instance.animateToResting() } } } return instance } }
您应将上述代码段修改为:
object ScaleIndicationNodeFactory : IndicationNodeFactory { override fun create(interactionSource: InteractionSource): DelegatableNode { return ScaleIndicationNode(interactionSource) } override fun hashCode(): Int = -1 override fun equals(other: Any?) = other === this }
使用 Indication 创建 IndicationInstance
在大多数情况下,您应使用 Modifier.indication 显示组件的 Indication。不过,在极少数情况下,如果您使用 rememberUpdatedInstance 手动创建 IndicationInstance,则需要更新实现,以检查 Indication 是否为 IndicationNodeFactory,以便您可以使用更轻量的实现。例如,如果创建的节点是 IndicationNodeFactory,Modifier.indication 会在内部委托给该节点。否则,它将使用 Modifier.composed 调用 rememberUpdatedInstance。