Migrer vers les API Indication et Ripple

Pour améliorer les performances de composition des composants interactifs qui utilisent Modifier.clickable, nous avons introduit de nouvelles API. Ces API vous permettent des implémentations Indication efficaces, telles que les ondulations.

androidx.compose.foundation:foundation:1.7.0+ et Les androidx.compose.material:material-ripple:1.7.0+ incluent l'API suivante modifications:

Obsolète

Remplacement

Indication#rememberUpdatedInstance

IndicationNodeFactory

rememberRipple()

Nouvelles API ripple() fournies dans les bibliothèques Material.

Remarque: Dans ce contexte, le terme "bibliothèques Material" fait référence à androidx.compose.material:material, androidx.compose.material3:material3, androidx.wear.compose:compose-material et androidx.wear.compose:compose-material3.

RippleTheme

Deux options s'offrent à vous :

  • utiliser les API RippleConfiguration de la bibliothèque Material ; ou
  • Créer votre propre implémentation d'ondulation de système de conception

Cette page décrit l'impact des modifications de comportement et fournit des instructions pour migrer vers les nouvelles API.

Changement de comportement

Les versions suivantes de la bibliothèque incluent un changement de comportement d'ondulation:

  • androidx.compose.material:material:1.7.0+
  • androidx.compose.material3:material3:1.3.0+
  • androidx.wear.compose:compose-material:1.4.0+

Ces versions des bibliothèques Material n'utilisent plus rememberRipple(). à la place, ils utilisent les nouvelles API Ripple. Par conséquent, ils n'interrogent pas LocalRippleTheme. Par conséquent, si vous définissez LocalRippleTheme dans votre application, Material n'utilisent pas ces valeurs.

La section suivante explique comment revenir temporairement à l'ancien comportement sans migrer, Toutefois, nous vous recommandons de migrer vers les nouvelles API. Pour pour obtenir des instructions sur la migration, consultez Migrer de rememberRipple vers ripple. et les sections suivantes.

Mettre à niveau la version de la bibliothèque Material sans effectuer de migration

Pour débloquer la mise à niveau des versions de la bibliothèque, vous pouvez utiliser le API LocalUseFallbackRippleImplementation CompositionLocal à configurer Les composants Material reprennent l'ancien comportement:

CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) {
    MaterialTheme {
        App()
    }
}

Veillez à le fournir en dehors de MaterialTheme pour que les anciennes ondulations puissent être fourni via LocalIndication.

Dans les sections suivantes, nous allons voir comment migrer vers les nouvelles API.

Migrer de rememberRipple vers ripple

Utiliser une bibliothèque Material

Si vous utilisez une bibliothèque Material, remplacez directement rememberRipple() par un un appel à ripple() à partir de la bibliothèque correspondante. Cette API crée une ondulation à l'aide de valeurs dérivées des API Material Theme. Ensuite, transmettez la valeur renvoyée à Modifier.clickable et/ou à d'autres composants.

Par exemple, l'extrait de code suivant utilise les API obsolètes:

Box(
    Modifier.clickable(
        onClick = {},
        interactionSource = remember { MutableInteractionSource() },
        indication = rememberRipple()
    )
) {
    // ...
}

Vous devez modifier l'extrait ci-dessus de la manière suivante:

@Composable
private fun RippleExample() {
    Box(
        Modifier.clickable(
            onClick = {},
            interactionSource = remember { MutableInteractionSource() },
            indication = ripple()
        )
    ) {
        // ...
    }
}

Notez que ripple() n'est plus une fonction modulable et n'a pas besoin d'être mémorisé. Il peut également être réutilisé pour plusieurs composants, des modificateurs. Par conséquent, envisagez d'extraire la création de l'ondulation à une valeur de niveau supérieur pour enregistrer les allocations.

Implémenter un système de conception personnalisé

Si vous mettez en œuvre votre propre système de conception et que vous utilisiez auparavant rememberRipple() avec un RippleTheme personnalisé pour configurer l'ondulation À la place, fournissez votre propre API Ripple qui délègue au nœud Ripple API exposées dans material-ripple. Vos composants peuvent ensuite utiliser votre propre ondulation qui utilise directement les valeurs de votre thème. Pour en savoir plus, consultez la section Migrer de RippleTheme.

Migrer depuis RippleTheme

Désactiver temporairement le changement de comportement

Les bibliothèques Material ont un CompositionLocal temporaire, LocalUseFallbackRippleImplementation, que vous pouvez utiliser pour configurer Composants Material à utiliser en cas d'utilisation de rememberRipple. De cette façon, rememberRipple continue d'interroger LocalRippleTheme.

L'extrait de code suivant montre comment utiliser la classe API LocalUseFallbackRippleImplementation CompositionLocal:

CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) {
    MaterialTheme {
        App()
    }
}

Si vous utilisez un thème d'application personnalisé basé sur Material, vous pouvez fournir en toute sécurité la composition locale dans le thème de votre application:

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun MyAppTheme(content: @Composable () -> Unit) {
    CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) {
        MaterialTheme(content = content)
    }
}

Pour en savoir plus, consultez Mettre à niveau la version de la bibliothèque Material sans migration.

Utiliser RippleTheme pour désactiver une ondulation pour un composant donné

Les bibliothèques material et material3 exposent RippleConfiguration et LocalRippleConfiguration, qui vous permettent de configurer l'apparence ondulations dans une sous-arborescence. Notez que RippleConfiguration et Les LocalRippleConfiguration sont expérimentales et ne sont destinées qu'à des composants spécifiques la personnalisation. La personnalisation globale/à l'échelle du thème n'est pas prise en charge avec ces les API ; consultez la section Utiliser RippleTheme pour modifier globalement toutes les ondulations d'une application pour en savoir plus sur ce cas d'utilisation.

