Özel değiştiriciler oluşturma

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ştiricilerin birden çok bölümü vardır:

  • Değiştirici fabrika
    • Bu, Modifier üzerinde deyimsel API sağlayan bir uzantı işlevidir. değiştiricilerinizin kolayca birbirine bağlanmasını sağlar. İlgili içeriği oluşturmak için kullanılan değiştiricisi fabrika, Compose tarafından değişiklik yapmak için kullanılan değiştirici öğeleri üretir emin olun.
  • Değiştirici öğe
    • Değiştiricinizin davranışını burada uygulayabilirsiniz.

Özel değiştiriciyi uygulamanın, kullanım şeklinize göre emin olmanız gerekir. Özel değiştiriciyi uygulamanın en kolay yolu genellikle önceden tanımlanmış diğer öğeleri birleştiren bir özel değiştirici fabrika uygulaması birlikte kullanır. Daha fazla özel davranışa ihtiyacınız varsa daha düşük düzeyde ancak Modifier.Node API'lerini kullanan bir değiştirici öğe daha fazla esneklik sağlayabilir.

Mevcut değiştiricileri birbirine bağlayın

Özel değiştiricileri oluşturmak çoğu zaman yalnızca mevcut kullanabilirsiniz. Örneğin Modifier.clip(), graphicsLayer değiştiricisi. Bu stratejide mevcut değiştirici öğeler kullanılır ve kendi özel değiştirici fabrikanızı sağlayın.

Kendi özel değiştiricinizi uygulamadan önce, aynı düzenleyiciyi kullanıp kullanamayacağınıza bakın üzerine konuşacağız.

fun Modifier.clip(shape: Shape) = graphicsLayer(shape = shape, clip = true)

Aynı değiştirici grubu sık sık tekrar ediyorsanız bunları kendi değiştiricinizde toplayın:

fun Modifier.myBackground(color: Color) = padding(16.dp)
    .clip(RoundedCornerShape(8.dp))
    .background(color)

Fabrika ayarı kullanarak özel değiştirici oluşturun

Değerleri iletmek için composable işlev kullanarak özel bir değiştirici de oluşturabilirsiniz. mevcut bir değiştiriciye dönüştürülebilir. Bu, composable değiştirici fabrikası olarak bilinir.

Değiştirici oluşturmak için composable değiştirici fabrika kullanmak, animate*AsState ve diğer Compose'lar gibi daha üst düzey yazma API'leri durum destekli animasyon API'leri hakkında daha fazla bilgi edinin. Örneğin, aşağıdaki snippet'te bir etkinleştirildiğinde/devre dışı bırakıldığında alfa değişikliği canlandıran değiştiricisi:

@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, bunu uygulamanın en kolay yolu bir composable değiştirici fabrikası:

@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 bulunmaktadır.

CompositionLocal değerleri, değiştirici fabrikanın çağrı sitesinde çözümlenir

Besteci değiştirici fabrikası kullanarak özel değiştirici oluştururken kompozisyonun içeriği yerel kullanıcılar değeri, oluşturuldukları kompozisyon ağacından alır, kullanılır. Bu durum beklenmedik sonuçlara yol açabilir. Örneğin, bu bölümün yukarıdaki yerel değiştirici örneği, composable işlev:

@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 özel bir Kompozisyonun yerel halkı olacağı için bunun yerine Modifier.Node yerinde doğru şekilde çözülür ve güvenli bir şekilde kaldırılabilir.

Özelleştirilebilir işlev değiştiricileri hiçbir zaman atlanmaz

Birleştirilebilir fabrika değiştiricileri, composable işlevler nedeniyle hiçbir zaman atlanmaz döndürülen URL'ler atlanamaz. Bu, değiştirici fonksiyonunuzun her yeniden bestede çağrılacaktır; bu da yeniden oluşturulursa pahalı olabilir alabilir.

Özelleştirilebilir işlev değiştiricileri, composable işlev içinde çağrılmalıdır

Tüm composable işlevler gibi bir composable fabrika değiştiricisi de müzakere tekniği de eklediniz. Bu, bir değiştiricinin kaldırılabileceği yerleri asla düzenlemenin dışına çıkmamalıdır. Buna karşılık, composable olmayan değiştirici fabrikalar, composable fonksiyonların dışına çıkarılarak yeniden kullanım ve performansı artırmak için:

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ştirici oluşturmak için kullanılan alt düzey bir API'dir. Google Compose'un kendi değiştiricilerini uyguladığı API ile aynıdır. en iyi performans gösteren yöntemidir.

