Zur Verbesserung der Kompositionsleistung interaktiver Komponenten, die Modifier.clickable
verwenden, haben wir neue APIs eingeführt. Diese APIs ermöglichen effizientere Indication
-Implementierungen, z. B. Ripples.
androidx.compose.foundation:foundation:1.7.0+
und androidx.compose.material:material-ripple:1.7.0+
enthalten die folgenden API-Änderungen:
Eingestellt |
Ersatz |
---|---|
|
|
|
Neue Hinweis: In diesem Zusammenhang bezieht sich „Material-Bibliotheken“ auf |
|
Entweder:
|
Auf dieser Seite werden die Auswirkungen der Verhaltensänderung und die Anleitung für die Migration zu den neuen APIs beschrieben.
Verhaltensänderung
Die folgenden Bibliotheksversionen enthalten 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+
In diesen Versionen von Material-Bibliotheken wird rememberRipple()
nicht mehr verwendet, sondern die neuen Ripple-APIs. Daher wird LocalRippleTheme
nicht abgefragt.
Wenn Sie LocalRippleTheme
in Ihrer Anwendung festlegen, werden diese Werte nicht von Material-Komponenten verwendet.
In den folgenden Abschnitten wird beschrieben, wie Sie zu den neuen APIs migrieren.
Von rememberRipple
zu ripple
migrieren
Materialbibliothek verwenden
Wenn Sie eine Material-Bibliothek verwenden, ersetzen Sie rememberRipple()
direkt durch einen Aufruf von ripple()
aus der entsprechenden Bibliothek. Mit dieser API wird ein Ripple-Effekt mit Werten erstellt, die von den Material-Theme-APIs abgeleitet werden. Übergeben Sie das zurückgegebene Objekt dann an Modifier.clickable
und/oder andere Komponenten.
Im folgenden Snippet werden beispielsweise die verworfenen APIs verwendet:
Box( Modifier.clickable( onClick = {}, interactionSource = remember { MutableInteractionSource() }, indication = rememberRipple() ) ) { // ... }
Sie sollten das obige Snippet so ä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. Sie kann auch für mehrere Komponenten wiederverwendet werden, ähnlich wie Modifikatoren. Sie sollten daher die Erstellung des Ripples in einen Wert auf oberster Ebene auslagern, um Zuweisungen zu sparen.
Benutzerdefiniertes Designsystem implementieren
Wenn Sie Ihr eigenes Designsystem implementieren und zuvor rememberRipple()
zusammen mit einem benutzerdefinierten RippleTheme
verwendet haben, um den Ripple-Effekt zu konfigurieren, sollten Sie stattdessen Ihre eigene Ripple-API bereitstellen, die an die in material-ripple
bereitgestellten Ripple-Knoten-APIs delegiert. Ihre Komponenten können dann Ihre eigene Ripple-Implementierung verwenden, die Ihre Themenwerte direkt nutzt. Weitere Informationen finden Sie unter Von RippleTheme
migrieren.
Von RippleTheme
migrieren
RippleTheme
verwenden, um einen Rippel-Effekt für eine bestimmte Komponente zu deaktivieren
Die Bibliotheken material
und material3
stellen RippleConfiguration
und LocalRippleConfiguration
zur Verfügung, mit denen Sie das Erscheinungsbild von Ripples in einem Unterbaum konfigurieren können. RippleConfiguration
und LocalRippleConfiguration
sind experimentell und nur für die Anpassung einzelner Komponenten vorgesehen. Globale oder themenweite Anpassungen werden von diesen APIs nicht unterstützt. Weitere Informationen zu diesem Anwendungsfall finden Sie unter RippleTheme
verwenden, um alle Ripples in einer Anwendung global zu ändern.
Im folgenden Snippet werden beispielsweise die verworfenen 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 so ändern:
CompositionLocalProvider(LocalRippleConfiguration provides null) { Button { // ... } }
Mit RippleTheme
die Farbe/den Alphawert einer Wellenanimation für eine bestimmte Komponente ändern
Wie im vorherigen Abschnitt beschrieben, sind RippleConfiguration
und LocalRippleConfiguration
experimentelle APIs, die nur für die Anpassung einzelner Komponenten vorgesehen sind.
Im folgenden Snippet werden beispielsweise die verworfenen 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 so ändern:
@OptIn(ExperimentalMaterialApi::class) private val MyRippleConfiguration = RippleConfiguration(color = Color.Red, rippleAlpha = MyRippleAlpha) // ... CompositionLocalProvider(LocalRippleConfiguration provides MyRippleConfiguration) { Button { // ... } }
RippleTheme
verwenden, um alle Ripples in einer Anwendung global zu ändern
Bisher konnten Sie LocalRippleTheme
verwenden, um das Ripple-Verhalten auf Theme-Ebene zu definieren. Dies war im Grunde ein Integrationspunkt zwischen benutzerdefinierten lokalen Kompositionsvariablen für das Designsystem und Ripple. Anstelle eines generischen Theming-Primitivs wird in material-ripple
jetzt eine createRippleModifierNode()
-Funktion bereitgestellt. Diese Funktion ermöglicht es, in Designsystembibliotheken eine wrapper
-Implementierung höherer Ordnung zu erstellen, die ihre Themenwerte abfragt und die Ripple-Implementierung dann an den von dieser Funktion erstellten Knoten delegiert.
So können Designsysteme direkt abfragen, was sie benötigen, und alle erforderlichen benutzerkonfigurierbaren Theming-Ebenen darüber bereitstellen, ohne sich an die Vorgaben der material-ripple
-Ebene halten zu müssen. Durch diese Änderung wird auch deutlicher, welchem Theme bzw. welcher Spezifikation die Wellenanimation entspricht, da der Vertrag durch die Ripple API selbst definiert wird und nicht implizit vom Theme abgeleitet wird.
Eine Anleitung dazu finden Sie in der Implementierung der Ripple API in den Material-Bibliotheken. Ersetzen Sie die Aufrufe der lokalen Material-Komposition nach Bedarf für Ihr eigenes Designsystem.
Von Indication
zu IndicationNodeFactory
migrieren
Weitergabe von Indication
Wenn Sie nur ein Indication
erstellen, um es weiterzugeben, z. B. um es an Modifier.clickable
oder Modifier.indication
zu übergeben, müssen Sie keine Änderungen vornehmen. IndicationNodeFactory
erbt von Indication
, daher wird alles weiterhin kompiliert und funktioniert.
Indication
wird erstellt
Wenn Sie Ihre eigene Indication
-Implementierung erstellen, sollte die Migration in den meisten Fällen einfach sein. Ein Beispiel für eine Indication
, die einen Skalierungseffekt auf die Presse 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() } } }
Die Migration kann in zwei Schritten erfolgen:
Migrieren Sie
ScaleIndicationInstance
zu einemDrawModifierNode
. Die API-Oberfläche fürDrawModifierNode
ähnelt sehr der vonIndicationInstance
: Sie stellt eineContentDrawScope#draw()
-Funktion bereit, die funktional mitIndicationInstance#drawContent()
identisch ist. Sie müssen diese Funktion ändern und dann diecollectLatest
-Logik direkt im Knoten anstelle vonIndication
implementieren.Im folgenden Snippet werden beispielsweise die verworfenen 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 so ä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() } } }
Migrieren Sie
ScaleIndication
, umIndicationNodeFactory
zu implementieren. Da die Erfassungslogik jetzt in den Knoten verschoben wird, ist dies ein sehr einfaches Fabrikobjekt, dessen einzige Aufgabe darin besteht, eine Knoteninstanz zu erstellen.Im folgenden Snippet werden beispielsweise die verworfenen 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 so ä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 anzuzeigen. Wenn Sie jedoch in dem seltenen Fall, dass Sie manuell ein IndicationInstance
mit rememberUpdatedInstance
erstellen, Ihre Implementierung aktualisieren müssen, um zu prüfen, ob das Indication
ein IndicationNodeFactory
ist, damit Sie eine einfachere Implementierung verwenden können. Wenn Modifier.indication
beispielsweise ein IndicationNodeFactory
ist, wird intern an den erstellten Knoten delegiert. Andernfalls wird Modifier.composed
verwendet, um rememberUpdatedInstance
aufzurufen.