Pour améliorer les performances de composition des composants interactifs qui utilisent Modifier.clickable
, nous avons introduit 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 suivantes de l'API :
Obsolète |
Remplacement |
---|---|
|
|
|
De nouvelles API Remarque : Dans ce contexte, "bibliothèques Material" fait référence à |
|
Deux options s'offrent à vous :
|
Cette page décrit l'impact des modifications de comportement et fournit des instructions pour migrer vers les nouvelles API.
Modification du comportement
Les versions suivantes de la bibliothèque incluent une modification du comportement de l'effet d'onde :
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 Ripple. Par conséquent, ils n'interrogent pas LocalRippleTheme
.
Par conséquent, si vous définissez LocalRippleTheme
dans votre application, les composants Material n'utiliseront pas ces valeurs.
Les sections suivantes décrivent 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 appel à ripple()
à partir de la bibliothèque correspondante. Cette API crée une ondulation à l'aide de valeurs dérivées des API du thème Material. Transmettez ensuite l'objet renvoyé à Modifier.clickable
et/ou à d'autres composants.
Par exemple, l'extrait 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ée. Il peut également être réutilisé dans plusieurs composants, comme les modificateurs. Pensez donc à extraire la création de l'onde dans une valeur de premier niveau pour économiser des 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'effet d'ondulation, vous devez plutôt 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 Migrer depuis RippleTheme
.
Migrer depuis RippleTheme
Utiliser RippleTheme
pour désactiver l'effet d'onde pour un composant donné
Les bibliothèques material
et material3
exposent RippleConfiguration
et LocalRippleConfiguration
, qui vous permettent de configurer l'apparence des ondulations dans un sous-arbre. 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 Utiliser RippleTheme
pour modifier globalement toutes les ondulations d'une application.
Par exemple, l'extrait 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 :
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 LocalRippleConfiguration
sont des API expérimentales et ne sont destinées qu'à la personnalisation par composant.
Par exemple, l'extrait 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 de l'effet d'ondulation au niveau du thème. Il s'agissait essentiellement d'un point d'intégration entre les compositionLocals de système de conception personnalisés et l'effet ripple. Au lieu d'exposer une primitive de thème générique, material-ripple
expose désormais une fonction createRippleModifierNode()
. Cette fonction permet aux bibliothèques du système de conception de créer une implémentation wrapper
d'ordre supérieur, qui interroge les valeurs de leur thème, puis délègue l'implémentation de l'effet d'onde 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 les couches de thèmes configurables par l'utilisateur requises en plus, sans avoir à se conformer à ce qui est fourni au niveau de la couche material-ripple
. Ce changement rend également plus explicite le thème/la spécification auxquels l'ondulation se conforme, car c'est l'API d'ondulation elle-même qui définit ce contrat, plutôt que d'être implicitement dérivée du thème.
Pour obtenir des conseils, consultez l'implémentation de l'API Ripple dans les bibliothèques Material et remplacez les appels aux compositionLocals Material selon les besoins de votre propre système de conception.
Migrer de Indication
vers IndicationNodeFactory
Indication
Si vous créez simplement un Indication
à partager, par exemple une ondulation à transmettre à Modifier.clickable
ou Modifier.indication
, vous n'avez pas besoin d'apporter de modifications. IndicationNodeFactory
hérite de Indication
, donc tout continuera à se compiler et à fonctionner.
Création de Indication
Si vous créez votre propre implémentation Indication
, la migration devrait être simple dans la plupart des cas. Prenons l'exemple d'un Indication
qui applique un effet de mise à l'échelle lors de l'appui :
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() } } }
Pour ce faire, vous avez le choix entre deux méthodes :
Migrez
ScaleIndicationInstance
pour qu'il devienneDrawModifierNode
. La surface de l'API pourDrawModifierNode
est très semblable à celle deIndicationInstance
: elle expose une fonctionContentDrawScope#draw()
qui est 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 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 désormais déplacée dans le nœud, il s'agit d'un objet factory très simple dont la seule responsabilité est de créer une instance de nœud.Par exemple, l'extrait 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 }
Créer un IndicationInstance
à l'aide de Indication
Dans la plupart des cas, vous devez utiliser Modifier.indication
pour afficher Indication
pour un composant. Toutefois, dans le cas rare où vous créez manuellement un IndicationInstance
à l'aide de rememberUpdatedInstance
, vous devez mettre à jour votre implémentation pour vérifier si le Indication
est un IndicationNodeFactory
afin de pouvoir utiliser une implémentation plus légère. Par exemple, Modifier.indication
sera délégué en interne au nœud créé s'il s'agit d'un IndicationNodeFactory
. Sinon, il utilisera Modifier.composed
pour appeler rememberUpdatedInstance
.