Zu Indication und Ripple APIs migrieren

Zur Verbesserung der Zusammensetzungsleistung interaktiver Komponenten, die Modifier.clickable, wir haben neue APIs eingeführt. Diese APIs ermöglichen mehr effiziente Indication-Implementierungen wie Ripples.

androidx.compose.foundation:foundation:1.7.0+ und androidx.compose.material:material-ripple:1.7.0+ enthalten die folgende API Änderungen:

Eingestellt

Ersatz

Indication#rememberUpdatedInstance

IndicationNodeFactory

rememberRipple()

Stattdessen werden neue ripple()-APIs in Materialbibliotheken bereitgestellt.

Hinweis: In diesem Kontext bezieht sich auf androidx.compose.material:material, androidx.compose.material3:material3, androidx.wear.compose:compose-material und androidx.wear.compose:compose-material3.

RippleTheme

Entweder:

  • Verwenden Sie die RippleConfiguration APIs der Material Library oder
  • Erstellen Sie Ihre eigene Ripple-Implementierung für das Designsystem

Auf dieser Seite werden die Auswirkungen der Verhaltensänderung sowie eine Anleitung für die Migration zu für die neuen APIs.

Verhaltensänderung

Die folgenden Bibliotheksversionen enthalten eine Änderung im Ripple-Verhalten:

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

In diesen Versionen von Material Library wird rememberRipple() nicht mehr verwendet. verwenden Sie stattdessen nutzen sie die neuen Ripple-APIs. Daher wird LocalRippleTheme nicht abgefragt. Wenn Sie also in Ihrer Anwendung LocalRippleTheme festlegen, wird Material Komponenten verwenden diese Werte nicht.

Im folgenden Abschnitt wird beschrieben, wie Sie vorübergehend auf das alte Verhalten zurückgreifen können. ohne zu migrieren; Wir empfehlen jedoch die Migration zu den neuen APIs. Für Eine Migrationsanleitung finden Sie unter Von rememberRipple zu ripple migrieren. und den nachfolgenden Abschnitten.

Version der Material-Bibliothek ohne Migration aktualisieren

Die Blockierung von Upgrades der Bibliotheksversionen aufheben, indem Sie die temporäre Zu konfigurierende LocalUseFallbackRippleImplementation CompositionLocal API Materialkomponenten, die auf das alte Verhalten zurückgreifen:

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

Stellen Sie dies außerhalb von MaterialTheme bereit, damit die alten Wellen über LocalIndication bereitgestellt werden.

In den folgenden Abschnitten wird die Migration zu den neuen APIs beschrieben.

Von rememberRipple zu ripple migrieren

Material-Bibliothek verwenden

Wenn Sie eine Material-Bibliothek verwenden, ersetzen Sie rememberRipple() direkt durch einen ripple() aus der entsprechenden Bibliothek aufrufen. Diese API erstellt eine Welle unter Verwendung von Werten, die aus den Material Theme-APIs abgeleitet wurden. Übergeben Sie dann die zurückgegebene Objekt für Modifier.clickable und/oder andere Komponenten.

Im folgenden Snippet werden beispielsweise die eingestellten APIs verwendet:

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

Sie sollten das obige Snippet wie folgt ändern:

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

ripple() ist keine zusammensetzbare Funktion mehr und muss nicht gespeichert werden. Es kann auch über mehrere Komponenten hinweg wiederverwendet werden, ähnlich wie Modifizierer. Extrahieren Sie daher die Wellenbildung in einen Wert auf oberster Ebene, Zuweisungen speichern.

Implementierung eines kundenspezifischen Designsystems

Wenn Sie Ihr eigenes Designsystem implementieren und zuvor rememberRipple() zusammen mit einem benutzerdefinierten RippleTheme zum Konfigurieren der Verbreitung. sollten Sie stattdessen Ihre eigene Ripple-API bereitstellen, die an den Ripple-Knoten delegiert In material-ripple bereitgestellte APIs. Dann können Ihre Komponenten Ihre eigene Welle verwenden. die direkt auf die Werte Ihres Themas angerechnet wird. Weitere Informationen finden Sie unter Migrieren aus RippleTheme.

Von RippleTheme migrieren

Verhaltensänderung vorübergehend deaktivieren

Materialbibliotheken haben eine temporäre CompositionLocal, LocalUseFallbackRippleImplementation, mit dem Sie alle Materialkomponenten, für die rememberRipple verwendet werden soll. Auf diese Weise rememberRipple fragt weiterhin LocalRippleTheme ab.

Das folgende Code-Snippet zeigt, wie das Tag LocalUseFallbackRippleImplementation CompositionLocal-API:

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

Wenn Sie ein benutzerdefiniertes App-Design verwenden, das auf Material aufbaut, können Sie Stellen Sie die Komposition sicher als Teil des Designs Ihrer App bereit:

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

Weitere Informationen finden Sie unter Upgrade von Material-Bibliothek-Version ohne Migration.

