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:
- Bir 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 kolayca zincirlenmesine olanak tanır. Değiştirici fabrikası, Compose'un kullanıcı arayüzünüzü değiştirmek için kullandığı değiştirici öğelerini üretir.
- Bu,
- Bir değiştirici öğe
- Değiştiricinizin davranışını burada uygulayabilirsiniz.
Gerekli işlevselliğe bağlı olarak özel değiştiriciyi uygulamanın birden fazla yolu vardır. Genellikle, özel bir değiştiriciyi uygulamanın en kolay yolu, diğer tanımlanmış 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 düşük 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
Genellikle, yalnızca mevcut değiştiricileri kullanarak özel değiştiriciler oluşturmak 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ızı kontrol edin.
fun Modifier.clip(shape: Shape) = graphicsLayer(shape = shape, clip = true)
Aynı değiştirici grubunu sık sık kullandığınızı fark ederseniz bunları kendi değiştiricinizde birleştirebilirsiniz:
fun Modifier.myBackground(color: Color) = padding(16.dp) .clip(RoundedCornerShape(8.dp)) .background(color)
Composable değiştirici fabrikası kullanarak özel değiştirici oluşturma
Mevcut bir değiştiriciye değerler iletmek için composable işlevi kullanarak özel bir değiştirici de oluşturabilirsiniz. Bu, composable modifier factory olarak bilinir.
Değiştirici oluşturmak için birleştirilebilir değiştirici fabrikası kullanmak, animate*AsState
gibi daha üst düzey Compose API'lerinin ve diğer Compose durum destekli animasyon API'lerinin kullanılmasını da sağlar. Ö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öntemi ise bunu uygulamanın en kolay yolu birleştirilebilir bir 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 aşağıda ayrıntılı olarak açıklanan bazı sınırlamaları vardır.
CompositionLocal
değerleri, değiştirici fabrikasının çağrı sitesinde çözümlenir.
Birleştirilebilir değiştirici fabrikası kullanarak özel bir değiştirici oluştururken, birleştirme yerelleri, oluşturuldukları birleştirme ağacındaki değeri alır, kullanıldıkları yerdeki değeri almaz. Bu durum beklenmedik sonuçlara yol açabilir. Örneğin, yukarıdaki yerel değiştirici örneğini ele alalım. Bu örnek, composable işlevi kullanılarak biraz farklı şekilde uygulanmıştı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) } } }
Değiştiricinizin bu şekilde çalışmasını beklemiyorsanız bunun yerine özel bir Modifier.Node
kullanın. Bileşim yerelleri, kullanım sitesinde doğru şekilde çözümlenir ve güvenli bir şekilde yükseltilebilir.
Composable işlev değiştiriciler hiçbir zaman atlanmaz
Döndürülen değerleri olan composable işlevler atlanamadığı için 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 da sık sık yeniden oluşturma yapılıyorsa maliyetli olabilir.
Composable işlev değiştiricileri, composable işlev içinde çağrılmalıdır
Tüm composable işlevler gibi, composable fabrika değiştiricisi de kompozisyonun içinden çağrılmalıdır. Bu, değiştiricinin asla kompozisyonun dışına çıkarılamayacağı için nereye yükseltilebileceğini sınırlar. Buna karşılık, birleştirilebilir olmayan değiştirici fabrikaları, daha kolay yeniden kullanıma olanak tanımak ve performansı artırmak için birleştirilebilir işlevlerin dışına çı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ışı uygulama
Modifier.Node
, Compose'da değiştiriciler oluşturmak için kullanılan daha düşük düzeyli bir API'dir. Bu API, Compose'un kendi değiştiricilerini uyguladığı API ile aynıdır ve özel değiştiriciler oluşturmanın en iyi performanslı yoludur.
Modifier.Node
kullanarak özel değiştirici uygulama
Modifier.Node kullanarak özel değiştirici uygulama işlemi üç bölümden oluşur:
- Değiştiricinizin mantığını ve durumunu içeren bir
Modifier.Node
uygulaması. - Değiştirici düğümü örnekleri oluşturan ve güncelleyen bir
ModifierNodeElement
. - Yukarıda ayrıntılı olarak açıklanan 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 varlığını sürdürebilir ve hatta yeniden kullanılabilir.
Aşağıdaki bölümde her bir bölüm 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 renkteki 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şlevselliğe bağlı olarak farklı düğüm türleri vardır. Yukarıdaki örnekte çizim yapılması gerektiği için DrawModifierNode
uygulanır. Bu sayede çizim yöntemi geçersiz kılınabilir.
Kullanılabilir türler şunlardır:
Düğüm |
Kullanım |
Örnek bağlantı |
Sarmalanmış içeriğin nasıl ölçülüp düzenlendiğini değiştiren bir |
||
Düzenin alanına çizilen bir |
||
Bu arayüzü uyguladığınızda |
||
Test, erişilebilirlik ve benzer kullanım alanlarında kullanılmak üzere semantik anahtar/değer çiftleri ekleyen bir |
||
PointerInputChanges alan bir |
||
Üst düzene veri sağlayan bir |
||
|
||
İçeriğin genel konumu değişmiş olabileceği durumlarda, düzenin son |
||
|
||
İşi diğer Bu, birden fazla düğüm uygulamasını tek bir uygulamada birleştirmek için yararlı olabilir. |
||
|
Düğümler, ilgili öğelerinde güncelleme çağrıldığında otomatik olarak geçersiz kılınır. Örneğimiz bir DrawModifierNode
olduğundan, öğede herhangi bir güncelleme yapıldığında düğüm yeniden çizimi tetikler ve rengi doğru şekilde güncellenir. Aşağıda ayrıntılı olarak açıklandığı gibi otomatik geçersiz kılmayı devre dışı bırakabilirsiniz.
ModifierNodeElement
ModifierNodeElement
, özel değiştiricinizi oluşturmak veya güncellemek için gereken verileri içeren değişmez 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ü oluşturan işlevdir. Bu, değiştiriciniz ilk uygulandığında düğümü oluşturmak için çağrılır. Bu işlem genellikle düğümü oluşturup değiştirici fabrikasına iletilen parametrelerle yapılandırmayı içerir.update
: Bu işlev, bu değiştirici, düğümün zaten bulunduğu aynı noktada sağlandığında ancak bir özellik değiştiğinde çağrılır. Bu, sınıfınequals
yö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.Node
ile elde edilen performans artışının anahtarıdır. 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üncellenir.
Ayrıca, ModifierNodeElement
uygulamalarında equals
ve hashCode
de uygulanmalıdır. update
yalnızca önceki öğeyle yapılan bir eşitlik karşılaştırması yanlış değerini döndürürse çağrılır.
Yukarıdaki örnekte, bu amaca ulaşmak için bir veri sınıfı kullanılıyor. 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 uyumluluk nedenleriyle veri sınıflarından kaçınmak istiyorsanız equals
ve hashCode
'yi (ör. padding değiştirici öğesi) manuel olarak uygulayabilirsiniz.
Değiştirici fabrikası
Bu, değiştiricinizin herkese açık API yüzeyidir. Çoğu uygulamada değiştirici öğe oluşturulur ve değiştirici zincirine eklenir:
// Modifier factory fun Modifier.circle(color: Color) = this then CircleElement(color)
Eksiksiz örnek
Bu üç bölüm, Modifier.Node
API'lerini kullanarak daire çizmek için özel değiştiriciyi oluşturmak üzere bir araya gelir:
// 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 verilmiştir.
Parametre yok
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) } } }
Bileşim yerellerine referans verme
Modifier.Node
değiştiricileri, CompositionLocal
gibi Compose durumu nesnelerindeki değişiklikleri otomatik olarak gözlemlemez. Modifier.Node
değiştiricilerin, yalnızca birleştirilebilir fabrika ile oluşturulan değiştiricilere göre avantajı, currentValueOf
kullanarak değiştiricinin ayrıldığı yerden değil, kullanıcı arayüzü ağınızda kullanıldığı yerden kompozisyon yerelinin değerini okuyabilmeleridir.
Ancak değiştirici düğüm örnekleri, durum değişikliklerini otomatik olarak gözlemlemez. Besteye özel bir yerelin değişmesine otomatik olarak tepki vermek için kapsam içinde geçerli değerini okuyabilirsiniz:
DrawModifierNode
:ContentDrawScope
LayoutModifierNode
:MeasureScope
&IntrinsicMeasureScope
SemanticsModifierNode
:SemanticsPropertyReceiver
Bu örnekte, arka planı rengine göre ç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() } }
Bir kapsamın 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
'deki değişiklikleri gözlemlemek için bu tekniği kullanır. Basitleştirilmiş bir örnek aşağıda 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) } }
Animasyon değiştirici
Modifier.Node
uygulamaları coroutineScope
erişebilir. Bu, Compose Animatable API'lerinin kullanılmasına olanak tanır. Örneğin, bu snippet yukarıdaki CircleNode
öğesini tekrar tekrar solup 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 paylaşım durumu
Modifier.Node
değiştiriciler, diğer düğümlere yetki verebilir. Bunun birçok kullanım alanı vardır. Örneğin, farklı değiştiricilerdeki ortak uygulamaları ayıklamak için kullanılabilir. Ayrıca, değiştiriciler arasında ortak durum 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ün otomatik olarak geçersiz kılınmasını devre dışı bırakma
Modifier.Node
düğümleri, ilgili ModifierNodeElement
çağrıları güncellendiğinde 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ğı konusunda daha ayrıntılı kontrol sahibi olmak için bu davranışın kapsamı dışında kalmak 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ırakarak yalnızca color
gibi çizimle ilgili özellikler değiştiğinde çizimi geçersiz kılabilir ve düzeni geçersiz kılmayabilirsiniz.
Bu, değiştiricinizin performansını artırabilir.
Bununla ilgili varsayıma dayalı bir örnek, aşağıda color
, size
ve onClick
lambda'ya sahip bir değiştiriciyle gösterilmektedir. Bu değiştirici yalnızca gerekli olanı geçersiz kılar ve gerekli olmayan 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) } } }