Esegui la migrazione alle API Indication e Ripple

Per migliorare le prestazioni della composizione dei componenti interattivi che utilizzano Modifier.clickable, abbiamo introdotto nuove API. Queste API consentono di avere implementazioni Indication efficienti, come le eco.

androidx.compose.foundation:foundation:1.7.0+ e androidx.compose.material:material-ripple:1.7.0+ includono la seguente API modifiche:

Obsoleta

Sostituzione

Indication#rememberUpdatedInstance

IndicationNodeFactory

rememberRipple()

Nuove API ripple() fornite invece nelle librerie Material.

Nota: in questo contesto, le "librerie di materiali" si riferisce a androidx.compose.material:material, androidx.compose.material3:material3, androidx.wear.compose:compose-material e androidx.wear.compose:compose-material3.

RippleTheme

Procedi in uno dei seguenti modi:

  • Usa le API Material Library RippleConfiguration oppure
  • Crea il tuo sistema di progettazione per l'implementazione degli onde

Questa pagina descrive l'impatto delle modifiche del comportamento e le istruzioni per eseguire la migrazione a le nuove API.

Cambiamento del comportamento

Le seguenti versioni della libreria includono una modifica del comportamento dell'eco:

  • 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(); invece, usano le nuove API Ripple. Di conseguenza, non eseguono query su LocalRippleTheme. Pertanto, se imposti LocalRippleTheme nella tua applicazione, Materiale non utilizzeranno questi valori.

La seguente sezione descrive come tornare temporaneamente al vecchio comportamento senza eseguire la migrazione; tuttavia, consigliamo di eseguire la migrazione alle nuove API. Per istruzioni per la migrazione, vedi Eseguire la migrazione da rememberRipple a ripple e nelle sezioni successive.

Esegui l'upgrade della versione della libreria di materiali senza eseguire la migrazione

Per sbloccare l'upgrade delle versioni della libreria, puoi utilizzare la LocalUseFallbackRippleImplementation CompositionLocal API da configurare Componenti materiali su cui basare il vecchio comportamento:

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

Assicurati di fornire questo codice al di fuori di MaterialTheme in modo che le vecchie onde possano essere fornito tramite LocalIndication.

Le sezioni seguenti descrivono come eseguire la migrazione alle nuove API.

Esegui la migrazione da rememberRipple a ripple

Uso di una libreria di materiali

Se utilizzi una libreria di materiali, sostituisci direttamente rememberRipple() con un chiamata a ripple() dalla libreria corrispondente. Questa API crea un'eco utilizzando valori derivati dalle API Material Tema. Quindi, passa il token a Modifier.clickable e/o ad altri componenti.

Ad esempio, il seguente snippet utilizza le API ritirate:

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

Devi modificare lo snippet riportato sopra per:

@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 ricordato. Può essere riutilizzato anche in più componenti, come modificatori, quindi considera la possibilità di estrarre la creazione dell'onda in un valore di primo livello salvare le allocazioni.

Implementazione di un sistema di progettazione personalizzato

Se implementi un tuo sistema di progettazione e in precedenza utilizzavi rememberRipple() insieme a un RippleTheme personalizzato per configurare l'eco, devi invece fornire la tua API Ripple che delega al nodo Ripple API esposte in material-ripple. I componenti possono quindi utilizzare la tua eco che utilizza direttamente i valori del tuo tema. Per ulteriori informazioni, consulta Eseguire la migrazione da RippleTheme.

Esegui la migrazione da RippleTheme

Disattiva temporaneamente la modifica del comportamento

Le librerie di materiali hanno un CompositionLocal temporaneo, LocalUseFallbackRippleImplementation, che puoi usare per configurare tutte Componenti dei materiali su cui ricorrere a rememberRipple. In questo modo rememberRipple continua a eseguire la query su LocalRippleTheme.

Il seguente snippet di codice illustra come utilizzare il parametro API LocalUseFallbackRippleImplementation CompositionLocal:

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

Se utilizzi un tema personalizzato dell'app basato su Material, puoi fornire in modo sicuro la composizione locale come parte del tema della tua app:

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

