Oluşturma, yaygın davranışlar için kullanıma hazır birçok değişken sağlar ancak kendi özel değiştiricilerinizi de oluşturabilirsiniz.
Değiştiricilerin birden fazla bölümü vardır:
- Değiştirici fabrikası
- Bu,
Modifier
üzerinde bir uzantı işlevidir. Değiştiriciniz için idiomatik bir API sağlar ve değiştiricilerin kolayca birbirine bağlanmasına olanak tanır. Değiştirici fabrika, Compose tarafından kullanıcı arayüzünü değiştirmek için kullanılan değiştirici öğeleri üretir.
- Bu,
- Değiştirici öğe
- Burada, değiştiricinizin davranışını uygulayabilirsiniz.
Gerekli işleve bağlı olarak özel bir değiştiriciyi uygulamanın birden fazla yolu vardır. Özel bir değiştiriciyi uygulamanın en kolay yolu genellikle, ö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 değiştirici öğeyi, daha düşük düzeyde ancak daha fazla esneklik sunan Modifier.Node
API'lerini kullanarak uygulayın.
Mevcut değiştiricileri birbirine bağlama
Özel değiştiriciler oluşturmak genellikle yalnızca mevcut değiştiricileri kullanarak mümkündür. Örneğin, Modifier.clip()
, graphicsLayer
değiştiricisi kullanılarak uygulanır. Bu stratejide mevcut değiştirici öğeler kullanılıyor ve siz
kendi özel değiştirici fabrikanızı sunuyorsunuz.
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 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)
Fabrika ayarı kullanarak özel değiştirici oluşturun
Mevcut bir değiştiriciye değer aktarmak için birleştirilebilir bir işlev kullanarak özel değiştirici de oluşturabilirsiniz. Buna, birleştirilebilir değiştirici fabrikası denir.
Bir değiştirici oluşturmak için birleştirilebilir değiştirici fabrikası kullanmak, animate*AsState
ve diğer Compose durumu destekli animasyon API'leri gibi daha üst düzey Compose API'lerinin kullanılmasına 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 animasyonlu olarak gösteren 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
kaynağından varsayılan değerler sağlamak için kullanılan bir kolaylık yöntemiyse 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ı sakıncaları vardır.
CompositionLocal
değerleri, değiştirici fabrikasının çağrı yerinde çözülür
Kompozit değiştirici fabrikası kullanarak özel değiştirici oluştururken beste yerelleri, değeri kullanılmadığında, oluşturuldukları bileşim ağacından alırlar. Bu durum beklenmedik sonuçlara yol açabilir. Örneğin, yukarıdaki kompozisyon yerel değiştirici örneğini ele alalım. Bu örnek, birleştirilebilir bir işlev 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 yerel derlemeler kullanım sitesinde doğru şekilde çözüleceği ve güvenli bir şekilde kaldırılabileceği için bunun yerine özel bir Modifier.Node
kullanın.
Kompozit işlev değiştiriciler hiçbir zaman atlanmaz
Döndürülen değeri olan birleştirilebilir işlevler atlanamayabileceğinden, birleştirilebilir fabrika değiştiricileri hiçbir zaman atlanmaz. Bu, değiştirici işlevinizin her yeniden oluşturma işleminde çağrılacağı anlamına gelir. Bu, sık sık yeniden oluşturma işlemi yapılması durumunda pahalı olabilir.
Özelleştirilebilir işlev değiştiricileri, composable işlev içinde çağrılmalıdır
Tüm birleştirilebilir işlevler gibi, birleştirilebilir fabrika değiştirici de kompozisyon içinden çağrılmalıdır. Bu, değiştiricinin hiçbir zaman kompozisyonun dışına çıkarılamaması nedeniyle değiştiricinin kaldırılabileceği yeri sınırlar. Buna karşılık, daha kolay yeniden kullanım 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ışı uygulama
Modifier.Node
, Oluştur'da değiştirici oluşturmak için kullanılan daha düşük düzeyli bir API'dir. Bu API, Compose'un kendi değiştiricilerini uyguladığı API'dir ve özel değiştirici oluşturmanın en performanslı yoludur.
Modifier.Node
kullanarak özel değiştirici uygulama
Modifier.Node kullanarak özel bir değiştirici uygulamanın üç bölümü vardır:
- 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 ayrıntılı olarak açıklanan isteğe bağlı bir değiştirici fabrikası.
ModifierNodeElement
sınıfları durum bilgisizdir ve her yeniden derleme işleminde yeni örnekler ayrılır. Modifier.Node
sınıfları ise durum bilgisine sahip olabilir, birden fazla yeniden derleme işleminde hayatta kalır ve 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 iletilen renkle daireyi çizer.
Bir düğüm, Modifier.Node
'ün yanı sıra sıfır veya daha fazla düğüm türünü uygular. Değiştiricinizin gerektirdiği işleve bağlı olarak farklı düğüm türleri vardır. Yukarıdaki örneğin çizebilmesi gerekir. Bu nedenle, draw yöntemini geçersiz kılmasına olanak tanıyan DrawModifierNode
uygulanır.
Kullanılabilir türler şunlardır:
Düğüm |
Kullanım |
Örnek Bağlantı |
Sarmalanmış içeriğinin nasıl ölçüleceğini ve düzenleneceğini değiştiren bir |
||
Sayfa düzeninin alanına çizilen bir |
||
Bu arayüzü uygulamak, |
||
Test, erişilebilirlik ve benzer kullanım alanlarında kullanılmak üzere anlamsal anahtar/değer ekleyen bir |
||
PointerInputChanges alan bir |
||
Üst düzene veri sağlayan bir |
||
|
||
İçeriğin genel konumu değişmiş olduğunda düzenin son |
||
|
||
İşleri diğer Bu, birden fazla düğüm uygulamasını tek bir uygulamada derlemek için yararlı olabilir. |
||
|
İlgili öğelerinde update çağrısı yapıldığında düğümler otomatik olarak geçersiz kılınır. Örneğimiz DrawModifierNode
olduğundan, öğe üzerinde her güncelleme çağrıldığında düğüm bir yeniden çizimi tetikler ve rengi doğru şekilde güncellenir. Otomatik geçersiz kılma özelliğini aşağıda açıklandığı şekilde devre dışı bırakabilirsiniz.
ModifierNodeElement
ModifierNodeElement
, özel değiştiricinizi oluşturmak veya güncellemek için gerekli verileri barındıran 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ü örnekleyen işlevdir. Bu işlem, değiştiriciniz ilk uygulandığında düğümü oluşturmak için çağrılır. Bu genellikle düğümü oluşturmak ve değiştirici fabrikasına iletilen parametrelerle yapılandırmak anlamına gelir.update
: Bu işlev, bu değiştiricinin düğümün zaten bulunduğu aynı yerde sağlandığı ancak bir özelliğin değiştiği her zaman çağrılır. Bu değer, sınıfınequals
yöntemi tarafından 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 uygun olacak şekilde güncellemeniz gerekir. Düğümlerin bu şekilde yeniden kullanılabilmesi,Modifier.Node
'nin sağladığı performans kazanımları için çok ö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üncellenir.
Ayrıca, ModifierNodeElement
uygulamalarının equals
ve hashCode
uygulamalarını da uygulaması gerekir. update
yalnızca önceki öğeyle yapılan eşitlik karşılaştırması yanlış döndürürse çağrılır.
Yukarıdaki ö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 uyumluluk nedeniyle veri sınıflarından kaçınmak istiyorsanız equals
ve hashCode
'i (ö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 uygulamada, değiştirici öğesi oluşturulur ve değiştirici zincirine eklenir:
// Modifier factory fun Modifier.circle(color: Color) = this then CircleElement(color)
Tam örnek
Bu üç parça bir araya gelerek Modifier.Node
API'lerini kullanarak 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
kullanan 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.
Sıfır parametre
Değiştiricinizin parametresi yoksa hiçbir zaman güncellenmesi gerekmez ve ayrıca veri sınıfı olması gerekmez. Bir bileşiğe sabit miktarda dolgu uygulayan bir değiştiricinin örnek uygulaması aşağıda 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) } } }
Kompozisyon yerellerine referans verme
Modifier.Node
değiştiricileri, CompositionLocal
gibi Compose durum nesnelerindeki değişiklikleri otomatik olarak gözlemlemez. Modifier.Node
değiştiricilerinin, yalnızca composable fabrika ile oluşturulan değiştiricilere kıyasla avantajı, değiştiricinin ayrıldığı yere değil, kullanıcı arayüzü ağacınızda kullanıldığı yerdeki bestenin yerel değerini currentValueOf
kullanarak okuyabilmeleridir.
Ancak değiştirici düğüm örnekleri, durum değişikliklerini otomatik olarak gözlemlemez. Bir bestenin yerel değişimine otomatik olarak tepki vermek için bir kapsamdaki mevcut değerini okuyabilirsiniz:
DrawModifierNode
:ContentDrawScope
LayoutModifierNode
:MeasureScope
&IntrinsicMeasureScope
SemanticsModifierNode
:SemanticsPropertyReceiver
Bu örnekte, LocalContentColor
değerine göre arka plan çizmek için renginin değeri gözlemlenir. ContentDrawScope
, anlık görüntü değişikliklerini gözlemlediğinden, LocalContentColor
değeri değiştiğinde bu işlem otomatik olarak yeniden çizim yapar:
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
'daki 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
öğesine erişimi var. Bu sayede Compose Animatable APIs'in kullanılmasına olanak tanınmaktadır. Örneğin, bu snippet yukarıdaki CircleNode
öğesini tekrar tekrar görünür ve görünmez hale gelecek şekilde değiştirir:
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 verme özelliğini kullanarak değiştiriciler arasında durum paylaşımı
Modifier.Node
değiştiriciler diğer düğümlere yetki verebilir. Bunun, farklı değiştiricilerdeki yaygın uygulamaları ayıklama gibi pek çok kullanım alanı vardır ancak aynı zamanda değiştiriciler arasında ortak bir 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 bir 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ın kapsamı dışında kalma
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ışı 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, çizimi yalnızca color
gibi çizimle ilgili özelliklerde (ör. değişiklik yapıldığında) geçersiz kılmanıza ve düzeni geçersiz kılmanıza olanak tanır.
Bu, değiştiricinizin performansını artırabilir.
Bunun varsayıma dayalı bir örneği aşağıda verilmiştir. Bu örnekte, color
, size
ve onClick
lambda'sı olan bir değiştirici gösterilmektedir. Bu değiştirici yalnızca gerekli olanları geçersiz kılar ve gerekli olmayan geçersiz kılmaları 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) } } }