Oluştur, genel davranışlar için kullanıma hazır birçok değiştirici sağlar, ancak kendi özel değiştiricilerinizi de oluşturabilirsiniz.
Değiştiricilerin birden fazla bölümü vardır:
- Bir değiştirici fabrikası
- Bu,
Modifier
ürününde bulunan bir uzantı işlevidir. Bu işlev, değiştiriciniz için deyimsel bir API sağlar ve değiştiricilerin kolayca zincirlenmesini sağlar. Değiştirici fabrikası, kullanıcı arayüzünüzü değiştirmek için Compose tarafından kullanılan değiştirici öğelerini üretir.
- Bu,
- Değiştirici öğe
- Değiştiricinizin davranışını buradan uygulayabilirsiniz.
Gereken işlevlere bağlı olarak özel değiştiricileri uygulamanın birden fazla yolu vardır. Genellikle, bir özel değiştiriciyi uygulamanın en kolay yolu, önceden tanımlanmış diğer değiştirici fabrikalarını bir araya getiren bir özel değiştirici fabrikası uygulamaktır. Daha fazla özel davranışa ihtiyacınız varsa daha düşük düzeyli ancak daha fazla esneklik sunan Modifier.Node
API'lerini kullanarak değiştirici öğeyi uygulayın.
Mevcut değiştiricileri birlikte zincirleme
Çoğu zaman, yalnızca mevcut değiştiricileri kullanarak özel değiştiriciler oluşturabilirsiniz. Örneğin, Modifier.clip()
graphicsLayer
değiştiricisi kullanılarak uygulanır. Bu stratejide mevcut değiştirici öğeleri kullanılır ve
kendi özel değiştirici fabrikanızı oluşturursunuz.
Kendi özel değiştiricinizi uygulamadan önce aynı stratejiyi kullanıp kullanamayacağınıza bakın.
fun Modifier.clip(shape: Shape) = graphicsLayer(shape = shape, clip = true)
Veya aynı değiştirici grubunu sık sık tekrarladığınızı fark ederseniz bunları kendi değiştiricinize dahil edebilirsiniz:
fun Modifier.myBackground(color: Color) = padding(16.dp) .clip(RoundedCornerShape(8.dp)) .background(color)
Değiştirilebilir bir değiştirici fabrikası kullanarak özel değiştirici oluşturun
Mevcut bir değiştiriciye değer aktarmak için composable işlevini kullanarak da özel değiştirici oluşturabilirsiniz. Bu, composable değiştirici fabrikası olarak bilinir.
Düzenleyici oluşturmak için composable değiştirici fabrikasının kullanılması, animate*AsState
gibi daha üst düzey oluşturma API'lerinin ve diğer Compose durumu destekli animasyon API'lerinin kullanılmasına da olanak tanır. Örneğin, aşağıdaki snippet, etkinleştirildiğinde/devre dışı bırakıldığında alfa değişikliğini canlandıran bir değiştirici gösterir:
@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 } }
Özel değiştiriciniz, bir CompositionLocal
öğesinden varsayılan değerler sağlamanın kolay bir yöntemiyse bunu uygulamanın en kolay yolu bir composable değiştirici fabrikası kullanmaktır:
@Composable fun Modifier.fadedBackground(): Modifier { val color = LocalContentColor.current return this then Modifier.background(color.copy(alpha = 0.5f)) }
Bu yaklaşımda, aşağıda ayrıntıları verilen bazı uyarılar vardır.
CompositionLocal
değerleri, değiştirici fabrikasının çağrı sitesinde çözüldü
Bestecileri, bir composable değiştirici fabrikası kullanarak özel değiştiriciler oluştururken kompozisyon ağaçlarının değerini, oluşturuldukları kompozisyon ağacından alırlar. Bu durum beklenmedik sonuçlara yol açabilir. Örneğin, yukarıdaki yerel düzenleyici örneğini ele alalım. Bu örnekte, composable bir işlev kullanılarak biraz farklı bir uygulama uygulanır:
@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) } } }
Düzenleyicinizin çalışmasını beklediğiniz gibi değilse bunun yerine özel bir Modifier.Node
kullanın. Beste yerelleri kullanım alanında doğru şekilde çözümlenir ve güvenli bir şekilde kaldırılabilir.
Oluşturulabilir işlev değiştiricileri hiçbir zaman atlanmaz
Dönüş değerleri olan composable işlevler atlanamayacağından, kompozit fabrika değiştiricileri hiçbir zaman atlanmaz. Bu, değiştirici fonksiyonunuzun her yeniden oluşturmada çağrılacağı anlamına gelir. Sık sık yeniden oluşturulduğunda bu işlem pahalı olabilir.
Toplanabilir işlev değiştiriciler composable bir işlev içinde çağrılmalıdır
Tüm composable işlevlerde olduğu gibi, composable fabrika değiştiricisi de kompozisyon içinden çağrılmalıdır. Bu, hiçbir zaman bileşimden çıkarılamayacağı için bir değiştiricinin kaldırılabileceği alanları sınırlar. Buna karşılık, oluşturulamayan değiştirici fabrikalar, yeniden kullanımı kolaylaştırmak ve performansı artırmak için composable işlevlerden kaldırılabilir:
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 }
Modifier.Node
kullanarak özel değiştirici davranışı uygulayın
Modifier.Node
, Compose'da değiştiriciler oluşturmaya yönelik daha düşük düzey bir API'dir. Bu, Compose'un kendi değiştiricilerini uyguladığı API'dir ve özel değiştiriciler oluşturmanın en etkili yoludur.
Modifier.Node
kullanarak özel değiştirici uygulama
Modifier.Node kullanarak özel bir değiştirici uygulama üç bölümden oluşur:
- Değiştiricinizin mantığını ve durumunu barındıran bir
Modifier.Node
uygulaması. - Değiştirici düğüm örnekleri oluşturan ve güncelleyen bir
ModifierNodeElement
. - Yukarıda açıklandığı gibi isteğe bağlı bir değiştirici fabrikası.
ModifierNodeElement
sınıfları durum bilgisizdir ve her yeniden oluşturma işlemine yeni örnekler ayrılır. Modifier.Node
sınıfları durum bilgili olabilir ve birden fazla yeniden derlemede bile dayanabilir, hatta yeniden kullanılabilir.
Aşağıdaki bölümde her bir bölüm açıklanmakta ve daire çizmek için özel değiştirici oluşturma örneği gösterilmektedir.
Modifier.Node
Modifier.Node
uygulaması (bu örnekte, CircleNode
), özel değiştiricinizin işlevini uygular.
// Modifier.Node private class CircleNode(var color: Color) : DrawModifierNode, Modifier.Node() { override fun ContentDrawScope.draw() { drawCircle(color) } }
Bu örnekte, değiştirici işlevine geçirilen rengin bulunduğu daireyi çizmektedir.
Bir düğüm, hem Modifier.Node
hem de sıfır veya daha fazla düğüm türünü uygular. Değiştiricinizin gerektirdiği işlevlere bağlı olarak farklı düğüm türleri bulunur. Yukarıdaki örneğin çizim yapabilmesi gerekir. Bu nedenle, DrawModifierNode
yöntemini uygular. Bu durum, örneğin, çizim yöntemini geçersiz kılmasına olanak tanır.
Kullanılabilir türler aşağıda belirtilmiştir:
Düğüm |
Kullanım |
Örnek Bağlantı |
Sarmalanmış içeriğin ölçülme ve düzenlenme şeklini değiştiren bir |
||
Düzenin alanına çizim yapan bir |
||
Bu arayüzün uygulanması |
||
Test, erişilebilirlik ve benzer kullanım alanlarında kullanılmak üzere anlamsal anahtar/değer çifti ekleyen |
||
PointerInputChanges alan bir |
||
Üst düzene veri sağlayan bir |
||
|
||
İçeriğin genel konumu değişmiş olabileceği zaman düzenin son |
||
|
||
Diğer Bu, birden fazla düğüm uygulamasını tek bir düğümde oluşturmak için yararlı olabilir. |
||
|
Düğümler, karşılık gelen öğelerinde güncelleme çağrıldığında otomatik olarak geçersiz kılınır. Örneğimiz bir DrawModifierNode
olduğundan, öğede her güncelleme çağrıldığında, düğüm bir yeniden çizimi tetikler ve rengi doğru şekilde güncellenir. Otomatik geçersiz kılmayı aşağıda ayrıntılı olarak açıklandığı şekilde devre dışı bırakabilirsiniz.
ModifierNodeElement
ModifierNodeElement
, özel değiştiricinizi oluşturmak veya güncellemek için verileri saklayan sabit bir sınıftır:
// ModifierNodeElement private data class CircleElement(val color: Color) : ModifierNodeElement<CircleNode>() { override fun create() = CircleNode(color) override fun update(node: CircleNode) { node.color = color } }
ModifierNodeElement
uygulamanın aşağıdaki yöntemleri geçersiz kılması gerekiyor:
create
: Değiştirici düğümünüzü somutlaştıran işlevdir. Bu işlev, değiştiriciniz ilk uygulandığında düğümü oluşturmak için çağrılır. Genellikle bu, düğümü oluşturmak ve değiştirici fabrikasına iletilen parametrelerle yapılandırmak anlamına gelir.update
: Bu düzenleyici, bu düğümün hâlihazırda mevcut olduğu ancak bir özelliği değiştirildiği yerde sağlandığında çağrılır. Bu, sınıfınequals
yöntemi ile belirlenir. Daha önce oluşturulan değiştirici düğüm,update
çağrısına parametre olarak gönderilir. Bu noktada, güncellenen parametrelere karşılık gelecek şekilde düğümlerin özelliklerini güncellemeniz gerekir. Düğümlerin bu şekilde yeniden kullanılabilmesiModifier.Node
'nin sağladığı performans kazançları açısından son derece önemlidir. Bu nedenle,update
yönteminde yeni bir düğüm oluşturmak yerine mevcut düğümü güncellemeniz gerekir. Daire örneğimizde düğümün rengi güncellenmiştir.
Buna ek olarak, ModifierNodeElement
uygulamalarının equals
ve hashCode
değerlerini de uygulaması gerekir. update
, yalnızca önceki öğe ile eşittir karşılaştırma değeri false (yanlış) değerini döndürürse çağrılır.
Yukarıdaki örnekte, bunu sağlamak için bir veri sınıfı kullanılmaktadır. Bu yöntemler, bir düğümün güncellenmesi gerekip gerekmediğini kontrol etmek için kullanılır. Öğenizde, bir düğümün güncellenmesine katkı sağlamayan özellikler varsa veya ikili program uyumluluğu nedeniyle veri sınıflarından kaçınmak istiyorsanız equals
ve hashCode
(ör. dolgu değiştirici öğesi) manuel olarak uygulayabilirsiniz.
Değiştirici fabrikası
Bu, değiştiricinizin herkese açık API yüzeyidir. Çoğu uygulama, değiştirici öğesini oluşturur ve onu değiştirici zincirine ekler.
// Modifier factory fun Modifier.circle(color: Color) = this then CircleElement(color)
Eksiksiz örnek
Bu üç parça bir araya gelerek Modifier.Node
API'lerini kullanarak bir daire çizmek için özel değiştiriciyi oluşturur:
// 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) } }
Modifier.Node
kullanımıyla ilgili yaygın durumlar
Modifier.Node
ile özel değiştiriciler oluştururken karşılaşabileceğiniz bazı yaygın durumları aşağıda bulabilirsiniz.
Sıfır parametre
Değiştiricinizde parametre yoksa hiçbir zaman güncellenmesi gerekmez. Ayrıca veri sınıfı olması gerekmez. Aşağıda, bir composable'a sabit miktarda dolgu uygulayan bir değiştiricinin örnek uygulaması verilmiştir:
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) } } }
Beste yerellerine referans verme
Modifier.Node
değiştiricileri, CompositionLocal
gibi Oluşturma durumu nesnelerindeki değişiklikleri otomatik olarak gözlemlemez. Modifier.Node
değiştiricilerinin avantajı, composable fabrika ile kısa süre önce oluşturulmuş olan göreceli değiştiricilere sahip olmalarıdır. Bunlar, currentValueOf
ile düzenleyicinin atandığı yerde değil, değiştiricinin atandığı yerde kullanılan yerel değeri okuyabilmeleridir.
Bununla birlikte, değiştirici düğüm örnekleri durum değişikliklerini otomatik olarak gözlemlemez. Bir bileşimdeki yerel değişikliklere otomatik olarak tepki vermek için geçerli değerini bir kapsam içinde okuyabilirsiniz:
DrawModifierNode
:ContentDrawScope
LayoutModifierNode
:MeasureScope
veIntrinsicMeasureScope
SemanticsModifierNode
:SemanticsPropertyReceiver
Bu örnekte, rengine göre bir arka plan çizmek için LocalContentColor
değeri gösterilmektedir. ContentDrawScope
anlık görüntü değişiklikleri gözlemlediğinden, LocalContentColor
değeri değiştiğinde bu işlem otomatik olarak yeniden çizer:
class BackgroundColorConsumerNode : Modifier.Node(), DrawModifierNode, CompositionLocalConsumerModifierNode { override fun ContentDrawScope.draw() { val currentColor = currentValueOf(LocalContentColor) drawRect(color = currentColor) drawContent() } }
Kapsam dışındaki durum değişikliklerine tepki vermek ve değiştiricinizi otomatik olarak güncellemek için ObserverModifierNode
kullanın.
Örneğin Modifier.scrollable
, LocalDensity
öğesindeki değişiklikleri gözlemlemek için bu tekniği kullanır. Aşağıda basitleştirilmiş bir örnek verilmiştir:
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) } }
Animasyon değiştirici
Modifier.Node
uygulamalarının coroutineScope
erişimi var. Bu, Compose Animatable API'lerin kullanılmasına olanak tanır. Örneğin, bu snippet, yukarıdan aşağıya doğru CircleNode
öğesini değiştirerek tekrar tekrar kararma ve sönme yapar:
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) ) { } } } }
Yetki kullanarak değiştiriciler arasında durum paylaşma
Modifier.Node
değiştiricileri diğer düğümlere yetki verebilir. Bunun, farklı değiştiricilerde yaygın uygulamaları ayıklama gibi birçok kullanım alanı vardır ancak değiştiriciler arasında ortak durum paylaşımı için de kullanılabilir.
Örneğin, etkileşim verilerini paylaşan tıklanabilir bir değiştirici düğümünün temel uygulaması:
class ClickableNode : DelegatingNode() { val interactionData = InteractionData() val focusableNode = delegate( FocusableNode(interactionData) ) val indicationNode = delegate( IndicationNode(interactionData) ) }
Düğümlerin otomatik olarak geçersiz kılınmasını devre dışı bırakma
İlgili ModifierNodeElement
çağrıları güncellendiğinde Modifier.Node
düğüm otomatik olarak geçersiz kılınır. Bazen daha karmaşık bir değiştiricide, değiştiricinizin aşamaları ne zaman geçersiz kılacağı üzerinde daha ayrıntılı bir kontrole sahip olmak için bu davranışı devre dışı bırakmak isteyebilirsiniz.
Bu, özellikle özel değiştiriciniz hem düzeni hem de çizimi değiştiriyorsa yararlı olabilir. Otomatik geçersiz kılmayı devre dışı bırakmak, yalnızca çizimle ilgili özellikler (ör. color
, değiştir) olduğunda çizimi geçersiz kılmanıza ve düzeni geçersiz kılmamanıza olanak tanır.
Bu, değiştiricinizin performansını artırabilir.
Bunun varsayımsal bir örneği, özellik olarak color
, size
ve onClick
lambdası olan bir değiştiriciyle gösterilmiştir. Bu değiştirici yalnızca gerekli olanları geçersiz kılar ve olmayan tüm geçersiz kılma işlemlerini atlar:
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) } } }