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
ScaleIndicationInstancezu 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 vonIndicationimplementieren.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, umIndicationNodeFactoryzu 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.