Para mejorar el rendimiento de la composición de los componentes interactivos que usan Modifier.clickable
, presentamos nuevas APIs. Estas APIs permiten implementaciones de Indication
más eficientes, como ondas.
androidx.compose.foundation:foundation:1.7.0+
y androidx.compose.material:material-ripple:1.7.0+
incluyen los siguientes cambios en la API:
Obsoleto |
Reemplazo |
---|---|
|
|
|
En su lugar, se proporcionan nuevas APIs de Nota: En este contexto, "bibliotecas de Material" hace referencia a |
|
Por ejemplo, puedes hacer lo siguiente:
|
En esta página, se describen el impacto del cambio de comportamiento y las instrucciones para migrar a las nuevas APIs.
Cambio de comportamiento
Las siguientes versiones de la biblioteca incluyen un cambio en el comportamiento de ondulación:
androidx.compose.material:material:1.7.0+
androidx.compose.material3:material3:1.3.0+
androidx.wear.compose:compose-material:1.4.0+
Estas versiones de las bibliotecas de Material ya no usan rememberRipple()
, sino que usan las nuevas APIs de ondulación. Como resultado, no consultan LocalRippleTheme
.
Por lo tanto, si configuras LocalRippleTheme
en tu aplicación, los componentes de Material no usarán estos valores.
En las siguientes secciones, se describe cómo migrar a las nuevas APIs.
Migra de rememberRipple
a ripple
Cómo usar una biblioteca de Material
Si usas una biblioteca de Material, reemplaza rememberRipple()
directamente por una llamada a ripple()
desde la biblioteca correspondiente. Esta API crea una onda con valores derivados de las APIs del tema de Material. Luego, pasa el objeto devuelto a Modifier.clickable
o a otros componentes.
Por ejemplo, el siguiente fragmento usa las APIs que dejaron de estar disponibles:
Box( Modifier.clickable( onClick = {}, interactionSource = remember { MutableInteractionSource() }, indication = rememberRipple() ) ) { // ... }
Debes modificar el fragmento anterior de la siguiente manera:
@Composable private fun RippleExample() { Box( Modifier.clickable( onClick = {}, interactionSource = remember { MutableInteractionSource() }, indication = ripple() ) ) { // ... } }
Ten en cuenta que ripple()
ya no es una función componible y no es necesario recordarla. También se puede reutilizar en varios componentes, de manera similar a los modificadores, por lo que se recomienda extraer la creación de la propagación a un valor de nivel superior para ahorrar asignaciones.
Implementación de un sistema de diseño personalizado
Si implementas tu propio sistema de diseño y antes usabas rememberRipple()
junto con un RippleTheme
personalizado para configurar la onda, debes proporcionar tu propia API de onda que delega en las APIs de nodos de onda expuestas en material-ripple
. Luego, tus componentes pueden usar tu propia onda que consuma los valores del tema directamente. Para obtener más información, consulta Migra desdeRippleTheme
.
Migra desde RippleTheme
Cómo usar RippleTheme
para inhabilitar la propagación de un componente determinado
Las bibliotecas material
y material3
exponen RippleConfiguration
y LocalRippleConfiguration
, que te permiten configurar la apariencia de las ondas dentro de un subárbol. Ten en cuenta que RippleConfiguration
y LocalRippleConfiguration
son experimentales y solo se diseñaron para la personalización por componente. La personalización global o en todo el tema no se admite con estas APIs. Consulta Cómo usar RippleTheme
para cambiar globalmente todas las ondas en una aplicación para obtener más información sobre ese caso de uso.
Por ejemplo, el siguiente fragmento usa las APIs que dejaron de estar disponibles:
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 { // ... } }
Debes modificar el fragmento anterior de la siguiente manera:
CompositionLocalProvider(LocalRippleConfiguration provides null) { Button { // ... } }
Cómo usar RippleTheme
para cambiar el color o el valor alfa de una onda para un componente determinado
Como se describió en la sección anterior, RippleConfiguration
y LocalRippleConfiguration
son APIs experimentales y solo están diseñadas para la personalización por componente.
Por ejemplo, el siguiente fragmento usa las APIs que dejaron de estar disponibles:
private object DisabledRippleThemeColorAndAlpha : RippleTheme { @Composable override fun defaultColor(): Color = Color.Red @Composable override fun rippleAlpha(): RippleAlpha = MyRippleAlpha } // ... CompositionLocalProvider(LocalRippleTheme provides DisabledRippleThemeColorAndAlpha) { Button { // ... } }
Debes modificar el fragmento anterior de la siguiente manera:
@OptIn(ExperimentalMaterialApi::class) private val MyRippleConfiguration = RippleConfiguration(color = Color.Red, rippleAlpha = MyRippleAlpha) // ... CompositionLocalProvider(LocalRippleConfiguration provides MyRippleConfiguration) { Button { // ... } }
Cómo usar RippleTheme
para cambiar globalmente todas las ondas en una aplicación
Anteriormente, podías usar LocalRippleTheme
para definir el comportamiento de ondulación a nivel de todo el tema. Básicamente, era un punto de integración entre los datos locales de composición del sistema de diseño personalizado y el efecto de ondulación. En lugar de exponer un elemento primitivo de temas genérico, material-ripple
ahora expone una función createRippleModifierNode()
. Esta función permite que las bibliotecas del sistema de diseño creen una implementación de wrapper
de orden superior que consulte los valores de su tema y, luego, delegue la implementación de la onda en el nodo creado por esta función.
Esto permite que los sistemas de diseño consulten directamente lo que necesitan y expongan las capas de temas configurables por el usuario que se requieran en la parte superior sin tener que ajustarse a lo que se proporciona en la capa material-ripple
. Este cambio también hace más explícito a qué tema o especificación se ajusta la onda, ya que es la propia API de la onda la que define ese contrato, en lugar de derivarse implícitamente del tema.
Para obtener orientación, consulta la implementación de la API de Ripple en las bibliotecas de Material y reemplaza las llamadas a las composiciones locales de Material según sea necesario para tu propio sistema de diseño.
Migra de Indication
a IndicationNodeFactory
Pase alrededor de Indication
Si solo creas un Indication
para pasarlo, como crear una onda para pasarla a Modifier.clickable
o Modifier.indication
, no necesitas realizar ningún cambio. IndicationNodeFactory
hereda de Indication
, por lo que todo seguirá compilándose y funcionando.
Creando Indication
Si creas tu propia implementación de Indication
, la migración debería ser sencilla en la mayoría de los casos. Por ejemplo, considera un Indication
que aplica un efecto de escala en la presión:
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() } } }
Puedes migrar esto en dos pasos:
Migra
ScaleIndicationInstance
para que seaDrawModifierNode
. La superficie de la API deDrawModifierNode
es muy similar a la deIndicationInstance
: expone una funciónContentDrawScope#draw()
que es funcionalmente equivalente aIndicationInstance#drawContent()
. Debes cambiar esa función y, luego, implementar la lógica decollectLatest
directamente dentro del nodo, en lugar de enIndication
.Por ejemplo, el siguiente fragmento usa las APIs que dejaron de estar disponibles:
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() } } }
Debes modificar el fragmento anterior de la siguiente manera:
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() } } }
Migra
ScaleIndication
para implementarIndicationNodeFactory
. Dado que la lógica de recopilación ahora se mueve al nodo, este es un objeto de fábrica muy simple cuya única responsabilidad es crear una instancia de nodo.Por ejemplo, el siguiente fragmento usa las APIs que dejaron de estar disponibles:
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 } }
Debes modificar el fragmento anterior de la siguiente manera:
object ScaleIndicationNodeFactory : IndicationNodeFactory { override fun create(interactionSource: InteractionSource): DelegatableNode { return ScaleIndicationNode(interactionSource) } override fun hashCode(): Int = -1 override fun equals(other: Any?) = other === this }
Usa Indication
para crear un IndicationInstance
En la mayoría de los casos, debes usar Modifier.indication
para mostrar Indication
en un componente. Sin embargo, en el caso poco frecuente de que crees manualmente un IndicationInstance
con rememberUpdatedInstance
, debes actualizar tu implementación para verificar si el Indication
es un IndicationNodeFactory
, de modo que puedas usar una implementación más ligera. Por ejemplo, Modifier.indication
delegará internamente al nodo creado si es un IndicationNodeFactory
. De lo contrario, usará Modifier.composed
para llamar a rememberUpdatedInstance
.