Özel değiştiriciler oluşturma

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.
  • 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ı

LayoutModifierNode

Sarmalanmış içeriğin ölçülme ve düzenlenme şeklini değiştiren bir Modifier.Node.

Örnek

DrawModifierNode

Düzenin alanına çizim yapan bir Modifier.Node.

Örnek

CompositionLocalConsumerModifierNode

Bu arayüzün uygulanması Modifier.Node cihazınızın beste yerellerini okumasına olanak sağlar.

Örnek

SemanticsModifierNode

Test, erişilebilirlik ve benzer kullanım alanlarında kullanılmak üzere anlamsal anahtar/değer çifti ekleyen Modifier.Node.

Örnek

PointerInputModifierNode

PointerInputChanges alan bir Modifier.Node.

Örnek

ParentDataModifierNode

Üst düzene veri sağlayan bir Modifier.Node.

Örnek

LayoutAwareModifierNode

onMeasured ve onPlaced geri çağırmaları alan bir Modifier.Node.

Örnek

GlobalPositionAwareModifierNode

İçeriğin genel konumu değişmiş olabileceği zaman düzenin son LayoutCoordinates öğesiyle birlikte onGloballyPositioned geri çağırması alan bir Modifier.Node.

Örnek

ObserverModifierNode

ObserverNode uygulayan Modifier.Node'ler, bir observeReads bloğu içinde okunan anlık görüntü nesnelerinde yapılan değişikliklere yanıt olarak çağrılacak olan kendi onObservedReadsChanged uygulamasını sağlayabilir.

Örnek

DelegatingNode

Diğer Modifier.Node örneklerine iş yetkisi verebilen bir Modifier.Node.

Bu, birden fazla düğüm uygulamasını tek bir düğümde oluşturmak için yararlı olabilir.

Örnek

TraversableNode

Modifier.Node sınıflarının, aynı türdeki sınıflar veya belirli bir anahtar için düğüm ağacında yukarı/aşağı geçiş yapmasına izin verir.

Örnek

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:

  1. 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.
  2. 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ın equals 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ılabilmesi Modifier.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:

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)
        }
    }
}