Compose bietet viele Modifikatoren für gängige Verhaltensweisen, Sie können aber auch eigene benutzerdefinierte Modifikatoren erstellen.
Modifikatoren bestehen aus mehreren Teilen:
- Eine Modifikatorfabrik
<ph type="x-smartling-placeholder">
- </ph>
- Dies ist eine Erweiterungsfunktion für
Modifier
, die eine idiomatische API bereitstellt. für Ihren Modifikator ein und ermöglicht die einfache Verkettung von Modifikatoren. Die Modifikator-Factory erzeugt die Modifikatorelemente, die von Compose zum Ändern Ihrer Benutzeroberfläche.
- Dies ist eine Erweiterungsfunktion für
- Modifikatorelement
<ph type="x-smartling-placeholder">
- </ph>
- Hier können Sie das Verhalten des Modifikators implementieren.
Es gibt mehrere Möglichkeiten, einen benutzerdefinierten Modifizierer zu implementieren, je nachdem,
Funktionalität erforderlich sind. Häufig lässt sich ein benutzerdefinierter Modifizierer am einfachsten
um eine benutzerdefinierte Modifikator-Factory zu implementieren,
die andere bereits definierte Modifikatoren kombiniert.
Modifikator-Faktoren zu kombinieren. Wenn Sie benutzerdefiniertes Verhalten benötigen, implementieren Sie den
Modifikatorelement verwenden. Diese Modifier.Node
APIs befinden sich auf einer niedrigeren Ebene,
bieten mehr Flexibilität.
Vorhandene Modifikatoren verketten
Häufig ist es möglich, benutzerdefinierte Modifizierer allein durch die Verwendung vorhandener
Modifizierern. Beispielsweise wird Modifier.clip()
mithilfe des
graphicsLayer
-Modifikator. Bei dieser Strategie werden vorhandene Modifikatorelemente verwendet.
Ihre eigene Factory mit benutzerdefinierten Modifikatoren bereitstellen.
Bevor Sie einen eigenen benutzerdefinierten Modifizierer implementieren, prüfen Sie, ob Sie denselben .
fun Modifier.clip(shape: Shape) = graphicsLayer(shape = shape, clip = true)
Wenn Sie feststellen, dass Sie dieselbe Gruppe von Modifizierern häufig wiederholen, können Sie in eigene Modifikatoren einfügen:
fun Modifier.myBackground(color: Color) = padding(16.dp) .clip(RoundedCornerShape(8.dp)) .background(color)
Benutzerdefinierten Modifikator mithilfe einer Factory zusammensetzbaren Modifikatoren erstellen
Sie können auch einen benutzerdefinierten Modifikator mit einer zusammensetzbaren Funktion erstellen, um Werte zu übergeben. mit einem vorhandenen Modifikator. Dies wird als zusammensetzbare Modifikatorfabrik bezeichnet.
Die Verwendung einer zusammensetzbaren Modifikator-Factory zum Erstellen eines Modifikators ermöglicht auch die Verwendung von
übergeordnete Editoren-APIs, z. B. animate*AsState
und andere Compose-APIs
zustandsorientierte Animations-APIs. Das folgende Snippet zeigt beispielsweise
Modifikator, der bei Aktivierung bzw. Deaktivierung eine Alpha-Änderung animiert:
@Composable fun Modifier.fade(enable: Boolean): Modifier { val alpha by animateFloatAsState(if (enable) 0.5f else 1.0f) return this then Modifier.graphicsLayer { this.alpha = alpha } }
Wenn Ihr benutzerdefinierter Modifikator die bequeme Methode zur Bereitstellung von Standardwerten aus einem
CompositionLocal
lässt sich dies am einfachsten mit einer zusammensetzbaren Funktion implementieren
Modifikator-Factory:
@Composable fun Modifier.fadedBackground(): Modifier { val color = LocalContentColor.current return this then Modifier.background(color.copy(alpha = 0.5f)) }
Dieser Ansatz hat einige Einschränkungen, die im Folgenden beschrieben werden.
CompositionLocal
-Werte werden auf der Aufrufseite der Modifikatorfabrik aufgelöst
Wenn Sie einen benutzerdefinierten Modifizierer mit einer Factory mit zusammensetzbaren Modifikatoren erstellen, Die Einheimischen berücksichtigen den Wert aus dem Kompositionsbaum, in dem sie erstellt wurden, verwendet. Dies kann zu unerwarteten Ergebnissen führen. Nehmen wir zum Beispiel die Komposition Beispiel für einen lokalen Modifikator (siehe oben), der etwas anders implementiert wurde mit einem zusammensetzbare Funktion:
@Composable fun Modifier.myBackground(): Modifier { val color = LocalContentColor.current return this then Modifier.background(color.copy(alpha = 0.5f)) } @Composable fun MyScreen() { CompositionLocalProvider(LocalContentColor provides Color.Green) { // Background modifier created with green background val backgroundModifier = Modifier.myBackground() // LocalContentColor updated to red CompositionLocalProvider(LocalContentColor provides Color.Red) { // Box will have green background, not red as expected. Box(modifier = backgroundModifier) } } }
Wenn der Modifikator nicht so funktioniert, verwenden Sie ein benutzerdefiniertes
Modifier.Node
, da lokale Kompositionen
am Einsatzort korrekt beseitigt und kann sicher hochgezogen werden.
Zusammensetzbare Funktionsmodifikatoren werden nie übersprungen
Zusammensetzbare Factory-Modifikatoren werden niemals übersprungen, da zusammensetzbare Funktionen mit Rückgabewerten können nicht übersprungen werden. Das bedeutet, dass Ihre Modifikatorfunktion bei jeder Neuzusammensetzung aufgerufen, was teuer sein kann, wenn sie häufig auftreten.
Modifikatoren für zusammensetzbare Funktionen müssen innerhalb einer zusammensetzbaren Funktion aufgerufen werden
Wie alle zusammensetzbaren Funktionen muss ein zusammensetzbarer Factory-Modifikator aus der innerhalb der Komposition. Dadurch wird begrenzt, wohin ein Modifikator so hochgezogen werden kann, dürfen niemals aus der Komposition gezogen werden. Nicht zusammensetzbare Modifizierer Fabriken können aus zusammensetzbaren Funktionen gezogen werden, um die Wiederverwendung zu vereinfachen und Leistung verbessern:
val extractedModifier = Modifier.background(Color.Red) // Hoisted to save allocations @Composable fun Modifier.composableModifier(): Modifier { val color = LocalContentColor.current.copy(alpha = 0.5f) return this then Modifier.background(color) } @Composable fun MyComposable() { val composedModifier = Modifier.composableModifier() // Cannot be extracted any higher }
Verhalten benutzerdefinierter Modifikatoren mit Modifier.Node
implementieren
Modifier.Node
ist eine untergeordnete API zum Erstellen von Modifikatoren in Compose. Es
ist dieselbe API, in der Compose seine eigenen Modifikatoren implementiert, und die am stärksten
benutzerdefinierte Modifikatoren erstellen.
Benutzerdefinierten Modifikator mit Modifier.Node
implementieren
Die Implementierung eines benutzerdefinierten Modifikators mithilfe von Modifier.Node besteht aus drei Teilen:
- Eine
Modifier.Node
-Implementierung, die die Logik und Status des Modifikators. - Ein
ModifierNodeElement
, das Modifikatoren erstellt und aktualisiert Knoteninstanzen. - Eine optionale Modifikator-Factory wie oben beschrieben.
ModifierNodeElement
Klassen sind zustandslos und jede neue Instanz wird zugewiesen
Zusammensetzung, während Modifier.Node
-Klassen zustandsorientiert sein können und
für mehrere Neuzusammensetzungen
verwendet und sogar wiederverwendet werden können.
Der folgende Abschnitt beschreibt die einzelnen Teile und zeigt ein Beispiel für den Aufbau einer benutzerdefinierten Modifikator, um einen Kreis zu zeichnen.
Modifier.Node
Mit der Modifier.Node
-Implementierung (in diesem Beispiel CircleNode
) wird
die
des benutzerdefinierten Modifikators.
// Modifier.Node private class CircleNode(var color: Color) : DrawModifierNode, Modifier.Node() { override fun ContentDrawScope.draw() { drawCircle(color) } }
In diesem Beispiel wird der Kreis mit der an den Modifikator übergebenen Farbe gezeichnet. .
Ein Knoten implementiert Modifier.Node
sowie null oder mehr Knotentypen. Es gibt
unterschiedliche Knotentypen erstellen, je nachdem, welche Funktionalität der Modifikator benötigt. Die
im Beispiel oben muss gezeichnet werden können. Daher wird DrawModifierNode
implementiert,
ermöglicht das Überschreiben der Zeichenmethode.
Folgende Typen sind verfügbar:
Knoten |
Verwendung |
Beispiellink |
Ein |
||
Ein |
||
Durch die Implementierung dieser Schnittstelle kann dein |
||
Ein |
||
Ein |
||
Ein |
||
Ein |
||
Ein |
||
|
||
Ein Dies kann nützlich sein, um mehrere Knotenimplementierungen in einer zusammenzufassen. |
||
Erlaubt |
Knoten werden automatisch ungültig gemacht, wenn ein Update auf dem entsprechenden Knoten aufgerufen wird.
-Elements. Da es sich bei unserem Beispiel um eine DrawModifierNode
handelt, wird jedes Mal ein Update am
das Element enthält, löst der Knoten eine Neuzeichnung aus und seine Farbe wird korrekt aktualisiert. Es ist
Möglichkeit, die automatische Entwertung zu deaktivieren, wie unten beschrieben.
ModifierNodeElement
Eine ModifierNodeElement
ist eine unveränderliche Klasse, die die zu erstellenden oder zu erstellenden Daten enthält.
aktualisieren Sie den benutzerdefinierten Modifikator:
// ModifierNodeElement private data class CircleElement(val color: Color) : ModifierNodeElement<CircleNode>() { override fun create() = CircleNode(color) override fun update(node: CircleNode) { node.color = color } }
ModifierNodeElement
-Implementierungen müssen die folgenden Methoden überschreiben:
create
: Dies ist die Funktion, die den Modifikatorknoten instanziiert. Dadurch erhalten Sie wird aufgerufen, um den Knoten zu erstellen, wenn Ihr Modifikator zum ersten Mal angewendet wird. Normalerweise den Knoten zu erstellen und mit den Parametern zu konfigurieren, an die Modifikatorfabrik übergeben wurden.update
: Diese Funktion wird immer dann aufgerufen, wenn dieser Modifikator im Dieser Knoten ist bereits vorhanden, aber eine Eigenschaft hat sich geändert. Dies ist derequals
-Methode der Klasse bestimmt. Der Modifikatorknoten, der wird als Parameter an denupdate
-Aufruf gesendet. An dieser Stelle sollten Sie den Wert der Knoten Eigenschaften, die den aktualisierten Parameter. Knoten können auf diese Weise wiederverwendet werden, die Leistungssteigerung durchModifier.Node
; Daher müssen Sie die vorhandenen Knoten zu erstellen, anstatt einen neuen Knoten in der Methodeupdate
zu erstellen. In unserem Kreis hinzugefügt, wird die Farbe des Knotens aktualisiert.
Außerdem muss bei ModifierNodeElement
-Implementierungen equals
implementiert werden.
und hashCode
. update
wird nur aufgerufen, wenn ein Gleichheitsvergleich mit dem
Das vorherige Element gibt false zurück.
Im obigen Beispiel wird dies mithilfe einer Datenklasse erreicht. Diese Methoden dienen dazu,
ob ein Knoten aktualisiert werden muss. Wenn Ihr Element Eigenschaften aufweist, die
tragen nicht dazu bei, ob ein Knoten aktualisiert werden muss oder Sie Daten vermeiden möchten.
aus Gründen der binären Kompatibilität, dann können Sie equals
manuell implementieren
und hashCode
z.B. das Element mit dem Modifikator für den Innenrand.
Modifikatorfabrik
Dies ist die öffentliche API-Oberfläche des Modifikators. Die meisten Implementierungen das Modifikatorelement erstellen und zur Modifikatorkette hinzufügen:
// Modifier factory fun Modifier.circle(color: Color) = this then CircleElement(color)
Vollständiges Beispiel
Diese drei Teile ergeben den benutzerdefinierten Modifikator zum Zeichnen eines Kreises.
mithilfe der Modifier.Node
APIs:
// Modifier factory fun Modifier.circle(color: Color) = this then CircleElement(color) // ModifierNodeElement private data class CircleElement(val color: Color) : ModifierNodeElement<CircleNode>() { override fun create() = CircleNode(color) override fun update(node: CircleNode) { node.color = color } } // Modifier.Node private class CircleNode(var color: Color) : DrawModifierNode, Modifier.Node() { override fun ContentDrawScope.draw() { drawCircle(color) } }
Häufige Situationen bei der Verwendung von Modifier.Node
Wenn Sie benutzerdefinierte Modifikatoren mit Modifier.Node
erstellen,
können folgende Situationen auftreten,
begegnen können.
Nullparameter
Wenn Ihr Modifikator keine Parameter hat, muss er nie aktualisiert werden. muss keine Datenklasse sein. Hier sehen Sie eine Beispielimplementierung eines Modifikators, mit dem ein fester Abstand auf eine zusammensetzbare Funktion angewendet wird:
fun Modifier.fixedPadding() = this then FixedPaddingElement data object FixedPaddingElement : ModifierNodeElement<FixedPaddingNode>() { override fun create() = FixedPaddingNode() override fun update(node: FixedPaddingNode) {} } class FixedPaddingNode : LayoutModifierNode, Modifier.Node() { private val PADDING = 16.dp override fun MeasureScope.measure( measurable: Measurable, constraints: Constraints ): MeasureResult { val paddingPx = PADDING.roundToPx() val horizontal = paddingPx * 2 val vertical = paddingPx * 2 val placeable = measurable.measure(constraints.offset(-horizontal, -vertical)) val width = constraints.constrainWidth(placeable.width + horizontal) val height = constraints.constrainHeight(placeable.height + vertical) return layout(width, height) { placeable.place(paddingPx, paddingPx) } } }
Auf lokale Kompositionen verweisen
Modifier.Node
-Modifikatoren berücksichtigen Änderungen am Erstellungsstatus nicht automatisch
Objekte wie CompositionLocal
. Die Vorteile von Modifier.Node
-Modifikatoren gegenüber
Modifikatoren, die gerade mit einer zusammensetzbaren
Factory erstellt werden, sind, dass sie
Wert der lokalen Zusammensetzung, von der aus der Modifikator in Ihrer Benutzeroberfläche verwendet wird
Baumstruktur dargestellt, nicht dort, wo der Modifikator zugewiesen ist. Verwenden Sie dazu currentValueOf
.
Instanzen mit Modifikatorknoten berücksichtigen jedoch Statusänderungen nicht automatisch. Bis automatisch auf lokale Änderungen der Komposition reagiert, innerhalb eines Bereichs:
DrawModifierNode
:ContentDrawScope
LayoutModifierNode
:MeasureScope
undIntrinsicMeasureScope
SemanticsModifierNode
:SemanticsPropertyReceiver
In diesem Beispiel wird der Wert von LocalContentColor
beobachtet, um einen hintergrundbasierten
an seiner Farbe. Bei der Beobachtung von Snapshot-Änderungen durch ContentDrawScope
Wird automatisch neu gezeichnet, wenn sich der Wert von LocalContentColor
ändert:
class BackgroundColorConsumerNode : Modifier.Node(), DrawModifierNode, CompositionLocalConsumerModifierNode { override fun ContentDrawScope.draw() { val currentColor = currentValueOf(LocalContentColor) drawRect(color = currentColor) drawContent() } }
Um auf Statusänderungen außerhalb eines Bereichs zu reagieren und Ihre
verwenden möchten, verwenden Sie ObserverModifierNode
.
In Modifier.scrollable
wird dieses Verfahren beispielsweise verwendet, um
Veränderungen bei LocalDensity
beobachten. Hier ein vereinfachtes Beispiel:
class ScrollableNode : Modifier.Node(), ObserverModifierNode, CompositionLocalConsumerModifierNode { // Place holder fling behavior, we'll initialize it when the density is available. val defaultFlingBehavior = DefaultFlingBehavior(splineBasedDecay(UnityDensity)) override fun onAttach() { updateDefaultFlingBehavior() observeReads { currentValueOf(LocalDensity) } // monitor change in Density } override fun onObservedReadsChanged() { // if density changes, update the default fling behavior. updateDefaultFlingBehavior() } private fun updateDefaultFlingBehavior() { val density = currentValueOf(LocalDensity) defaultFlingBehavior.flingDecay = splineBasedDecay(density) } }
Modifikator wird animiert
Implementierungen von Modifier.Node
haben Zugriff auf ein coroutineScope
. So können Sie
Compose Animatable APIs verwenden. Mit diesem Snippet wird beispielsweise die
CircleNode
von oben zum wiederholten Ein- und Ausblenden:
class CircleNode(var color: Color) : Modifier.Node(), DrawModifierNode { private val alpha = Animatable(1f) override fun ContentDrawScope.draw() { drawCircle(color = color, alpha = alpha.value) drawContent() } override fun onAttach() { coroutineScope.launch { alpha.animateTo( 0f, infiniteRepeatable(tween(1000), RepeatMode.Reverse) ) { } } } }
Freigabe des Status zwischen Modifikatoren durch Delegierung
Modifier.Node
-Modifikatoren können an andere Knoten delegieren. Es gibt viele Anwendungsfälle,
Dazu gehört das Extrahieren gängiger Implementierungen
für verschiedene Modifikatoren,
aber es kann auch verwendet werden,
um einen gemeinsamen Status für alle Modifikatoren zu erhalten.
Eine einfache Implementierung eines anklickbaren Modifikatorknotens, der Interaktionsdaten:
class ClickableNode : DelegatingNode() { val interactionData = InteractionData() val focusableNode = delegate( FocusableNode(interactionData) ) val indicationNode = delegate( IndicationNode(interactionData) ) }
Automatische Knotenentwertung deaktivieren
Modifier.Node
Knoten werden automatisch entwertet, wenn ihre entsprechenden
Aktualisierung der ModifierNodeElement
-Aufrufe. Manchmal können Sie bei einem komplexeren Modifikator
dieses Verhalten deaktivieren möchten, um genauer steuern zu können, wann
dass Ihr Modifizierer
Phasen ungültig macht.
Dies ist besonders nützlich, wenn Ihr benutzerdefinierter Modifizierer sowohl das Layout
Zeichnen. Wenn Sie die automatische Entwertung deaktivieren, können Sie die Zeichnung entwerten, wenn
nur zeichnungsbezogene Eigenschaften wie color
ändern und das Layout nicht ungültig machen.
Dadurch kann die Leistung des Modifizierers verbessert werden.
Ein hypothetisches Beispiel dafür finden Sie unten mit einem Modifikator mit einem color
,
size
und onClick
Lambda als Properties. Durch diesen Modifikator wird nur das ungültig
erforderlich. Dabei werden alle Entwertungen übersprungen, die den folgenden Kriterien entsprechen:
class SampleInvalidatingNode( var color: Color, var size: IntSize, var onClick: () -> Unit ) : DelegatingNode(), LayoutModifierNode, DrawModifierNode { override val shouldAutoInvalidate: Boolean get() = false private val clickableNode = delegate( ClickablePointerInputNode(onClick) ) fun update(color: Color, size: IntSize, onClick: () -> Unit) { if (this.color != color) { this.color = color // Only invalidate draw when color changes invalidateDraw() } if (this.size != size) { this.size = size // Only invalidate layout when size changes invalidateMeasurement() } // If only onClick changes, we don't need to invalidate anything clickableNode.update(onClick) } override fun ContentDrawScope.draw() { drawRect(color) } override fun MeasureScope.measure( measurable: Measurable, constraints: Constraints ): MeasureResult { val size = constraints.constrain(size) val placeable = measurable.measure(constraints) return layout(size.width, size.height) { placeable.place(0, 0) } } }