Par exemple, l'extrait de code suivant utilise les API obsolètes:

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 {
            // ...
        }
    }

Vous devez modifier l'extrait ci-dessus de la manière suivante:

CompositionLocalProvider(LocalRippleConfiguration provides null) {
    Button {
        // ...
    }
}

Utiliser RippleTheme pour modifier la couleur/l'alpha d'une ondulation pour un composant donné

Comme décrit dans la section précédente, RippleConfiguration et Les API LocalRippleConfiguration sont expérimentales et ne sont destinées qu'à personnalisation par composant.

Par exemple, l'extrait de code suivant utilise les API obsolètes:

private object DisabledRippleThemeColorAndAlpha : RippleTheme {

    @Composable
    override fun defaultColor(): Color = Color.Red

    @Composable
    override fun rippleAlpha(): RippleAlpha = MyRippleAlpha
}

// ...
    CompositionLocalProvider(LocalRippleTheme provides DisabledRippleThemeColorAndAlpha) {
        Button {
            // ...
        }
    }

Vous devez modifier l'extrait ci-dessus de la manière suivante:

@OptIn(ExperimentalMaterialApi::class)
private val MyRippleConfiguration =
    RippleConfiguration(color = Color.Red, rippleAlpha = MyRippleAlpha)

// ...
    CompositionLocalProvider(LocalRippleConfiguration provides MyRippleConfiguration) {
        Button {
            // ...
        }
    }

Utiliser RippleTheme pour modifier globalement toutes les ondulations d'une application

Auparavant, vous pouviez utiliser LocalRippleTheme pour définir le comportement de l'ondulation à un niveau à l'échelle du thème. Il s'agissait essentiellement d'un point d'intégration la composition locale du système de conception et l'ondulation. Au lieu d'exposer une image primitive de thématisation, material-ripple expose désormais une createRippleModifierNode() . Cette fonction permet aux bibliothèques de systèmes de conception de créer commandent l'implémentation de wrapper, qui interrogent leurs valeurs de thème, puis délèguent l'implémentation de l'ondulation sur le nœud créé par cette fonction.

Cela permet aux systèmes de conception d'interroger directement ce dont ils ont besoin et d'exposer tout nécessite des couches de thématisation configurables par l'utilisateur au-dessus, sans avoir à se conformer ce qui est fourni au niveau de la couche material-ripple. Ce changement rend également plus explicite du thème ou de la spécification auxquels l'ondulation se conforme, car il s'agit qui définit ce contrat, plutôt que d'être implicitement en fonction du thème.

Pour obtenir des conseils, consultez la section Implémentation de l'API Ripple dans Material Design. et de remplacer les appels aux composants locaux de la composition Material si nécessaire pour votre propre système de conception.

Migrer de Indication vers IndicationNodeFactory

Contournement de Indication

Si vous créez simplement un Indication à transmettre, par exemple en créant une pour être transmis à Modifier.clickable ou Modifier.indication, n'ont pas besoin d'apporter des modifications. IndicationNodeFactory hérite de Indication, afin que tout continue à se compiler et à fonctionner.

Création de Indication...

Si vous créez votre propre implémentation de Indication, la migration doit être simple dans la plupart des cas. Prenons l'exemple d'un Indication qui applique une Effet de mise à l'échelle lorsque l'utilisateur appuie:

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()
        }
    }
}

Vous pouvez effectuer la migration en deux étapes:

  1. Migrez ScaleIndicationInstance pour devenir un DrawModifierNode. Surface de l'API pour DrawModifierNode est très semblable à IndicationInstance: il expose un fonction ContentDrawScope#draw() fonctionnellement équivalente à IndicationInstance#drawContent() Vous devez modifier cette fonction, puis implémenter la logique collectLatest directement à l'intérieur du nœud, au lieu de Indication

    Par exemple, l'extrait de code suivant utilise les API obsolètes:

    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()
            }
        }
    }

    Vous devez modifier l'extrait ci-dessus de la manière suivante:

    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()
            }
        }
    }

  2. Migrez ScaleIndication pour implémenter IndicationNodeFactory. En effet, la logique de collecte est déplacée dans le nœud, il s'agit d'une fabrique très simple dont la seule responsabilité est de créer une instance de nœud.

    Par exemple, l'extrait de code suivant utilise les API obsolètes:

    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
        }
    }

    Vous devez modifier l'extrait ci-dessus de la manière suivante:

    object ScaleIndicationNodeFactory : IndicationNodeFactory {
        override fun create(interactionSource: InteractionSource): DelegatableNode {
            return ScaleIndicationNode(interactionSource)
        }
    
        override fun hashCode(): Int = -1
    
        override fun equals(other: Any?) = other === this
    }

Utiliser Indication pour créer un IndicationInstance

Dans la plupart des cas, vous devez utiliser Modifier.indication pour afficher Indication pour une . Toutefois, dans les rares cas où vous créeriez manuellement un IndicationInstance avec rememberUpdatedInstance, vous devez mettre à jour votre pour vérifier si Indication est de type IndicationNodeFactory. utiliser une implémentation plus légère. Par exemple, Modifier.indication va déléguer en interne au nœud créé s'il s'agit d'un IndicationNodeFactory. Si non, il utilisera Modifier.composed pour appeler rememberUpdatedInstance.