Tworzenie wiadomości od razu udostępnia wiele modyfikatorów typowych działań, ale możesz też tworzyć własne modyfikatory niestandardowe.
Modyfikatory składają się z kilku części:
- Fabryka modyfikatorów
- To jest funkcja rozszerzenia
Modifier
, która udostępnia idiomatyczny interfejs API . Fabryka modyfikatorów tworzy elementy modyfikujące używane przez funkcję Compose do modyfikacji w interfejsie użytkownika.
- To jest funkcja rozszerzenia
- Element modyfikujący
- Tutaj możesz zastosować działanie modyfikatora.
Istnieje wiele sposobów na zastosowanie modyfikatora niestandardowego w zależności od
niezbędną funkcjonalność. Często najłatwiejszym sposobem zastosowania modyfikatora niestandardowego jest
aby wdrożyć fabrykę modyfikatorów niestandardowych, która łączy inne
fabryki modyfikatorów razem. Jeśli potrzebujesz bardziej niestandardowych działań, zaimplementuj funkcję
element modyfikujący za pomocą interfejsów API Modifier.Node
, które są niższego poziomu, ale
które zapewniają większą elastyczność.
Połącz istniejące modyfikatory
Często można utworzyć niestandardowe modyfikatory, wykorzystując istniejące
modyfikatory. Na przykład zaimplementowano Modifier.clip()
za pomocą parametru
Modyfikator graphicsLayer
. Ta strategia korzysta z istniejących elementów modyfikujących, a Ty
udostępnić własną fabrykę modyfikatorów niestandardowych.
Zanim zastosujesz własny modyfikator niestandardowy, sprawdź, czy możesz go użyć strategii ustalania stawek.
fun Modifier.clip(shape: Shape) = graphicsLayer(shape = shape, clip = true)
A jeśli stwierdzisz, że powtarzasz często tę samą grupę modyfikatorów, umieść je we własnym modyfikatorze:
fun Modifier.myBackground(color: Color) = padding(16.dp) .clip(RoundedCornerShape(8.dp)) .background(color)
Tworzenie modyfikatora niestandardowego przy użyciu fabryki modyfikatorów kompozycyjnych
Możesz też utworzyć modyfikator niestandardowy, używając funkcji kompozycyjnej do przekazywania wartości. do istniejącego modyfikatora. Nazywamy to fabryką modyfikatorów kompozycyjnych.
Zastosowanie fabryki modyfikatora kompozycyjnego do utworzenia modyfikatora umożliwia też korzystanie z
interfejsów API do tworzenia wyższego poziomu, takich jak animate*AsState
i inne Compose
interfejsów API animacji opartych na stanie. Na przykład ten fragment kodu zawiera
modyfikator animujący zmianę alfa po włączeniu/wyłączeniu:
@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 } }
Jeśli niestandardowy modyfikator to dogodna metoda podawania wartości domyślnych z
CompositionLocal
. Najłatwiejszym sposobem wdrożenia tego rozwiązania jest użycie funkcji kompozycyjnej
Fabryka modyfikatorów:
@Composable fun Modifier.fadedBackground(): Modifier { val color = LocalContentColor.current return this then Modifier.background(color.copy(alpha = 0.5f)) }
Takie podejście wiąże się z pewnymi zastrzeżeniami, które opisujemy poniżej.
CompositionLocal
wartości zostało rozwiązane w witrynie połączenia z fabryką modyfikatorów
Podczas tworzenia niestandardowego modyfikatora przy użyciu fabryki modyfikatorów kompozycyjnych, kompozycja lokalne pobierają wartość z drzewa kompozycji, w którym zostały utworzone, a nie . Może to prowadzić do nieoczekiwanych rezultatów. Weźmy na przykład kompozycję przykład lokalnego modyfikatora, zaimplementowany nieco inaczej za pomocą funkcja kompozycyjna:
@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) } } }
Jeśli nie tak działa modyfikator, użyj
Modifier.Node
, ponieważ lokalne zasoby kompozycji
zostały rozwiązane prawidłowo w miejscu użytkowania i można je bezpiecznie podnosić.
Kompozycyjne modyfikatory funkcji nie są nigdy pomijane
Kompozycyjne modyfikatory fabryczne nigdy nie są pomijane, ponieważ funkcje kompozycyjne , które zwracają wartości, nie mogą zostać pominięte. Oznacza to, że funkcja modyfikatora będzie wywoływane przy każdej zmianie, co może być drogie, jeśli przekomponuje się często.
Modyfikatory funkcji kompozycyjne muszą być wywoływane w obrębie funkcji kompozycyjnej
Tak jak w przypadku wszystkich funkcji kompozycyjnych, modyfikator fabryczny z możliwością kompozycyjnej musi być wywoływany z w obrębie kompozycji. Ogranicza to miejsce, do którego można podnieść modyfikator, ponieważ nigdy nie zostaną wypchnięte poza kompozycję. Dla porównania: modyfikator niekompozycyjny fabryki można wyciągnąć z funkcji kompozycyjnych, aby ułatwić ponowne Popraw wydajność:
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 }
Zastosuj działanie modyfikatora niestandardowego, korzystając z funkcji Modifier.Node
Modifier.Node
to interfejs API niższego poziomu do tworzenia modyfikatorów w Compose. it
interfejsu API, w którym Compose implementuje własne modyfikatory.
wydajniejszy sposób tworzenia modyfikatorów niestandardowych.
Zastosuj modyfikator niestandardowy, używając funkcji Modifier.Node
Wdrożenie modyfikatora niestandardowego za pomocą modyfikatora.Węzeł składa się z 3 etapów:
- Implementacja
Modifier.Node
, która zawiera logikę stanu modyfikatora. ModifierNodeElement
, który tworzy i aktualizuje modyfikator instancji węzłów.- Opcjonalna fabryka modyfikatorów, jak opisano powyżej.
ModifierNodeElement
klasa jest bezstanowa, a każda z nich jest przydzielana nowe instancje
przekomponowanie, natomiast klasy Modifier.Node
mogą być stanowe i przetrwać
na wiele zmian, a nawet można je wykorzystać ponownie.
W poniższej sekcji opisano każdą z nich i przedstawiamy przykład tworzenia niestandardowy modyfikator, aby narysować okrąg.
Modifier.Node
Implementacja Modifier.Node
(w tym przykładzie CircleNode
) implementuje:
funkcji modyfikatora niestandardowego.
// Modifier.Node private class CircleNode(var color: Color) : DrawModifierNode, Modifier.Node() { override fun ContentDrawScope.draw() { drawCircle(color) } }
W tym przykładzie rysuje ono okrąg z kolorem przekazanym do modyfikatora. .
Węzeł implementuje interfejs Modifier.Node
oraz zero lub więcej typów węzłów. Istnieją
różnych typów węzłów w zależności od funkcji wymaganej przez modyfikator.
z powyższego przykładu musi mieć możliwość rysowania, więc implementuje funkcję DrawModifierNode
, która
umożliwia zastąpienie metody rysowania.
Dostępne typy:
Węzeł |
Wykorzystanie |
Przykładowy link |
Element |
||
Element |
||
Wdrożenie tego interfejsu umożliwia urządzeniu |
||
|
||
|
||
Element |
||
|
||
Element |
||
|
||
Pole Może to być przydatne, gdy chcesz utworzyć wiele implementacji węzłów w jedną. |
||
Umożliwia klasom |
Węzły są automatycznie unieważniane po wywołaniu aktualizacji
. Ponieważ nasz przykład to DrawModifierNode
, wywoływana jest każda aktualizacja o każdej porze.
elementu, węzeł aktywuje ponowne rysowanie, a jego kolor zostanie prawidłowo zaktualizowany. Jest
zrezygnować z automatycznej dezaktywacji, postępując zgodnie z instrukcjami opisanymi poniżej.
ModifierNodeElement
ModifierNodeElement
to stała klasa, która przechowuje dane do utworzenia lub
zaktualizuj modyfikator niestandardowy:
// ModifierNodeElement private data class CircleElement(val color: Color) : ModifierNodeElement<CircleNode>() { override fun create() = CircleNode(color) override fun update(node: CircleNode) { node.color = color } }
Implementacje typu ModifierNodeElement
muszą zastąpić te metody:
create
: to funkcja tworząca instancję węzła modyfikatora. Dzięki temu w celu utworzenia węzła przy pierwszym zastosowaniu modyfikatora. Zwykle sprowadza się do utworzenia węzła i skonfigurowania go za pomocą parametrów, zostały przekazane do fabryki modyfikatorów.update
: ta funkcja jest wywoływana za każdym razem, gdy ten modyfikator znajduje się w funkcji to samo miejsce, które ten węzeł już istnieje, ale właściwość została zmieniona. To jest jest określana przez metodęequals
klasy. Węzeł modyfikujący, który został utworzona wcześniej jest wysyłana jako parametr w wywołaniuupdate
. W tym momencie należy zaktualizować węzły właściwości, które odpowiadają zaktualizowanym . Możliwość ponownego wykorzystania węzłów w ten sposób jest kluczowa wzrost skuteczności kampaniiModifier.Node
; dlatego musisz zaktualizować istniejącego węzła, zamiast tworzyć nowy w metodzieupdate
. W naszym okrąg, kolor węzła zostanie zaktualizowany.
Dodatkowo implementacje ModifierNodeElement
muszą też implementować equals
i hashCode
. Funkcja update
zostanie wywołana tylko wtedy, gdy argument równa się
poprzedni element zwraca wartość false (fałsz).
W tym przykładzie użyto klasy danych. Te metody są wykorzystywane do
sprawdź, czy węzeł wymaga aktualizacji. Jeśli element ma właściwości, które mają
nie wpływają na to, czy węzeł wymaga aktualizacji, czy też nie chcesz otrzymywać danych
ze względu na zgodność binarną, możesz ręcznie zaimplementować funkcję equals
i hashCode
, np. element modyfikatora dopełnienia.
Fabryka modyfikatora
To jest publiczna powierzchnia interfejsu API modyfikatora. Większość implementacji po prostu utwórz element modyfikujący i dodaj go do łańcucha modyfikatorów:
// Modifier factory fun Modifier.circle(color: Color) = this then CircleElement(color)
Pełny przykład
Te trzy części się łączą, aby utworzyć modyfikator niestandardowy, który będzie rysować okrąg.
za pomocą interfejsów API Modifier.Node
:
// 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) } }
Typowe sytuacje podczas korzystania z funkcji Modifier.Node
Oto kilka typowych sytuacji, które mogą wystąpić podczas tworzenia modyfikatorów niestandardowych za pomocą funkcji Modifier.Node
spotkania.
Zero parametrów
Jeśli modyfikator nie ma parametrów, nigdy nie musi być aktualizowany. Co więcej, nie musi być klasą danych. Oto przykładowa implementacja modyfikatora, który stosuje stałe dopełnienie do funkcji kompozycyjnej:
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) } } }
Lokalne kompozycje odsyłające
Modyfikatory funkcji Modifier.Node
nie dostrzegają automatycznie zmian stanu tworzenia wiadomości
obiekty takie jak CompositionLocal
. Modyfikatory korzyści Modifier.Node
mają ponad
dla modyfikatorów w fabryce kompozycyjnej,
wartość kompozycji lokalnej, w której jest używany modyfikator w Twoim interfejsie
drzewo, w którym nie przydziela się modyfikator, używając funkcji currentValueOf
.
Jednak instancje węzłów z modyfikatorem nie obserwują automatycznie zmian stanu. Do automatycznie reagować na lokalną zmianę kompozycji, można odczytać jej aktualną w zakresie:
DrawModifierNode
:ContentDrawScope
LayoutModifierNode
:MeasureScope
iIntrinsicMeasureScope
SemanticsModifierNode
:SemanticsPropertyReceiver
W tym przykładzie obserwuje się wartość LocalContentColor
, która pozwala narysować tło na podstawie
na jego kolor. Ponieważ ContentDrawScope
obserwuje zmiany w zrzutach, to
automatycznie pobiera ponownie po zmianie wartości LocalContentColor
:
class BackgroundColorConsumerNode : Modifier.Node(), DrawModifierNode, CompositionLocalConsumerModifierNode { override fun ContentDrawScope.draw() { val currentColor = currentValueOf(LocalContentColor) drawRect(color = currentColor) drawContent() } }
Aby reagować na zmiany stanu spoza zakresu i automatycznie aktualizować
użyj ObserverModifierNode
.
Modifier.scrollable
używa tej metody na przykład do:
obserwuj zmiany zachodzące w komórce LocalDensity
. Poniżej znajduje się uproszczony przykład:
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) } }
Animowany modyfikator
Implementacje typu Modifier.Node
mają dostęp do komponentu coroutineScope
. Dzięki temu
interfejsy Compose animatable API. Na przykład ten fragment zmienia
CircleNode
z góry, aby stopniowo się i wyłączać:
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) ) { } } } }
Udostępnianie stanu między modyfikatorami korzystającymi z przekazywania dostępu
Modyfikatory Modifier.Node
mogą przekazywać uprawnienia do innych węzłów. Wiele przypadków użycia
np. wyodrębnienie wspólnych implementacji z różnymi modyfikatorami,
ale może też być używany do współdzielenia wspólnego stanu dla modyfikatorów.
Na przykład podstawowa implementacja klikalnego węzła modyfikatora, który ma taki sam dane interakcji:
class ClickableNode : DelegatingNode() { val interactionData = InteractionData() val focusableNode = delegate( FocusableNode(interactionData) ) val indicationNode = delegate( IndicationNode(interactionData) ) }
Rezygnacja z automatycznej unieważniania węzłów
Modifier.Node
węzłów jest unieważnianych automatycznie, jeśli odpowiadające im
Aktualizacja połączeń ModifierNodeElement
. Czasami w bardziej złożonym modyfikatorze
chcesz zrezygnować z tego zachowania, by mieć dokładniejszą kontrolę nad tym,
modyfikator unieważnia fazy.
Jest to szczególnie przydatne, gdy modyfikator niestandardowy modyfikuje zarówno układ,
remis. Rezygnacja z automatycznej unieważniania pozwoli Ci unieważnić rysowanie, gdy:
można zmieniać jedynie właściwości związane z rysowaniem, takie jak color
, i nie unieważniać układu.
Może to poprawić skuteczność modyfikatora.
Hipotetyczny przykład przedstawiono poniżej z modyfikatorem zawierającym color
,
size
i onClick
lambda jako właściwości. Ten modyfikator unieważnia tylko
wymagane i pomija wszystkie unieważnienia, które nie są:
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) } } }