Mit RippleTheme eine Welle für eine bestimmte Komponente deaktivieren

Die Bibliotheken material und material3 machen RippleConfiguration und LocalRippleConfiguration, mit denen Sie die Darstellung von in einer Unterstruktur. Beachten Sie, dass RippleConfiguration und LocalRippleConfiguration sind experimentell und nur für die Verwendung pro Komponente vorgesehen Personalisierung. Die globale/themenweite Anpassung wird hierbei nicht unterstützt. APIs Siehe Mit RippleTheme alle Ripples-Daten global ändern finden Sie weitere Informationen zu diesem Anwendungsfall.

Im folgenden Snippet werden beispielsweise die eingestellten APIs verwendet:

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

Sie sollten das obige Snippet wie folgt ändern:

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

RippleTheme verwenden, um die Farbe/den Alphawert einer Welle für eine bestimmte Komponente zu ändern

Wie im vorherigen Abschnitt beschrieben, werden RippleConfiguration und LocalRippleConfiguration sind experimentelle APIs, die nur für für die einzelnen Komponenten.

Im folgenden Snippet werden beispielsweise die eingestellten APIs verwendet:

private object DisabledRippleThemeColorAndAlpha : RippleTheme {

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

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

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

Sie sollten das obige Snippet wie folgt ändern:

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

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

Mit RippleTheme alle Ripples in einer Anwendung global ändern

Bisher konnten Sie mit LocalRippleTheme das Wellenverhalten an einem für das gesamte Thema gelten. Dies war im Wesentlichen ein Integrationspunkt zwischen benutzerdefinierten Designsystem von lokalen und Wellen. Anstelle eines allgemeinen nach dem thematischen Primitiv zeichnen, stellt material-ripple jetzt eine createRippleModifierNode() . Mit dieser Funktion können Designsystembibliotheken wrapper-Implementierung anzuordnen, die ihre Themenwerte abfragen und dann die Ripple-Implementierung auf dem Knoten, der von dieser Funktion erstellt wird.

So können Designsysteme benötigte Informationen direkt abfragen erforderten vom Nutzer konfigurierbare Design-Ebenen, ohne was in der material-ripple-Ebene bereitgestellt wird. Diese Änderung sorgt außerdem für mehr dem Thema/der Spezifikation der Welle entspricht, Ripple API selbst, die diesen Vertrag definiert, anstatt implizit aus dem Thema abgeleitet wird.

Weitere Informationen findest du in der Ripple API-Implementierung in Material und ersetzen Sie die Aufrufe von Material Ihr eigenes Designsystem nutzen.

Von Indication zu IndicationNodeFactory migrieren

Um Indication zu passieren

Wenn Sie nur ein Indication zur Weitergabe erstellen, also z. B. ein an Modifier.clickable oder Modifier.indication übergeben werden, Änderungen vornehmen müssen. IndicationNodeFactory übernimmt von Indication, sodass alles kompiliert wird und funktioniert.

Indication wird erstellt

Wenn Sie Ihre eigene Indication-Implementierung erstellen, sollte die Migration in den meisten Fällen einfach sein. Stellen Sie sich z. B. einen Indication vor, der Skalierungseffekt beim Drücken:

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

Für die Migration sind zwei Schritte erforderlich:

  1. Migrieren Sie ScaleIndicationInstance zu einer DrawModifierNode. Die API-Oberfläche für DrawModifierNode ist IndicationInstance sehr ähnlich: Sie stellt eine ContentDrawScope#draw(), die funktional äquivalent zu IndicationInstance#drawContent(). Sie müssen diese Funktion ändern implementieren Sie die collectLatest-Logik direkt im Knoten anstelle des Indication.

    Im folgenden Snippet werden beispielsweise die eingestellten APIs verwendet:

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

    Sie sollten das obige Snippet wie folgt ändern:

    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. Migrieren Sie ScaleIndication, um IndicationNodeFactory zu implementieren. Da die wird in den Knoten verschoben. Dies ist eine sehr einfache Fabrik, Objekt, dessen einzige Aufgabe darin besteht, eine Knoteninstanz zu erstellen.

    Im folgenden Snippet werden beispielsweise die eingestellten APIs verwendet:

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

    Sie sollten das obige Snippet wie folgt ändern:

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

IndicationInstance mit Indication erstellen

In den meisten Fällen sollten Sie Modifier.indication verwenden, um Indication für eine Komponente. In dem seltenen Fall, dass Sie manuell eine IndicationInstance verwendet rememberUpdatedInstance, du musst deine implementieren, um zu prüfen, ob Indication ein IndicationNodeFactory ist, sodass Sie können Sie eine leichtere Implementierung verwenden. Beispiel: Modifier.indication wird Delegieren Sie intern an den erstellten Knoten, wenn es sich um einen IndicationNodeFactory handelt. Wenn nicht, wird Modifier.composed verwendet, um rememberUpdatedInstance aufzurufen.