Modifier.Node kullanarak özel değiştirici uygulayın

Değiştirici.Düğüm'ü kullanarak özel değiştirici uygulamanın üç bölümü vardır:

  • Mantık ve kapsayıcılığın geçerli olduğu bir Modifier.Node uygulaması değiştiricinizin durumuna göre değişir.
  • Değiştirici oluşturan ve güncelleyen bir ModifierNodeElement düğüm örneği olabilir.
  • Yukarıda açıklandığı şekilde isteğe bağlı bir değiştirici fabrikası.

ModifierNodeElement sınıf durum bilgisizdir ve her bir sınıfa yeni örnekler ayrılır yeniden oluşturma işlemi sırasında, Modifier.Node sınıfları durum bilgili olabilir ve yeniden bileşime dahil edilir ve hatta yeniden kullanılabilir.

Aşağıdaki bölümde her bölüm açıklanmış ve bir özel değiştiriciyi tıklayın.

Modifier.Node

Modifier.Node uygulaması (bu örnekte CircleNode) "the" işlevleri ekleyin.

// Modifier.Node
private class CircleNode(var color: Color) : DrawModifierNode, Modifier.Node() {
    override fun ContentDrawScope.draw() {
        drawCircle(color)
    }
}

Bu örnekte, değiştiriciye aktarılan renk ile daireyi çiziyor işlevini kullanın.

Bir düğüm, Modifier.Node özelliğinin yanı sıra sıfır veya daha fazla düğüm türünü de uygular. Her biri 100'den az gösterim alan değiştiricinizin gerektirdiği işlevlere göre farklı düğüm türleri seçin. İlgili içeriği oluşturmak için kullanılan örneğinin çizim yapabilmesi gerekir; bu nedenle DrawModifierNode öğesini uygular. Bu, çizim yöntemini geçersiz kılmasına olanak tanır.

Kullanılabilir türler şunlardır:

Düğüm

Kullanım

Örnek Bağlantı

LayoutModifierNode

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

Örnek

DrawModifierNode

Düzenin alanını kullanan bir Modifier.Node.

Örnek

CompositionLocalConsumerModifierNode

Bu arayüzün uygulanması, Modifier.Node cihazınızın kompozisyon yerellerini okumasına olanak tanır.

Örnek

SemanticsModifierNode

Test, erişilebilirlik ve benzer kullanım alanlarında kullanım için 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ğırma alan bir Modifier.Node.

Örnek

GlobalPositionAwareModifierNode

İçeriğin genel konumu değişmiş olduğunda düzenin son LayoutCoordinates ile birlikte onGloballyPositioned geri çağırması alan Modifier.Node.

Örnek

ObserverModifierNode

ObserverNode uygulayan Modifier.Node'lar, observeReads blokunda okunan anlık görüntü nesnelerinde yapılan değişikliklere yanıt olarak çağrılacak 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ın tek bir uygulamada oluşturulması yararlı olabilir.

Örnek

TraversableNode

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

Örnek

Karşılık gelen konumlarda güncelleme çağrıldığında düğümler otomatik olarak geçersiz kılınır öğesine dokunun. Örneğimiz bir DrawModifierNode olduğundan, her zaman güncellemesi düğüm, bir yeniden çizimi tetikler ve rengi doğru şekilde güncellenir. Evet aşağıda açıklandığı şekilde otomatik geçersiz kılmayı devre dışı bırakmanızı öneririz.

ModifierNodeElement

ModifierNodeElement, oluşturulacak verileri barındıran sabit bir sınıftır özel değiştiricinizi güncelleyin:

// 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:

  1. create: Bu, değiştirici düğümünüzü örneklendiren işlevdir. Bu, , değiştiriciniz ilk uygulandığında düğümü oluşturmak için çağrılır. Genellikle bu, düğüm oluşturmak ve onu devre dışı bırakmak için kullanılan parametrelerle değiştirici fabrikaya geçirildi.
  2. update: Bu işlev, bu düğüm zaten var, ancak bir özellik değişmiş. Bu sınıfın equals yöntemi tarafından belirlenir. Önceki değiştirici düğüm daha önce oluşturulmuş olanlar update çağrısına parametre olarak gönderilir. Bu noktada düğümlerinizi güncellemeniz gerekir. karşılık gelecek şekilde, parametreleridir. Düğümlerin bu şekilde yeniden kullanılabilmesi Modifier.Node sağlanan performans kazancı; bu nedenle, yeni bir düğüm oluşturmak yerine update yöntemini kullanmanız gerekir. Şurada: daire örneğinde, düğümün rengi güncellenir.

