Zur Verbesserung der Zusammensetzungsleistung von interaktiven Komponenten, die Modifier.clickable
verwenden, haben wir neue APIs eingeführt. Diese APIs ermöglichen effizientere Indication
-Implementierungen wie Ripples.
androidx.compose.foundation:foundation:1.7.0+
und androidx.compose.material:material-ripple:1.7.0+
enthalten die folgenden API-Änderungen:
Eingestellt |
Ersatz |
---|---|
|
|
|
Stattdessen werden neue Hinweis: In diesem Kontext bezieht sich „Materialbibliotheken“ auf |
|
Sie haben zwei Möglichkeiten:
|
Auf dieser Seite werden die Auswirkungen der Verhaltensänderung und eine Anleitung für die Migration zu den neuen APIs beschrieben.
Verhaltensänderung
Die folgenden Bibliotheksversionen beinhalten eine Änderung des Ripple-Verhaltens:
androidx.compose.material:material:1.7.0+
androidx.compose.material3:material3:1.3.0+
androidx.wear.compose:compose-material:1.4.0+
Diese Versionen der Materialbibliotheken verwenden nicht mehr rememberRipple()
, sondern die neuen Ripple APIs. Daher wird LocalRippleTheme
nicht abgefragt.
Wenn Sie in Ihrer Anwendung LocalRippleTheme
festlegen, werden diese Werte daher nicht von Materialkomponenten verwendet.
Im folgenden Abschnitt wird beschrieben, wie Sie vorübergehend zum alten Verhalten zurückkehren, ohne zu migrieren. Wir empfehlen jedoch, zu den neuen APIs zu migrieren. Eine Migrationsanleitung finden Sie unter Von rememberRipple
zu ripple
migrieren und in den nachfolgenden Abschnitten.
Version der Material Library ohne Migration aktualisieren
Wenn Sie die Blockierung der Aktualisierung von Bibliotheksversionen aufheben möchten, können Sie mit der temporären LocalUseFallbackRippleImplementation CompositionLocal
API Materialkomponenten so konfigurieren, dass sie auf das alte Verhalten zurückgesetzt werden:
CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) { MaterialTheme { App() } }
Diese muss außerhalb von MaterialTheme
angegeben werden, damit die alten Wellen über LocalIndication
bereitgestellt werden können.
In den folgenden Abschnitten wird die Migration zu den neuen APIs beschrieben.
Von rememberRipple
nach ripple
migrieren
Materialbibliothek verwenden
Wenn Sie eine Material-Bibliothek verwenden, ersetzen Sie rememberRipple()
direkt durch einen Aufruf von ripple()
aus der entsprechenden Bibliothek. Diese API erzeugt eine Welle mit Werten, die aus den Material The APIs abgeleitet sind. Übergeben Sie dann das zurückgegebene Objekt an Modifier.clickable
und/oder andere Komponenten.
Im folgenden Snippet werden beispielsweise die eingestellten APIs verwendet:
Box( Modifier.clickable( onClick = {}, interactionSource = remember { MutableInteractionSource() }, indication = rememberRipple() ) ) { // ... }
Ändern Sie das obige Snippet wie folgt:
@Composable private fun RippleExample() { Box( Modifier.clickable( onClick = {}, interactionSource = remember { MutableInteractionSource() }, indication = ripple() ) ) { // ... } }
ripple()
ist keine zusammensetzbare Funktion mehr und muss nicht mehr berücksichtigt werden. Sie kann ähnlich wie Modifizierer auch für mehrere Komponenten wiederverwendet werden. Sie sollten daher die Erstellung der Wellenform auf einen Wert auf oberster Ebene extrahieren, um Zuweisungen zu speichern.
Implementierung eines benutzerdefinierten Designsystems
Wenn Sie ein eigenes Designsystem implementieren und bisher rememberRipple()
zusammen mit einem benutzerdefinierten RippleTheme
zum Konfigurieren der Wellen verwendet haben, sollten Sie stattdessen Ihre eigene Ripple API bereitstellen, die an die in material-ripple
bereitgestellten Ripple Node APIs delegiert. Anschließend können Ihre Komponenten eine eigene Welle verwenden, die Ihre Themenwerte direkt aufnimmt. Weitere Informationen finden Sie unter Von RippleTheme
migrieren.
Von RippleTheme
migrieren
Verhaltensänderungen vorübergehend deaktivieren
Materialbibliotheken haben eine temporäre CompositionLocal
, LocalUseFallbackRippleImplementation
, mit der Sie alle Materialkomponenten so konfigurieren können, dass sie rememberRipple
verwenden. Auf diese Weise fragt rememberRipple
weiterhin LocalRippleTheme
ab.
Das folgende Code-Snippet zeigt, wie die LocalUseFallbackRippleImplementation CompositionLocal
API verwendet wird:
CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) { MaterialTheme { App() } }
Wenn du ein benutzerdefiniertes App-Design verwendest, das auf Material basiert, kannst du die lokale Komposition sicher als Teil des App-Designs bereitstellen:
@OptIn(ExperimentalMaterialApi::class) @Composable fun MyAppTheme(content: @Composable () -> Unit) { CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) { MaterialTheme(content = content) } }
Weitere Informationen finden Sie im Abschnitt Version der Material Library ohne Migration upgraden.
Mit RippleTheme
die Wellen bei einer bestimmten Komponente deaktivieren
Die Bibliotheken material
und material3
stellen RippleConfiguration
und LocalRippleConfiguration
zur Verfügung. Damit können Sie die Darstellung von Wellen innerhalb einer Unterstruktur konfigurieren. RippleConfiguration
und LocalRippleConfiguration
sind experimentell und nur für die Anpassung pro Komponente vorgesehen. Die globale bzw. themeweite Anpassung wird für diese APIs nicht unterstützt. Weitere Informationen zu diesem Anwendungsfall finden Sie unter Mit RippleTheme
alle Wellen in einer Anwendung global ändern.
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 { // ... } }
Ändern Sie das obige Snippet wie folgt:
@OptIn(ExperimentalMaterialApi::class) private val DisabledRippleConfiguration = RippleConfiguration(isEnabled = false) // ... CompositionLocalProvider(LocalRippleConfiguration provides DisabledRippleConfiguration) { Button { // ... } }
Mit RippleTheme
die Farbe/Alpha der Welle für eine bestimmte Komponente ändern
Wie im vorherigen Abschnitt beschrieben, sind RippleConfiguration
und LocalRippleConfiguration
experimentelle APIs und nur für die Anpassung pro Komponente vorgesehen.
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 { // ... } }
Ändern Sie das obige Snippet wie folgt:
@OptIn(ExperimentalMaterialApi::class) private val MyRippleConfiguration = RippleConfiguration(color = Color.Red, rippleAlpha = MyRippleAlpha) // ... CompositionLocalProvider(LocalRippleConfiguration provides MyRippleConfiguration) { Button { // ... } }
Mit RippleTheme
alle Wellen in einer App global ändern
Bisher konnten Sie mit LocalRippleTheme
das Verhalten der Wellenlinie auf Theme-Ebene definieren. Dies war im Wesentlichen ein Integrationspunkt zwischen benutzerdefinierten Systemzusammensetzungen und Ripple. Anstatt eine generische Themen-Primitive offenzulegen, stellt material-ripple
jetzt eine createRippleModifierNode()
-Funktion zur Verfügung. Mit dieser Funktion können Designsystembibliotheken höhere wrapper
-Implementierungen erstellen, die ihre Themenwerte abfragen und dann die Ripple-Implementierung an den von dieser Funktion erstellten Knoten delegieren.
Auf diese Weise können Designsysteme direkt abfragen, was sie brauchen, und die erforderlichen, vom Nutzer konfigurierbaren Themenebenen darüber bereitstellen, ohne die Anforderungen der material-ripple
-Ebene erfüllen zu müssen. Durch diese Änderung wird auch deutlicher, welchem Thema bzw. welcher Spezifikation die Ripple entspricht, da diese von der Ripple API selbst definiert wird, anstatt implizit vom Thema abgeleitet zu werden.
Eine Anleitung finden Sie unter Ripple API-Implementierung in Materialbibliotheken. Ersetzen Sie die Aufrufe von lokalen Materialkompositionen, die für Ihr eigenes Designsystem erforderlich sind.
Von Indication
nach IndicationNodeFactory
migrieren
Du passierst ca. Indication
Wenn Sie nur ein Indication
zur Weitergabe erstellen, z. B. eine Welle zur Übergabe an Modifier.clickable
oder Modifier.indication
, müssen Sie keine Änderungen vornehmen. IndicationNodeFactory
übernimmt von Indication
, sodass alles weiter kompiliert und funktioniert.
Indication
wird erstellt
Wenn Sie eine eigene Indication
-Implementierung erstellen, sollte die Migration in den meisten Fällen einfach sein. Betrachten Sie beispielsweise ein Indication
, das einen Skaleneffekt auf das Drücken anwendet:
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() } } }
Sie können dies in zwei Schritten migrieren:
Migrieren Sie
ScaleIndicationInstance
zuDrawModifierNode
. Die API-Oberfläche fürDrawModifierNode
ist derIndicationInstance
sehr ähnlich: Sie stellt eineContentDrawScope#draw()
-Funktion bereit, die funktional derIndicationInstance#drawContent()
entspricht. Sie müssen diese Funktion ändern und dann diecollectLatest
-Logik direkt im Knoten anstelle vonIndication
implementieren.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() } } }
Ändern Sie das obige Snippet wie folgt:
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() } } }
Migrieren Sie
ScaleIndication
, umIndicationNodeFactory
zu implementieren. Da die Erfassungslogik jetzt in den Knoten verschoben wird, ist dies ein sehr einfaches Factory-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 } }
Ändern Sie das obige Snippet wie folgt:
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 anzuzeigen. In dem seltenen Fall, dass Sie manuell ein IndicationInstance
mit rememberUpdatedInstance
erstellen, müssen Sie Ihre Implementierung aktualisieren, um zu prüfen, ob Indication
ein IndicationNodeFactory
ist. Dann können Sie eine einfachere Implementierung verwenden. Modifier.indication
delegiert beispielsweise intern an den erstellten Knoten, wenn er ein IndicationNodeFactory
ist. Andernfalls wird Modifier.composed
verwendet, um rememberUpdatedInstance
aufzurufen.