Per ulteriori informazioni, consulta la sezione Eseguire l'upgrade della versione della libreria di materiali senza migrazione.

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 increspature in un sottoalbero. Tieni presente che RippleConfiguration e I LocalRippleConfiguration sono sperimentali e destinati esclusivamente all'utilizzo a livello di singolo componente personalizzazione. La personalizzazione globale/a livello di tema non è supportata con questi le API consulta Utilizzare RippleTheme per modificare a livello globale tutte le eco in una applicazione per ulteriori informazioni sul caso d'uso specifico.

Ad esempio, il seguente snippet utilizza le API ritirate:

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 per:

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

Utilizzo di RippleTheme per modificare il colore/alfa di un'ondata per un determinato componente

Come descritto nella sezione precedente, RippleConfiguration e LocalRippleConfiguration sono API sperimentali e sono destinate esclusivamente alla la personalizzazione per componente.

Ad esempio, il seguente snippet utilizza le API ritirate:

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 per:

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

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

Utilizzo di RippleTheme per modificare a livello globale tutti gli echi in un'applicazione

In precedenza, potevi utilizzare LocalRippleTheme per definire il comportamento dell'eco in un a livello di tema. Si trattava essenzialmente di un punto di integrazione tra di progettazione del sistema di produzione locali ed eco. Invece di esporre un generico a tema, material-ripple ora espone una createRippleModifierNode() personalizzata. Questa funzione consente alle librerie del sistema di progettazione di creare dell'implementazione wrapper, che eseguono query sui valori del tema e poi delegano l'implementazione di ripple nel nodo creato da questa funzione.

Ciò consente ai sistemi di progettazione di interrogare direttamente ciò di cui hanno bisogno ed esporre qualsiasi richiesti livelli tematici configurabili dall'utente nella parte superiore senza doversi conformare ciò che viene fornito al livello material-ripple. Questa modifica aumenta esplicitamente a quale tema/specifica si adatti l'eco, in quanto sulla stessa API Ripple, che definisce il contratto, anziché essere implicitamente derivati dal tema.

Per indicazioni, consulta l'implementazione dell'API Ripple in Material biblioteche e sostituire le richieste di assistenza ai canali di composizione Material secondo necessità il tuo sistema di progettazione.

Esegui la migrazione da Indication a IndicationNodeFactory

Intorno a Indication

Se stai solo creando un Indication da ignorare, ad esempio se stai creando un onde per passare a Modifier.clickable o Modifier.indication, devi apportare modifiche. IndicationNodeFactory eredita da Indication, in modo che tutto continui a essere compilato e a funzionare.

Creazione di Indication in corso...

Se stai creando la tua implementazione di Indication, la migrazione dovrebbe essere semplici nella maggior parte dei casi. Ad esempio, considera un valore Indication che applica una effetto scala 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:

  1. Esegui la migrazione di ScaleIndicationInstance in DrawModifierNode. La piattaforma API per DrawModifierNode è molto simile a IndicationInstance: espone una funzione ContentDrawScope#draw() che è funzionalmente equivalente a IndicationInstance#drawContent(). Devi modificare questa funzione implementare la logica collectLatest direttamente all'interno del nodo, anziché Indication.

    Ad esempio, il seguente snippet utilizza le API ritirate:

    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 per:

    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. Esegui la migrazione di ScaleIndication per implementare IndicationNodeFactory. Poiché la logica di raccolta dei dati è stata spostata nel nodo, la cui unica responsabilità è creare un'istanza del nodo.

    Ad esempio, il seguente snippet utilizza le API ritirate:

    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 per:

    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 di strumento di authoring. Tuttavia, nel raro caso in cui crei manualmente un IndicationInstance utilizzando rememberUpdatedInstance, devi aggiornare implementazione per verificare se Indication è un IndicationNodeFactory, quindi un'implementazione più semplice. Ad esempio, Modifier.indication delega internamente al nodo creato se si tratta di un IndicationNodeFactory. Se non, utilizzerà Modifier.composed per chiamare rememberUpdatedInstance.