Para mejorar el rendimiento de 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 proporcionaron nuevas APIs de Nota: En este contexto, "bibliotecas de Material" hace referencia a |
|
Por ejemplo, puedes hacer lo siguiente:
|
En esta página, se describe el impacto del cambio de comportamiento y las instrucciones para migrar a las APIs nuevas.
Cambio de comportamiento
Las siguientes versiones de la biblioteca incluyen un cambio de comportamiento de ondas:
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 ripple. 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 la siguiente sección, se describe cómo recurrir de manera temporal al comportamiento anterior sin migrar. Sin embargo, te recomendamos que migres a las APIs nuevas. Para obtener instrucciones de migración, consulta Migra de rememberRipple
a ripple
y las secciones posteriores.
Cómo actualizar la versión de la biblioteca de Material sin migrar
Para desbloquear las versiones actualizadas de la biblioteca, puedes usar la API temporal de LocalUseFallbackRippleImplementation CompositionLocal
para configurar los componentes de Material y volver al comportamiento anterior:
CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) { MaterialTheme { App() } }
Asegúrate de proporcionar esto fuera del MaterialTheme
para que las ondas antiguas se puedan proporcionar a través de LocalIndication
.
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 directamente rememberRipple()
por una llamada a ripple()
desde la biblioteca correspondiente. Esta API crea una onda con valores derivados de las APIs de temas de Material. Luego, pasa el objeto que se muestra a Modifier.clickable
o a otros componentes.
Por ejemplo, el siguiente fragmento usa las APIs obsoletas:
Box( Modifier.clickable( onClick = {}, interactionSource = remember { MutableInteractionSource() }, indication = rememberRipple() ) ) { // ... }
Deberías 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 de componibilidad y no es necesario recordarla. También se puede volver a usar en varios componentes, de manera similar a los modificadores, por lo que debes considerar extraer la creación de ondas a un valor de nivel superior para guardar asignaciones.
Implementación de un sistema de diseño personalizado
Si estás implementando tu propio sistema de diseño y antes usabas rememberRipple()
junto con un RippleTheme
personalizado para configurar el ripple, debes proporcionar tu propia API de ripple que se delegue a las APIs del nodo de ripple expuestas en material-ripple
. Luego, tus componentes pueden usar tu propia onda que consume directamente los valores de tu tema. Para obtener más información, consulta Cómo migrar desde RippleTheme
.
Migra desde RippleTheme
Cómo inhabilitar temporalmente el cambio de comportamiento
Las bibliotecas de Material tienen un CompositionLocal
temporal, LocalUseFallbackRippleImplementation
, que puedes usar para configurar todos los componentes de Material y recurrir a rememberRipple
. De esta manera, rememberRipple
continúa realizando consultas en LocalRippleTheme
.
En el siguiente fragmento de código, se muestra cómo usar la API de LocalUseFallbackRippleImplementation CompositionLocal
:
CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) { MaterialTheme { App() } }
Si usas un tema de app personalizado compilado sobre Material, puedes proporcionar, de forma segura, el elemento local de composición como parte del tema de tu app:
@OptIn(ExperimentalMaterialApi::class) @Composable fun MyAppTheme(content: @Composable () -> Unit) { CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) { MaterialTheme(content = content) } }
Para obtener más información, consulta la sección Cómo actualizar la versión de la biblioteca de Material sin migrar.
Cómo usar RippleTheme
para inhabilitar un efecto de ondas para un componente determinado
Las bibliotecas material
y material3
exponen RippleConfiguration
y LocalRippleConfiguration
, lo que te permite configurar la apariencia de ondas dentro de un subárbol. Ten en cuenta que RippleConfiguration
y LocalRippleConfiguration
son experimentales y solo están destinados a la personalización por componente. La personalización global o de todo el tema no es compatible con estas APIs. Consulta Cómo usar RippleTheme
para cambiar de forma global todas las ondas de una aplicación si quieres obtener más información sobre ese caso de uso.
Por ejemplo, el siguiente fragmento usa las APIs obsoletas:
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 { // ... } }
Deberías modificar el fragmento anterior de la siguiente manera:
@OptIn(ExperimentalMaterialApi::class) private val DisabledRippleConfiguration = RippleConfiguration(isEnabled = false) // ... CompositionLocalProvider(LocalRippleConfiguration provides DisabledRippleConfiguration) { 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 obsoletas:
private object DisabledRippleThemeColorAndAlpha : RippleTheme { @Composable override fun defaultColor(): Color = Color.Red @Composable override fun rippleAlpha(): RippleAlpha = MyRippleAlpha } // ... CompositionLocalProvider(LocalRippleTheme provides DisabledRippleThemeColorAndAlpha) { Button { // ... } }
Deberías 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, se podía usar LocalRippleTheme
para definir el comportamiento de ondas a nivel de todo el tema. En esencia, este era un punto de integración entre los entornos locales de composición del sistema de diseño personalizado y el efecto de ondas. En lugar de exponer un primitivo de tema genérico, material-ripple
ahora expone una función createRippleModifierNode()
. Esta función permite que las bibliotecas del sistema de diseño creen implementaciones de wrapper
de orden superior, que consulten sus valores de tema y luego deleguen la implementación de ripple al nodo creado por esta función.
Esto permite que los sistemas de diseño consulten directamente lo que necesitan y expongan en la parte superior cualquier capa de temas configurable por el usuario sin tener que ajustarse a lo que se proporciona en la capa material-ripple
. Este cambio también hace que sea más explícito a qué tema o especificación cumple la onda, ya que es la API de ripple en sí misma la que define ese contrato, en lugar de derivar de manera implícita 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 elementos locales de composición de Material según sea necesario para tu propio sistema de diseño.
Migra de Indication
a IndicationNodeFactory
Pasando por Indication
Si solo estás creando un Indication
para pasar, como una ondulación que pasará a Modifier.clickable
o Modifier.indication
, no es necesario que realices 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 simple en la mayoría de los casos. Por ejemplo, considera una Indication
que aplica un efecto de escala cuando se presiona:
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 migrarlo en dos pasos:
Migra
ScaleIndicationInstance
para que sea unDrawModifierNode
. La plataforma de la API paraDrawModifierNode
es muy similar aIndicationInstance
: expone una funciónContentDrawScope#draw()
que es funcionalmente equivalente aIndicationInstance#drawContent()
. Debes cambiar esa función y, luego, implementar la lógicacollectLatest
directamente dentro del nodo, en lugar deIndication
.Por ejemplo, el siguiente fragmento usa las APIs obsoletas:
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() } } }
Deberías 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
. Debido a que la lógica de recopilación ahora se traslada al nodo, se trata de un objeto de fábrica muy simple cuya única responsabilidad es crear una instancia de nodo.Por ejemplo, el siguiente fragmento usa las APIs obsoletas:
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 } }
Deberías 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
de un componente. Sin embargo, en el caso poco probable de que crees manualmente un IndicationInstance
con rememberUpdatedInstance
, debes actualizar tu implementación para verificar si Indication
es un IndicationNodeFactory
, de modo que puedas usar una implementación más ligera. Por ejemplo, Modifier.indication
delegará de forma interna al nodo creado si es un IndicationNodeFactory
. De lo contrario, usará Modifier.composed
para llamar a rememberUpdatedInstance
.