Per migliorare le prestazioni di composizione dei componenti interattivi che utilizzano Modifier.clickable, abbiamo introdotto nuove API. Queste API consentono implementazioni di Indication più efficienti, come gli effetti ripple.
androidx.compose.foundation:foundation:1.7.0+ e
androidx.compose.material:material-ripple:1.7.0+ includono le seguenti modifiche
all'API:
Deprecato |
Sostituzione |
|---|---|
|
|
|
Utilizza invece le nuove API Nota: in questo contesto, "librerie Material" si riferisce a |
|
Procedi in uno dei seguenti modi:
|
In questa pagina viene descritto l'impatto della modifica del comportamento e vengono fornite le istruzioni per la migrazione alle nuove API.
Modifica del comportamento
Le seguenti versioni della libreria includono una modifica del comportamento dell'effetto onda:
androidx.compose.material:material:1.7.0+androidx.compose.material3:material3:1.3.0+androidx.wear.compose:compose-material:1.4.0+
Queste versioni delle librerie Material non utilizzano più rememberRipple(), ma le nuove API Ripple. Di conseguenza, non eseguono query su LocalRippleTheme.
Pertanto, se imposti LocalRippleTheme nella tua applicazione, i componenti Material non utilizzeranno questi valori.
Le sezioni seguenti descrivono come eseguire la migrazione alle nuove API.
Esegui la migrazione da rememberRipple a ripple
Utilizzo di una libreria Material
Se utilizzi una libreria Material, sostituisci direttamente rememberRipple() con una chiamata a ripple() dalla libreria corrispondente. Questa API crea un effetto onda utilizzando valori derivati dalle API dei temi di Material. Quindi, passa l'oggetto restituito a Modifier.clickable e/o ad altri componenti.
Ad esempio, il seguente snippet utilizza le API deprecate:
Box( Modifier.clickable( onClick = {}, interactionSource = remember { MutableInteractionSource() }, indication = rememberRipple() ) ) { // ... }
Devi modificare lo snippet riportato sopra come segue:
@Composable private fun RippleExample() { Box( Modifier.clickable( onClick = {}, interactionSource = remember { MutableInteractionSource() }, indication = ripple() ) ) { // ... } }
Tieni presente che ripple() non è più una funzione componibile e non deve essere
memorizzata. Può anche essere riutilizzato in più componenti, in modo simile ai modificatori, quindi valuta la possibilità di estrarre la creazione dell'effetto onda in un valore di primo livello per risparmiare allocazioni.
Implementazione del sistema di progettazione personalizzato
Se stai implementando il tuo sistema di progettazione e in precedenza utilizzavi rememberRipple() insieme a un RippleTheme personalizzato per configurare l'effetto onda, devi invece fornire la tua API per l'effetto onda che delega alle API del nodo dell'effetto onda esposte in material-ripple. In questo modo, i tuoi componenti possono utilizzare l'effetto ripple personalizzato che utilizza direttamente i valori del tema. Per maggiori informazioni, consulta la pagina Esegui la migrazione da RippleTheme.
Esegui la migrazione da RippleTheme
Utilizzo di RippleTheme per disattivare un ripple per un determinato componente
Le librerie material e material3 espongono RippleConfiguration e LocalRippleConfiguration, che ti consentono di configurare l'aspetto degli effetti ripple all'interno di un sottoalbero. Tieni presente che RippleConfiguration e LocalRippleConfiguration sono destinati solo alla personalizzazione per componente. La personalizzazione globale/a livello di tema non è supportata con queste API; per ulteriori informazioni su questo caso d'uso, consulta Utilizzo di RippleTheme per modificare globalmente tutti gli effetti ripple in un'applicazione.
Ad esempio, il seguente snippet utilizza le API deprecate:
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 { // ... } }
Devi modificare lo snippet riportato sopra come segue:
CompositionLocalProvider(LocalRippleConfiguration provides null) { Button { // ... } }
Utilizzo di RippleTheme per modificare il colore/l'alpha di un ripple per un determinato componente
Come descritto nella sezione precedente, RippleConfiguration e
LocalRippleConfiguration sono destinati solo alla
personalizzazione per componente.
Ad esempio, il seguente snippet utilizza le API deprecate:
private object DisabledRippleThemeColorAndAlpha : RippleTheme { @Composable override fun defaultColor(): Color = Color.Red @Composable override fun rippleAlpha(): RippleAlpha = MyRippleAlpha } // ... CompositionLocalProvider(LocalRippleTheme provides DisabledRippleThemeColorAndAlpha) { Button { // ... } }
Devi modificare lo snippet riportato sopra come segue:
@OptIn(ExperimentalMaterialApi::class) private val MyRippleConfiguration = RippleConfiguration(color = Color.Red, rippleAlpha = MyRippleAlpha) // ... CompositionLocalProvider(LocalRippleConfiguration provides MyRippleConfiguration) { Button { // ... } }
Utilizzo di RippleTheme per modificare globalmente tutti gli effetti ripple in un'applicazione
In precedenza, potevi utilizzare LocalRippleTheme per definire il comportamento dell'effetto ripple a livello di tema. Si trattava essenzialmente di un punto di integrazione tra i CompositionLocal del sistema di progettazione personalizzato e l'effetto ripple. Anziché esporre una primitiva di tematizzazione generica, material-ripple ora espone una funzione createRippleModifierNode(). Questa funzione consente alle librerie del sistema di progettazione di creare un'implementazione di wrapper di ordine superiore, che esegue query sui valori del tema e poi delega l'implementazione dell'effetto onda al nodo creato da questa funzione.
In questo modo, i sistemi di progettazione possono eseguire query direttamente su ciò di cui hanno bisogno ed esporre eventuali livelli di tematizzazione configurabili dall'utente richiesti, senza dover rispettare ciò che viene fornito al livello material-ripple. Questa modifica rende anche più esplicito il tema/la specifica a cui si conforma l'effetto onda, poiché è l'API dell'effetto onda stessa a definire questo contratto, anziché essere derivato implicitamente dal tema.
Per indicazioni, consulta l'implementazione dell'API Ripple nelle librerie Material e sostituisci le chiamate ai componenti locali di composizione di Material in base alle esigenze del tuo sistema di progettazione.
Esegui la migrazione da Indication a IndicationNodeFactory
Passaggio vicino a Indication
Se stai solo creando un Indication da passare, ad esempio creando un ripple da passare a Modifier.clickable o Modifier.indication, non devi apportare modifiche. IndicationNodeFactory eredita da Indication,
quindi tutto continuerà a essere compilato e a funzionare.
Creazione di Indication in corso…
Se stai creando la tua implementazione di Indication, nella maggior parte dei casi la migrazione dovrebbe essere semplice. Ad esempio, considera un Indication che applica un effetto di scalabilità alla pressione:
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() } } }
Puoi eseguire la migrazione in due passaggi:
Esegui la migrazione di
ScaleIndicationInstanceper renderlo unDrawModifierNode. La superficie dell'API perDrawModifierNodeè molto simile aIndicationInstance: espone una funzioneContentDrawScope#draw()che è funzionalmente equivalente aIndicationInstance#drawContent(). Devi modificare questa funzione e poi implementare la logicacollectLatestdirettamente all'interno del nodo, anziché diIndication.Ad esempio, il seguente snippet utilizza le API deprecate:
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() } } }
Devi modificare lo snippet riportato sopra come segue:
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() } } }
Esegui la migrazione di
ScaleIndicationper implementareIndicationNodeFactory. Poiché la logica di raccolta è ora spostata nel nodo, si tratta di un oggetto factory molto semplice la cui unica responsabilità è creare un'istanza del nodo.Ad esempio, il seguente snippet utilizza le API deprecate:
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 } }
Devi modificare lo snippet riportato sopra come segue:
object ScaleIndicationNodeFactory : IndicationNodeFactory { override fun create(interactionSource: InteractionSource): DelegatableNode { return ScaleIndicationNode(interactionSource) } override fun hashCode(): Int = -1 override fun equals(other: Any?) = other === this }
Utilizzo di Indication per creare un IndicationInstance
Nella maggior parte dei casi, devi utilizzare Modifier.indication per visualizzare Indication per un componente. Tuttavia, nel raro caso in cui crei manualmente un IndicationInstance utilizzando rememberUpdatedInstance, devi aggiornare l'implementazione per verificare se Indication è un IndicationNodeFactory in modo da poter utilizzare un'implementazione più leggera. Ad esempio, Modifier.indication delegherà internamente al nodo creato se si tratta di un IndicationNodeFactory. In caso contrario, utilizzerà Modifier.composed per chiamare rememberUpdatedInstance.