Ayrıca, ModifierNodeElement uygulamalarının equals ve hashCode. update, yalnızca önceki öğe false (yanlış) değerini döndürür.

Yukarıdaki örnekte, bunu başarmak için bir veri sınıfı kullanılmaktadır. Bu yöntemler, bir düğümün güncellenmesi gerekip gerekmediğini kontrol edebilirsiniz. Öğenizin özellikleri Bir düğümün güncellenmesi gerekip gerekmediğine veya verilerden kaçınmak istediğinize uyumlu olması durumunda, manuel olarak equals uygulayabilirsiniz. ve hashCode ör. dolgu değiştirici öğesini kullanın.

Değiştirici fabrikası

Bu, değiştiricinizin herkese açık API yüzeyidir. Çoğu uygulama, değiştirici öğeyi oluşturun ve değiştirici zincirine ekleyin:

// Modifier factory
fun Modifier.circle(color: Color) = this then CircleElement(color)

Tam örnek

Bu üç bölüm bir araya getirilerek özel değiştiriciyi oluşturarak daire çizin (Modifier.Node API'leri) kullanarak:

// 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 şunlardır: bahsedeceğim.

Sıfır parametre

Değiştiricinizde parametre yoksa hiçbir zaman güncellenmesi gerekmez. bir veri sınıfı olması gerekmez. Burada, örnek bir uygulama composable'a sabit miktarda dolgu uygulayan bir değiştiricinin örneğidir:

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, Oluştur durumundaki değişiklikleri otomatik olarak gözlemlemez nesneler (CompositionLocal gibi). Modifier.Node değiştiricilerinin daha fazla avantajı değiştiricilerinden biri, composable fabrikasında değiştiricinin kullanıcı arayüzünde kullanıldığı yerin yerel değeri değiştiricinin ayrıldığı yer değil, currentValueOf kullanılır.

Bununla birlikte, değiştirici düğüm örnekleri durum değişikliklerini otomatik olarak gözlemlemez. Alıcı: bir bestenin yerel değişikliklerine otomatik olarak tepki verdiğini gösterirse, bir kapsamdaki değeri belirtin:

Bu örnekte, temel olarak temelli arka plan çizmek için LocalContentColor değeri gösterilmektedir rengine bağlıyorum. ContentDrawScope anlık görüntü değişiklikleri gözlemlediğinden, LocalContentColor değeri değiştiğinde 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ştiricisi varsa ObserverModifierNode kullanın.

Örneğin, Modifier.scrollable bu tekniği şu amaçlarla kullanır: LocalDensity metriğindeki değişiklikleri gözlemleyin. Aşağıda 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)
    }
}

Animasyon değiştirici

Modifier.Node uygulamalarının coroutineScope öğesine erişimi var. Bu da Oluşturulabilir API'lerin kullanımı. Örneğin, bu snippet Yukarıdan arka arkaya şeffaflaşma ve azalma için CircleNode:

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

Yetkiyi kullanarak değiştiriciler arasında paylaşım durumu

Modifier.Node değiştiricileri başka düğümlere yetki verebilir. Pek çok kullanım alanı vardır Örneğin, farklı değiştiricilerdeki yaygın uygulamaları ayıklama ve ancak değiştiriciler arasında ortak bir durumu paylaşmak için de kullanılabilir.

Örneğin, aynı öğenin birden fazla kez kullanıldığı tıklanabilir etkileşim verileri:

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üğüm, karşılık gelen ModifierNodeElement araması güncellemesi. Bazen daha karmaşık bir değiştiricide kontrol edebilmek için bu davranışı devre dışı bırakmak değiştiriciniz aşamaları geçersiz kılar.

Bu, özel değiştiriciniz hem düzeni hem de çizin. Otomatik geçersiz kılmayı devre dışı bırakmak, yalnızca şu durumlarda çizimi geçersiz kılmanıza olanak tanır: yalnızca color gibi çizimle ilgili özellikleri değiştirin, düzeni geçersiz kılmayın. Bu, değiştiricinizin performansını artırabilir.

Bunun varsayımsal bir örneği, color değerine sahip bir değiştiriciyle gösterilmiştir. size ve onClick lambda özellikleri. Bu değiştirici yalnızca gerekir ve aşağıdaki 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)
        }
    }
}