为了提高使用Modifier.clickable
的交互式组件的合成性能,我们引入了新的 API。这些 API 允许更有效地实现指示
,例如涟漪。
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 库版本而不迁移
为了解除升级库版本的阻塞,您可以使用临时的 LocalUseFallbackRippleImplementation CompositionLocal
API 将 Material 组件配置为回退到旧行为
CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) { MaterialTheme { App() } }
确保在 MaterialTheme
的**外部**提供此内容,以便可以通过 LocalIndication
提供旧波纹。
以下部分描述了如何迁移到新的 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
迁移
暂时选择退出行为更改
Material 库有一个临时的 CompositionLocal
,LocalUseFallbackRippleImplementation
,您可以使用它将所有 Material 组件配置为回退到使用 rememberRipple
。这样,rememberRipple
将继续查询 LocalRippleTheme
。
以下代码段演示了如何使用 LocalUseFallbackRippleImplementation CompositionLocal
API
CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) { MaterialTheme { App() } }
如果您正在使用构建在 Material 之上的自定义应用程序主题,则可以安全地将组合本地作为应用程序主题的一部分提供
@OptIn(ExperimentalMaterialApi::class) @Composable fun MyAppTheme(content: @Composable () -> Unit) { CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) { MaterialTheme(content = content) } }
有关更多信息,请参阅升级 Material 库版本而不迁移 部分。
使用 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,仅用于每个组件的自定义。
例如,以下代码段使用了已弃用的 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 ) { [email protected]() } } }
您可以分两个步骤迁移它
将
ScaleIndicationInstance
迁移为DrawModifierNode
。DrawModifierNode
的 API 表面与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 ) { [email protected]() } } }
您应该将上述代码段修改为
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 ) { [email protected]() } } }
将
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
,以便您可以使用更轻量级的实现。例如,Modifier.indication
会在内部委托给创建的节点(如果它是 IndicationNodeFactory
)。如果不是,它将使用 Modifier.composed
调用 rememberUpdatedInstance
。