Para mejorar el rendimiento de composición de los componentes interactivos que usan
Modifier.clickable
, incorporamos nuevas APIs. Estas APIs permiten más
implementaciones eficientes de Indication
, como ripples.
androidx.compose.foundation:foundation:1.7.0+
y
androidx.compose.material:material-ripple:1.7.0+
incluyen la siguiente API
cambios:
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 describe 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 las 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 bibliotecas de Material ya no usan rememberRipple()
. en su lugar,
usan las nuevas APIs de ripple. Como resultado, no consultan LocalRippleTheme
.
Por lo tanto, si configuras LocalRippleTheme
en tu aplicación, Material
componentes no usarán estos valores.
En la siguiente sección, se describe cómo volver temporalmente al comportamiento anterior
sin migrar; sin embargo, te recomendamos
migrar a las APIs nuevas. Para
instrucciones de migración, consulta Cómo migrar de rememberRipple
a ripple
y en las secciones posteriores.
Actualiza la versión de la biblioteca de Material sin migrar
Para desbloquear las versiones actualizadas de las bibliotecas, puedes usar el
API de LocalUseFallbackRippleImplementation CompositionLocal
para configurar
Componentes de Material para volver al comportamiento anterior:
CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) { MaterialTheme { App() } }
Asegúrate de proporcionar esto fuera de MaterialTheme
para que los ondas anteriores puedan
proporcionarse a través de LocalIndication
.
En las siguientes secciones, se describe cómo migrar a las APIs nuevas.
Migra de rememberRipple
a ripple
Cómo usar una biblioteca de Material
Si usas una biblioteca de Material, reemplaza directamente rememberRipple()
por un
llamada a ripple()
desde la biblioteca correspondiente. Esta API crea un ripple,
con valores derivados de las APIs de tema de Material. Luego, pasa los valores de
objeto a Modifier.clickable
o a otros componentes.
Por ejemplo, en el siguiente fragmento, se usan 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
recordarse. También se puede reutilizar en varios componentes, de manera similar a
modificadores, así que considera extraer la creación de ondas a un valor de nivel superior para
y guardar 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 el efecto de ondas,
debes proporcionar tu propia API de ripple que delega al nodo de ripple,
APIs expuestas en material-ripple
. Luego, tus componentes pueden usar tu propio efecto
que consuma los valores de tu tema directamente. 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 todo
Componentes de Material a los que volver a usar rememberRipple
De esta manera,
rememberRipple
continúa realizando consultas a 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 que se basa en Material, puedes Proporciona de manera segura la composición local 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 actualización de la versión de la biblioteca de Material sin Migración.
Cómo usar RippleTheme
para inhabilitar un ripple para un componente determinado
Las bibliotecas material
y material3
exponen RippleConfiguration
y
LocalRippleConfiguration
, que te permiten configurar la apariencia de
ondas en un subárbol. Ten en cuenta que RippleConfiguration
y
Las LocalRippleConfiguration
son experimentales y solo están diseñadas por componente
personalización. La personalización global o de todo el tema no es compatible con estos
APIs; consulta Usar RippleTheme
para cambiar globalmente todos los ondas en una
aplicación para obtener más información sobre ese caso de uso.
Por ejemplo, en el siguiente fragmento, se usan 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:
CompositionLocalProvider(LocalRippleConfiguration provides null) { Button { // ... } }
Uso de RippleTheme
para cambiar el color/alfa de un ripple para un componente determinado
Como se describió en la sección anterior, RippleConfiguration
y
LocalRippleConfiguration
son APIs experimentales y están diseñadas solo para
personalización por componente.
Por ejemplo, en el siguiente fragmento, se usan 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 todos los ecos en una aplicación
Anteriormente, se podía usar LocalRippleTheme
para definir el comportamiento de la onda en un
en todo el tema. Básicamente, esto era un punto de integración
de la composición del sistema de diseño local y de ripple. En lugar de exponer una dirección
primitivo de temas, material-ripple
ahora expone un createRippleModifierNode()
. Esta función permite que las bibliotecas de sistemas de diseño
ordenar la implementación de wrapper
, que consultan sus valores de tema y, luego, delegan
la implementación de ripple en el nodo creado por esta función.
Esto permite que los sistemas de diseño consulten directamente lo que necesitan y expongan
y las capas de temas
configurables por el usuario necesarias
lo que se proporciona en la capa material-ripple
. Este cambio también hace más
explícito a qué tema/especificación se ajusta la onda, ya que es el
La API de ripple que define ese contrato, en lugar de ser implícitamente
derivadas del tema.
Para obtener orientación, consulta la implementación de la API de ripple en Material. y reemplazar las llamadas a los 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, por ejemplo, si creas un
ripple para pasar a Modifier.clickable
o Modifier.indication
, no
no necesitas hacer ningún cambio. IndicationNodeFactory
hereda de Indication
,
por lo que todo seguirá
compilando y funcionando.
Creando Indication
Si creas tu propia implementación de Indication
, la migración debe
puede ser simple en la mayoría de los casos. Por ejemplo, considera un Indication
que aplica un
efecto de escala al presionar:
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 migrarla en dos pasos:
Migra
ScaleIndicationInstance
para que seaDrawModifierNode
. La plataforma de la API paraDrawModifierNode
es muy similar aIndicationInstance
: expone un funciónContentDrawScope#draw()
que es funcionalmente equivalente aIndicationInstance#drawContent()
Debes cambiar esa función y, luego, implementa la lógicacollectLatest
directamente dentro del nodo, en lugar delIndication
Por ejemplo, en el siguiente fragmento, se usan 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 el la lógica de recopilación se traslada al nodo, es una forma sencilla de su única responsabilidad es crear una instancia de nodo.Por ejemplo, en el siguiente fragmento, se usan 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
para una
este componente. Sin embargo, en el raro caso de que crees manualmente un
IndicationInstance
usando rememberUpdatedInstance
, debes actualizar tu
implementación para verificar si Indication
es un IndicationNodeFactory
, de modo que
puedes usar una implementación más ligera. Por ejemplo, Modifier.indication
delegar internamente al nodo creado si es un IndicationNodeFactory
. Si
no, usará Modifier.composed
para llamar a rememberUpdatedInstance
.