Pour améliorer les performances de composition des composants interactifs qui utilisent Modifier.clickable
, nous avons lancé de nouvelles API. Ces API permettent des implémentations Indication
plus efficaces, telles que les ondulations.
androidx.compose.foundation:foundation:1.7.0+
et androidx.compose.material:material-ripple:1.7.0+
incluent les modifications d'API suivantes:
Obsolète |
Remplacement |
---|---|
|
|
|
De nouvelles API Remarque: Dans ce contexte, les "bibliothèques Material" font référence à |
|
Either:
|
Cette page décrit l'impact des changements de comportement et explique comment migrer vers les nouvelles API.
Changement de comportement
Les versions de bibliothèques suivantes 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()
, mais les nouvelles API d'ondulation. Par conséquent, elles n'interrogent pas LocalRippleTheme
.
Par conséquent, si vous définissez LocalRippleTheme
dans votre application, les composants Material n'utilisent pas ces valeurs.
La section suivante explique comment revenir temporairement à l'ancien comportement sans effectuer de migration. Nous vous recommandons toutefois de migrer vers les nouvelles API. Pour obtenir des instructions de migration, consultez la section Migrer de rememberRipple
vers ripple
ainsi que les sections suivantes.
Mettre à niveau la version de la bibliothèque Material sans migrer
Pour débloquer la mise à niveau des versions de la bibliothèque, vous pouvez utiliser l'API LocalUseFallbackRippleImplementation CompositionLocal
temporaire pour configurer les composants Material de manière à revenir à l'ancien comportement:
CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) { MaterialTheme { App() } }
Assurez-vous de les fournir en dehors de MaterialTheme
afin que les anciennes ondulations puissent être fournies via LocalIndication
.
Les sections suivantes décrivent comment migrer vers les nouvelles API.
Effectuer une migration de rememberRipple
vers ripple
Utiliser une bibliothèque Material
Si vous utilisez une bibliothèque Material, remplacez directement rememberRipple()
par un appel à ripple()
à partir de la bibliothèque correspondante. Cette API crée une ondulation à l'aide de valeurs dérivées des API de thème Material. Transmettez ensuite l'objet renvoyé à 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 pour:
@Composable private fun RippleExample() { Box( Modifier.clickable( onClick = {}, interactionSource = remember { MutableInteractionSource() }, indication = ripple() ) ) { // ... } }
Notez que ripple()
n'est plus une fonction composable et n'a pas besoin d'être mémorisé. Elle peut également être réutilisée sur plusieurs composants, comme pour les modificateurs. Par conséquent, pensez à extraire la création d'ondulations vers une valeur de niveau supérieur pour économiser les allocations.
Implémenter un système de conception personnalisé
Si vous implémentez votre propre système de conception et que vous utilisiez auparavant rememberRipple()
avec un RippleTheme
personnalisé pour configurer l'ondulation, vous devez à la place fournir votre propre API d'ondulation qui délègue aux API de nœud d'ondulation exposées dans material-ripple
. Vos composants peuvent ensuite utiliser votre propre ondulation qui consomme directement les valeurs de votre thème. Pour en savoir plus, consultez la section Migrer depuis RippleTheme
.
Migrer depuis RippleTheme
Désactiver temporairement le changement de comportement
Les bibliothèques Material disposent d'un CompositionLocal
temporaire, LocalUseFallbackRippleImplementation
, que vous pouvez utiliser pour configurer tous les composants Material auxquels utiliser rememberRipple
. Ainsi, rememberRipple
continue d'interroger LocalRippleTheme
.
L'extrait de code suivant montre comment utiliser l'API LocalUseFallbackRippleImplementation CompositionLocal
:
CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) { MaterialTheme { App() } }
Si vous utilisez un thème d'application personnalisé basé sur Material, vous pouvez fournir la composition localement en toute sécurité 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 la section Mettre à niveau la version de la bibliothèque Material sans migrer.
Utiliser RippleTheme
afin de désactiver une ondulation pour un composant donné
Les bibliothèques material
et material3
exposent RippleConfiguration
et LocalRippleConfiguration
, ce qui vous permet de configurer l'apparence des ondulations dans une sous-arborescence. Notez que RippleConfiguration
et LocalRippleConfiguration
sont expérimentaux et ne sont destinés qu'à la personnalisation par composant. La personnalisation globale/à l'échelle du thème n'est pas prise en charge avec ces API. Pour en savoir plus sur ce cas d'utilisation, consultez la section Utiliser RippleTheme
pour modifier de façon globale toutes les ondulations d'une application.
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 pour:
@OptIn(ExperimentalMaterialApi::class) private val DisabledRippleConfiguration = RippleConfiguration(isEnabled = false) // ... CompositionLocalProvider(LocalRippleConfiguration provides DisabledRippleConfiguration) { 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 LocalRippleConfiguration
sont des API expérimentales destinées uniquement à la 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 pour:
@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 d'ondulation à l'échelle du thème. Il s'agissait essentiellement d'un point d'intégration entre la composition locale du système de conception personnalisée et l'ondulation. Au lieu d'exposer une primitive de thématisation générique, material-ripple
expose désormais une fonction createRippleModifierNode()
. Cette fonction permet aux bibliothèques de système de conception de créer une implémentation wrapper
d'ordre supérieur, qui interroge leurs valeurs de thème, puis délègue l'implémentation de l'ondulation au nœud créé par cette fonction.
Cela permet aux systèmes de conception d'interroger directement ce dont ils ont besoin et d'exposer toutes les couches de thématisation configurables par l'utilisateur requises sans avoir à se conformer à ce qui est fourni au niveau de la couche material-ripple
. Cette modification rend également plus explicite le thème/la spécification auquel l'ondulation se conforme, car c'est l'API Ondulation qui définit ce contrat, et non pas implicitement dériver du thème.
Pour en savoir plus, consultez la section sur l'implémentation de l'API Ondulation dans les bibliothèques Material et remplacez les appels locaux à la composition Material selon les besoins de votre propre système de conception.
Effectuer une migration de Indication
vers IndicationNodeFactory
Transmettre Indication
Si vous créez simplement une Indication
à transmettre, par exemple si vous créez une ondulation à transmettre à Modifier.clickable
ou Modifier.indication
, vous n'avez pas besoin d'apporter de modifications. IndicationNodeFactory
hérite de Indication
. Par conséquent, tout continue à se compiler et à fonctionner.
Création de Indication
...
Si vous créez votre propre implémentation de Indication
, la migration devrait être simple dans la plupart des cas. Prenons l'exemple d'un Indication
qui applique un effet d'échelle à la presse:
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:
Migrez
ScaleIndicationInstance
pour en faire unDrawModifierNode
. La surface de l'API pourDrawModifierNode
est très semblable àIndicationInstance
: elle expose une fonctionContentDrawScope#draw()
fonctionnellement équivalente àIndicationInstance#drawContent()
. Vous devez modifier cette fonction, puis implémenter la logiquecollectLatest
directement dans le nœud, au lieu deIndication
.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 pour:
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() } } }
Migrez
ScaleIndication
pour implémenterIndicationNodeFactory
. Étant donné que la logique de collecte est maintenant déplacée dans le nœud, il s'agit d'un objet de 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 pour:
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 un composant. Toutefois, dans les rares cas où vous créez manuellement un IndicationInstance
à l'aide de rememberUpdatedInstance
, vous devez mettre à jour votre implémentation pour vérifier si Indication
est un IndicationNodeFactory
afin de pouvoir utiliser une implémentation plus légère. Par exemple, Modifier.indication
délègue en interne au nœud créé s'il s'agit d'un IndicationNodeFactory
. Sinon, il utilisera Modifier.composed
pour appeler rememberUpdatedInstance
.