Compose, yaygın davranışlar için kullanıma hazır birçok değiştirici sunar ancak kendi özel değiştiricilerinizi de oluşturabilirsiniz.
Değiştiriciler birden fazla bölümden oluşur:
- Değiştirici fabrikası
- Bu,
Modifierüzerinde bir uzantı işlevidir. Değiştiriciniz için deyimsel bir API sağlar ve değiştiricilerin birbirine bağlanmasına olanak tanır. Değiştirici fabrikası, Compose'un kullanıcı arayüzünüzü değiştirmek için kullandığı değiştirici öğeleri üretir.
- Bu,
- Değiştirici öğe
- Değiştiricinizin davranışını buradan uygulayabilirsiniz.
Gerekli işlevlere bağlı olarak özel değiştirici uygulamanın birden fazla yolu vardır. Özel değiştirici uygulamanın en basit yolu, önceden tanımlanmış diğer değiştirici fabrikalarını birleştiren özel bir değiştirici fabrikası uygulamaktır. Daha fazla özel davranışa ihtiyacınız varsa daha alt düzeyde olan ancak daha fazla esneklik sağlayan Modifier.Node API'lerini kullanarak değiştirici öğesini uygulayın.
Mevcut değiştiricileri birbirine bağlama
Mevcut değiştiricileri kullanarak özel değiştiriciler oluşturmak genellikle mümkündür. Örneğin, Modifier.clip(), graphicsLayer değiştiricisi kullanılarak uygulanır. Bu strateji, mevcut değiştirici öğeleri kullanır ve kendi özel değiştirici fabrikanızı sağlarsınız.
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)
Aynı değiştirici grubunu sık sık tekrarladığınızı fark ederseniz bunları kendi değiştiricinizde sarmalayabilirsiniz:
fun Modifier.myBackground(color: Color) = padding(16.dp) .clip(RoundedCornerShape(8.dp)) .background(color)
Oluşturulabilir değiştirici fabrikası kullanarak özel değiştirici oluşturma
Mevcut bir değiştiriciye değer iletmek için composable işlev kullanarak özel bir değiştirici de oluşturabilirsiniz. Buna, composable değiştirici fabrikası denir.
Değiştirici oluşturmak için composable değiştirici fabrikası kullanmak, animate*AsState ve diğer Compose durum destekli animasyon API'leri gibi daha üst düzey Compose API'lerini kullanmanıza da olanak tanır. Örneğin, aşağıdaki snippet'te etkinleştirildiğinde/devre dışı bırakıldığında alfa değişikliğini canlandıran bir değiştirici gösterilmektedir:
@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, CompositionLocal öğesinden varsayılan değerler sağlayan bir kolaylık yöntemiyse bunu uygulamanın en kolay yolu, 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şımın bazı dezavantajları vardır. Bunlar aşağıdaki bölümlerde ayrıntılı olarak açıklanmıştır.
CompositionLocal değerleri, değiştirici fabrikasının çağrı sitesinde çözümlenir.
Oluşturulabilir değiştirici fabrikası kullanarak özel değiştirici oluştururken oluşturma yerelleri, değeri kullanıldıkları değil, oluşturuldukları oluşturma ağacından alır. Bu durum beklenmedik sonuçlara yol açabilir. Örneğin, daha önce bahsedilen ve composable işlev kullanılarak biraz farklı şekilde uygulanan CompositionLocal değiştirici örneğini ele alalım:
@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) } } }
Değiştiricinizin bu şekilde çalışmasını istemiyorsanız özel bir Modifier.Node kullanın. Böylece, oluşturma yerelleri kullanım alanında doğru şekilde çözümlenir ve güvenli bir şekilde kaldırılabilir.
Composable işlev değiştiriciler hiçbir zaman atlanmaz
Dönüş değerleri olan composable işlevler atlanamadığından composable fabrika değiştiriciler hiçbir zaman atlanmaz. Bu, değiştirici işlevinizin her yeniden oluşturmada çağrılacağı anlamına gelir. Bu işlem, sık sık yeniden oluşturulursa maliyetli olabilir.
Composable işlev değiştiriciler, bir composable işlev içinde çağrılmalıdır
Tüm composable işlevler gibi, composable fabrika değiştiricisi de oluşturma içinden çağrılmalıdır. Bu durum, değiştiricinin taşınabileceği yeri sınırlar çünkü değiştirici hiçbir zaman oluşturma işleminin dışına taşınamaz. Buna karşılık, birleştirilebilir olmayan değiştirici fabrikaları, daha kolay yeniden kullanım sağlamak ve performansı artırmak için composable işlevlerden çıkarı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ışını uygulama
Modifier.Node, Compose'da değiştirici oluşturmak için kullanılan alt düzey bir API'dir. Bu, Compose'un kendi değiştiricilerini uyguladığı API'dir ve özel değiştiriciler oluşturmanın en yüksek performanslı yoludur.
Modifier.Node kullanarak özel değiştirici uygulama
Modifier.Node kullanarak özel değiştirici uygulamanın üç bölümü vardır:
- Değiştiricinizin mantığını ve durumunu barındıran bir
Modifier.Nodeuygulaması. - Değiştirici düğüm örnekleri oluşturan ve güncelleyen bir
ModifierNodeElement. - Daha önce ayrıntılı olarak açıklandığı gibi, isteğe bağlı bir değiştirici fabrikası.
ModifierNodeElement sınıfları durum bilgisizdir ve her yeniden oluşturmada yeni örnekler ayrılır. Modifier.Node sınıfları ise durum bilgili olabilir, birden fazla yeniden oluşturma işleminde kullanılabilir ve hatta yeniden kullanılabilir.
Aşağıdaki bölümde her bir parça açıklanmakta ve daire çizmek için özel bir 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 iletilen renkle daire çizilir.
Bir düğüm, Modifier.Node ve sıfır veya daha fazla düğüm türü uygular. Değiştiricinizin gerektirdiği işlevlere göre farklı düğüm türleri vardır. Önceki örneğin çizim yapabilmesi gerekir. Bu nedenle, çizim yöntemini geçersiz kılmasına olanak tanıyan DrawModifierNode yöntemini uygular.
Kullanılabilir türler şunlardır:
Düğüm |
Kullanım |
Örnek Bağlantı |
Sarmalanmış içeriğinin nasıl ölçüldüğünü ve düzenlendiğini değiştiren bir |
||
Düzenin alanına çizim yapan bir |
||
Bu arayüzü uygulayarak |
||
Test, erişilebilirlik ve benzer kullanım alanlarında kullanılmak üzere semantik anahtar/değer ekleyen bir |
||
PointerInputChanges alan bir |
||
Üst yerleşime veri sağlayan bir |
||
|
||
İçeriğin genel konumu değiştiğinde düzenin son |
||
|
||
İşleri diğer Bu, birden fazla düğüm uygulamasını tek bir uygulamada birleştirmek 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 güncelleme çağrıldığında düğüm yeniden çizimi tetikler ve rengi doğru şekilde güncellenir. Düğüm otomatik geçersiz kılma özelliğini devre dışı bırakma bölümünde ayrıntılı olarak açıklandığı gibi, otomatik geçersiz kılma özelliğini devre dışı bırakabilirsiniz.
ModifierNodeElement
ModifierNodeElement, özel değiştiricinizi oluşturmak veya güncellemek için gereken verileri barındıran, değiştirilemez 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 uygulamalarının aşağıdaki yöntemleri geçersiz kılması gerekir:
create: Bu, değiştirici düğümünüzü örneklendiren işlevdir. Bu işlev, değiştiriciniz ilk kez uygulandığında düğümü oluşturmak için çağrılır. Genellikle bu, düğümün oluşturulması ve değiştirici fabrikasına iletilen parametrelerle yapılandırılması anlamına gelir.update: Bu işlev, bu değiştirici bu düğümün zaten mevcut olduğu aynı noktada sağlandığında ancak bir özellik değiştiğinde çağrılır. Bu, sınıfınequalsyöntemiyle belirlenir. Daha önce oluşturulan değiştirici düğümü,updateçağrısına parametre olarak gönderilir. Bu noktada, düğümlerin özelliklerini güncellenen parametrelere karşılık gelecek şekilde güncellemeniz gerekir. Düğümlerin bu şekilde yeniden kullanılabilmesi,Modifier.Nodetarafından sağlanan performans artışları açısından çok önemlidir. Bu nedenle,updateyönteminde yeni bir düğüm oluşturmak yerine mevcut düğümü güncellemeniz gerekir. Daire örneğimizde düğümün rengi güncellenir.
Ayrıca ModifierNodeElement uygulamalarının equals ve hashCode'yi de uygulaması gerekir. update, yalnızca önceki öğeyle yapılan eşitlik karşılaştırması false döndürürse çağrılır.
Önceki örnekte bunu yapmak için bir veri sınıfı kullanılmıştı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üncellenmesi gerekip gerekmediğine katkıda bulunmayan özellikler varsa veya ikili program uyumluluğu nedeniyle veri sınıflarından kaçınmak istiyorsanız equals ve hashCode öğelerini manuel olarak uygulayabilirsiniz (ör. dolgu değiştirici öğesi).
Değiştirici fabrikası
Bu, değiştiricinizin herkese açık API yüzeyidir. Çoğu uygulama, değiştirici öğesini oluşturur ve 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 daire çizen ö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ılan yaygın durumlar
Modifier.Node ile özel değiştiriciler oluştururken karşılaşabileceğiniz bazı yaygın durumlar aşağıda açıklanmıştır.
Sıfır parametre
Değiştiricinizin parametresi yoksa hiçbir zaman güncellenmesi gerekmez ve veri sınıfı olması da 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) } } }
Referans oluşturma yerelleri
Modifier.Node değiştiricileri, CompositionLocal gibi Compose durum nesnelerindeki değişiklikleri otomatik olarak gözlemlemez. Modifier.Node değiştiricilerin, yalnızca bir composable factory ile oluşturulan değiştiricilere göre avantajı, currentValueOf kullanarak, değiştiricinin ayrıldığı yerde değil, kullanıcı arayüzü ağacınızda kullanıldığı yerden yerel oluşturma değerini okuyabilmeleridir.
Ancak değiştirici düğüm örnekleri, durum değişikliklerini otomatik olarak gözlemlemez. Bir CompositionLocal'daki değişikliğe otomatik olarak tepki vermek için geçerli değerini bir kapsam içinde okuyabilirsiniz:
DrawModifierNode:ContentDrawScopeLayoutModifierNode:MeasureScopeveIntrinsicMeasureScopeSemanticsModifierNode:SemanticsPropertyReceiver
Bu örnekte, rengine göre arka plan çizmek için LocalContentColor değeri gözlemlenir. ContentDrawScope, anlık görüntü değişikliklerini gözlemlediğinden LocalContentColor değeri değiştiğinde otomatik olarak yeniden çizilir:
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 içindeki değişiklikleri gözlemlemek için bu tekniği kullanır. Aşağıdaki örnekte basitleştirilmiş bir örnek gösterilmektedir:
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) } }
Değiştiriciye animasyon ekleme
Modifier.Node uygulamalarının coroutineScope erişimi var. Bu sayede Compose Animatable API'leri kullanılabilir. Örneğin, bu snippet daha önce gösterilen CircleNode öğesini tekrar tekrar belirip kaybolacak şekilde değiştirir:
class CircleNode(var color: Color) : Modifier.Node(), DrawModifierNode { private lateinit var alpha: Animatable<Float, AnimationVector1D> override fun ContentDrawScope.draw() { drawCircle(color = color, alpha = alpha.value) drawContent() } override fun onAttach() { alpha = Animatable(1f) coroutineScope.launch { alpha.animateTo( 0f, infiniteRepeatable(tween(1000), RepeatMode.Reverse) ) { } } } }
Yetki verme özelliğini kullanarak değiştiriciler arasında durum paylaşma
Modifier.Node değiştiricileri diğer düğümlere yetki verebilir. Farklı değiştiricilerdeki ortak uygulamaları ayıklamak gibi birçok kullanım alanı vardır ancak değiştiriciler arasında ortak durumu paylaşmak için de kullanılabilir.
Örneğin, etkileşim verilerini paylaşan tıklanabilir bir değiştirici düğümün temel uygulaması:
class ClickableNode : DelegatingNode() { val interactionData = InteractionData() val focusableNode = delegate( FocusableNode(interactionData) ) val indicationNode = delegate( IndicationNode(interactionData) ) }
Düğüm otomatik geçersiz kılma özelliğini devre dışı bırakma
Modifier.Node düğümleri, karşılık gelen ModifierNodeElement çağrıları güncellendiğinde otomatik olarak geçersiz kılınır. Karmaşık değiştiriciler için, değiştiricinizin aşamaları ne zaman geçersiz kılacağı konusunda daha ayrıntılı kontrol elde etmek amacıyla bu davranışı devre dışı bırakmak isteyebilirsiniz.
Bu, özellikle özel değiştiriciniz hem düzeni hem de çizimi değiştiriyorsa kullanışlıdır. Otomatik geçersiz kılmayı devre dışı bırakmak, yalnızca çizimle ilgili özellikler (ör. color) değiştiğinde çizimi geçersiz kılmanıza olanak tanır. Bu sayede düzen geçersiz kılınmaz ve değiştiricinizin performansı artabilir.
Bunun varsayımsal bir örneği, özellik olarak color, size ve onClick lambda içeren bir değiştiriciyle aşağıdaki örnekte gösterilmiştir. Bu değiştirici, yalnızca gerekli olanları geçersiz kılar ve gereksiz 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) } } }