Indication ve Ripple API'lerine taşıma

Modifier.clickable kullanan etkileşimli bileşenlerin oluşturma performansını artırmak için yeni API'ler kullanıma sunduk. Bu API'ler, dalgalanma gibi daha verimli Indication uygulamalara olanak tanır.

androidx.compose.foundation:foundation:1.7.0+ ve androidx.compose.material:material-ripple:1.7.0+ aşağıdaki API değişikliklerini içerir:

Kullanımdan kaldırıldı

Değiştirme

Indication#rememberUpdatedInstance

IndicationNodeFactory

rememberRipple()

Yeni ripple() API'ler artık Materyal kitaplıklarında sunuluyor.

Not: Bu bağlamda "Materyal kitaplıkları" ifadesi androidx.compose.material:material, androidx.compose.material3:material3, androidx.wear.compose:compose-material ve androidx.wear.compose:compose-material3. için geçerlidir.

RippleTheme

Şu iki yöntemden birini kullanın:

  • Material kitaplığı RippleConfiguration API'lerini kullanın veya
  • Kendi tasarım sistemi dalgalanma uygulamanızı oluşturma

Bu sayfada, davranış değişikliğinin etkisi ve yeni API'lere geçişle ilgili talimatlar açıklanmaktadır.

Davranış değişikliği

Aşağıdaki kitaplık sürümlerinde dalgalanma davranışıyla ilgili bir değişiklik yapıldı:

  • androidx.compose.material:material:1.7.0+
  • androidx.compose.material3:material3:1.3.0+
  • androidx.wear.compose:compose-material:1.4.0+

Material kitaplıklarının bu sürümleri artık rememberRipple() kullanmıyor. Bunun yerine yeni dalgalanma API'lerini kullanıyor. Bu nedenle LocalRippleTheme sorgusu yapmazlar. Bu nedenle, uygulamanızda LocalRippleTheme değerini ayarlarsanız Material bileşenleri bu değerleri kullanmaz.

Aşağıdaki bölümlerde yeni API'lere nasıl geçiş yapılacağı açıklanmaktadır.

rememberRipple alanından ripple alanına taşıma

Malzeme kitaplığı kullanma

Material kitaplığı kullanıyorsanız rememberRipple() öğesini doğrudan ilgili kitaplıktan ripple() çağrısıyla değiştirin. Bu API, Material teması API'lerinden türetilen değerleri kullanarak bir dalgalanma oluşturur. Ardından, döndürülen nesneyi Modifier.clickable ve/veya diğer bileşenlere iletin.

Örneğin, aşağıdaki snippet'te desteği sonlandırılmış API'ler kullanılmaktadır:

Box(
    Modifier.clickable(
        onClick = {},
        interactionSource = remember { MutableInteractionSource() },
        indication = rememberRipple()
    )
) {
    // ...
}

Yukarıdaki snippet'i aşağıdaki gibi değiştirmelisiniz:

@Composable
private fun RippleExample() {
    Box(
        Modifier.clickable(
            onClick = {},
            interactionSource = remember { MutableInteractionSource() },
            indication = ripple()
        )
    ) {
        // ...
    }
}

ripple() artık birleştirilebilir bir işlev değildir ve hatırlanması gerekmez. Ayrıca, değiştiricilere benzer şekilde birden fazla bileşende yeniden kullanılabilir. Bu nedenle, tahsisleri kaydetmek için dalgalanma oluşturma işlemini üst düzey bir değere çıkarmanız önerilir.

Özel tasarım sistemini uygulama

Kendi tasarım sisteminizi uyguluyorsanız ve daha önce dalgalanmayı yapılandırmak için özel bir RippleTheme ile birlikte rememberRipple() kullanıyorsanız bunun yerine material-ripple içinde kullanıma sunulan dalgalanma düğümü API'lerine temsilci atayan kendi dalgalanma API'nizi sağlamanız gerekir. Ardından, bileşenleriniz tema değerlerinizi doğrudan kullanan kendi dalgalanma efektinizi kullanabilir. Daha fazla bilgi için RippleTheme başlıklı makaleyi inceleyin.

Taşıma kaynağı: RippleTheme

Belirli bir bileşenin dalgalanmasını devre dışı bırakmak için RippleTheme kullanma

material ve material3 kitaplıkları, bir alt ağaçtaki dalgalanmaların görünümünü yapılandırmanıza olanak tanıyan RippleConfiguration ve LocalRippleConfiguration öğelerini kullanıma sunar. RippleConfiguration ve LocalRippleConfiguration öğelerinin yalnızca bileşen başına özelleştirme için kullanıldığını unutmayın. Bu API'lerde genel/tema genelinde özelleştirme desteklenmez. Bu kullanım alanı hakkında daha fazla bilgi için Bir uygulamadaki tüm dalgalanmaları genel olarak değiştirmek için RippleTheme kullanma başlıklı makaleyi inceleyin.

Örneğin, aşağıdaki snippet'te desteği sonlandırılmış API'ler kullanılmaktadır:

private object DisabledRippleTheme : RippleTheme {

    @Composable
    override fun defaultColor(): Color = Color.Transparent

    @Composable
    override fun rippleAlpha(): RippleAlpha = RippleAlpha(0f, 0f, 0f, 0f)
}

// ...
    CompositionLocalProvider(LocalRippleTheme provides DisabledRippleTheme) {
        Button {
            // ...
        }
    }

Yukarıdaki snippet'i aşağıdaki gibi değiştirmelisiniz:

CompositionLocalProvider(LocalRippleConfiguration provides null) {
    Button {
        // ...
    }
}

Belirli bir bileşenin dalgalanma rengini/alfasını değiştirmek için RippleTheme kullanma

Önceki bölümde açıklandığı gibi, RippleConfiguration ve LocalRippleConfiguration yalnızca bileşen başına özelleştirme için tasarlanmıştır.

Örneğin, aşağıdaki snippet'te desteği sonlandırılmış API'ler kullanılmaktadır:

private object DisabledRippleThemeColorAndAlpha : RippleTheme {

    @Composable
    override fun defaultColor(): Color = Color.Red

    @Composable
    override fun rippleAlpha(): RippleAlpha = MyRippleAlpha
}

// ...
    CompositionLocalProvider(LocalRippleTheme provides DisabledRippleThemeColorAndAlpha) {
        Button {
            // ...
        }
    }

Yukarıdaki snippet'i aşağıdaki gibi değiştirmelisiniz:

@OptIn(ExperimentalMaterialApi::class)
private val MyRippleConfiguration =
    RippleConfiguration(color = Color.Red, rippleAlpha = MyRippleAlpha)

// ...
    CompositionLocalProvider(LocalRippleConfiguration provides MyRippleConfiguration) {
        Button {
            // ...
        }
    }

Bir uygulamadaki tüm dalgalanmaları genel olarak değiştirmek için RippleTheme kullanma

Daha önce, tema genelinde dalgalanma davranışını tanımlamak için LocalRippleTheme kullanılabiliyordu. Bu, temelde özel tasarım sistemi kompozisyon yerelleri ile ripple arasındaki bir entegrasyon noktasıydı. material-ripple, genel bir tema oluşturma öğesi sunmak yerine artık bir createRippleModifierNode() işlevi sunuyor. Bu işlev, tasarım sistemi kitaplıklarının daha yüksek düzeyde wrapper uygulama oluşturmasına olanak tanır. Bu uygulamalar, tema değerlerini sorgular ve ardından dalgalanma uygulamasını bu işlev tarafından oluşturulan düğüme devreder.

Bu sayede tasarım sistemleri, ihtiyaç duyduklarını doğrudan sorgulayabilir ve material-ripple katmanında sağlananlara uymak zorunda kalmadan gerekli kullanıcı tarafından yapılandırılabilen temalandırma katmanlarını üstte gösterebilir. Bu değişiklik, dalgalanmanın hangi temaya/şartnameye uygun olduğunu daha açık hale getirir. Çünkü bu sözleşmeyi tema değil, dalgalanma API'si tanımlar.

Yol gösterici bilgiler için Material kitaplıklarındaki ripple API uygulamasını inceleyin ve Material composition yerellerine yapılan çağrıları kendi tasarım sisteminize göre gerektiği şekilde değiştirin.

Indication alanından IndicationNodeFactory alanına taşıma

Indication civarında geçiş

Yalnızca Indication oluşturup paylaşmak istiyorsanız (ör. Modifier.clickable veya Modifier.indication'a göndermek için dalga oluşturma) herhangi bir değişiklik yapmanız gerekmez. IndicationNodeFactory, Indication öğesinden devralındığı için her şey derlenmeye ve çalışmaya devam eder.

Indication oluşturuluyor

Kendi Indication uygulamanızı oluşturuyorsanız çoğu durumda taşıma işlemi kolay olacaktır. Örneğin, basıldığında ölçek efekti uygulayan bir Indication düşünün:

object ScaleIndication : Indication {
    @Composable
    override fun rememberUpdatedInstance(interactionSource: InteractionSource): IndicationInstance {
        // key the remember against interactionSource, so if it changes we create a new instance
        val instance = remember(interactionSource) { ScaleIndicationInstance() }

        LaunchedEffect(interactionSource) {
            interactionSource.interactions.collectLatest { interaction ->
                when (interaction) {
                    is PressInteraction.Press -> instance.animateToPressed(interaction.pressPosition)
                    is PressInteraction.Release -> instance.animateToResting()
                    is PressInteraction.Cancel -> instance.animateToResting()
                }
            }
        }

        return instance
    }
}

private class ScaleIndicationInstance : IndicationInstance {
    var currentPressPosition: Offset = Offset.Zero
    val animatedScalePercent = Animatable(1f)

    suspend fun animateToPressed(pressPosition: Offset) {
        currentPressPosition = pressPosition
        animatedScalePercent.animateTo(0.9f, spring())
    }

    suspend fun animateToResting() {
        animatedScalePercent.animateTo(1f, spring())
    }

    override fun ContentDrawScope.drawIndication() {
        scale(
            scale = animatedScalePercent.value,
            pivot = currentPressPosition
        ) {
            this@drawIndication.drawContent()
        }
    }
}

Bunu iki adımda taşıyabilirsiniz:

  1. ScaleIndicationInstance, DrawModifierNode olarak taşınır. DrawModifierNode için API yüzeyi, IndicationInstance ile çok benzerdir: IndicationInstance#drawContent() ile işlevsel olarak eşdeğer olan bir ContentDrawScope#draw() işlevini kullanıma sunar. Bu işlevi değiştirmeniz ve ardından collectLatest mantığını Indication yerine doğrudan düğümün içinde uygulamanız gerekir.

    Örneğin, aşağıdaki snippet'te desteği sonlandırılmış API'ler kullanılmaktadır:

    private class ScaleIndicationInstance : IndicationInstance {
        var currentPressPosition: Offset = Offset.Zero
        val animatedScalePercent = Animatable(1f)
    
        suspend fun animateToPressed(pressPosition: Offset) {
            currentPressPosition = pressPosition
            animatedScalePercent.animateTo(0.9f, spring())
        }
    
        suspend fun animateToResting() {
            animatedScalePercent.animateTo(1f, spring())
        }
    
        override fun ContentDrawScope.drawIndication() {
            scale(
                scale = animatedScalePercent.value,
                pivot = currentPressPosition
            ) {
                this@drawIndication.drawContent()
            }
        }
    }

    Yukarıdaki snippet'i aşağıdaki gibi değiştirmelisiniz:

    private class ScaleIndicationNode(
        private val interactionSource: InteractionSource
    ) : Modifier.Node(), DrawModifierNode {
        var currentPressPosition: Offset = Offset.Zero
        val animatedScalePercent = Animatable(1f)
    
        private suspend fun animateToPressed(pressPosition: Offset) {
            currentPressPosition = pressPosition
            animatedScalePercent.animateTo(0.9f, spring())
        }
    
        private suspend fun animateToResting() {
            animatedScalePercent.animateTo(1f, spring())
        }
    
        override fun onAttach() {
            coroutineScope.launch {
                interactionSource.interactions.collectLatest { interaction ->
                    when (interaction) {
                        is PressInteraction.Press -> animateToPressed(interaction.pressPosition)
                        is PressInteraction.Release -> animateToResting()
                        is PressInteraction.Cancel -> animateToResting()
                    }
                }
            }
        }
    
        override fun ContentDrawScope.draw() {
            scale(
                scale = animatedScalePercent.value,
                pivot = currentPressPosition
            ) {
                this@draw.drawContent()
            }
        }
    }

  2. IndicationNodeFactory'ı uygulamak için ScaleIndication'ı taşıyın. Toplama mantığı artık düğüme taşındığından bu, tek sorumluluğu bir düğüm örneği oluşturmak olan çok basit bir fabrika nesnesidir.

    Örneğin, aşağıdaki snippet'te desteği sonlandırılmış API'ler kullanılmaktadır:

    object ScaleIndication : Indication {
        @Composable
        override fun rememberUpdatedInstance(interactionSource: InteractionSource): IndicationInstance {
            // key the remember against interactionSource, so if it changes we create a new instance
            val instance = remember(interactionSource) { ScaleIndicationInstance() }
    
            LaunchedEffect(interactionSource) {
                interactionSource.interactions.collectLatest { interaction ->
                    when (interaction) {
                        is PressInteraction.Press -> instance.animateToPressed(interaction.pressPosition)
                        is PressInteraction.Release -> instance.animateToResting()
                        is PressInteraction.Cancel -> instance.animateToResting()
                    }
                }
            }
    
            return instance
        }
    }

    Yukarıdaki snippet'i aşağıdaki gibi değiştirmelisiniz:

    object ScaleIndicationNodeFactory : IndicationNodeFactory {
        override fun create(interactionSource: InteractionSource): DelegatableNode {
            return ScaleIndicationNode(interactionSource)
        }
    
        override fun hashCode(): Int = -1
    
        override fun equals(other: Any?) = other === this
    }

Indication kullanarak IndicationInstance oluşturma

Çoğu durumda, bir bileşen için Indication öğesini görüntülemek üzere Modifier.indication kullanmalısınız. Ancak nadir durumlarda IndicationInstance öğesini rememberUpdatedInstance kullanarak manuel olarak oluşturuyorsanız daha hafif bir uygulama kullanabilmek için uygulamanızı Indication öğesinin IndicationNodeFactory olup olmadığını kontrol edecek şekilde güncellemeniz gerekir. Örneğin, Modifier.indication, IndicationNodeFactory ise dahili olarak oluşturulan düğüme devreder. Aksi takdirde, rememberUpdatedInstance'ı aramak için Modifier.composed kullanılır.