Indication ve Ripple API'lerine taşıma

Modifier.clickable kullanan etkileşimli bileşenlerin oluşturma performansını iyileştirmek için yeni API'ler kullanıma sunduk. Bu API'ler, dalgalar 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()

Bunun yerine Material kitaplıklarında yeni ripple() API'leri sunulur.

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.'yi ifade eder.

RippleTheme

Şu iki yöntemden birini kullanın:

  • Material kitaplığı RippleConfiguration API'lerini kullanın veya
  • Kendi tasarım sistemi dalga 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 dalga davranışı değişikliği vardır:

  • 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 dalga API'lerini kullanıyorlar. 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'dan ripple'a taşıma

Materyal kitaplığı kullanma

Material kitaplığı kullanıyorsanız rememberRipple() öğesini doğrudan ilgili kitaplıktan ripple() çağrısıyla değiştirin. Bu API, Materyal Teması Oluşturma API'lerinden türetilen değerleri kullanarak dalga efekti 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 şu şekilde değiştirmeniz gerekir:

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

ripple() artık bir composable işlev değildir ve hatırlanması gerekmez. Değiştiricilere benzer şekilde, birden fazla bileşende de yeniden kullanılabilir. Bu nedenle, tahsisatları kaydetmek için dalga oluşturma işlemini üst düzey bir değere ayırmayı düşünebilirsiniz.

Özel tasarım sistemi uygulanıyor

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

RippleTheme'ten taşıma

Belirli bir bileşen için dalga efektini devre dışı bırakmak üzere RippleTheme kullanma

material ve material3 kitaplıkları, bir alt ağaçtaki dalgaların görünümünü yapılandırmanıza olanak tanıyan RippleConfiguration ve LocalRippleConfiguration öğelerini gösterir. RippleConfiguration ve LocalRippleConfiguration yalnızca bileşen bazında özelleştirme için tasarlanmıştır. Bu API'ler, genel/tema genelinde özelleştirmeyi desteklemez. Bu kullanım alanı hakkında daha fazla bilgi için Bir uygulamadaki tüm dalgaları 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 şu şekilde değiştirmeniz gerekir:

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

Belirli bir bileşenin dalga rengini/alfa değerini değiştirmek için RippleTheme kullanma

Önceki bölümde açıklandığı gibi, RippleConfiguration ve LocalRippleConfiguration yalnızca bileşen bazında ö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 şu şekilde değiştirmeniz gerekir:

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

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

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

Daha önce, dalga davranışını tema genelinde tanımlamak için LocalRippleTheme kullanabiliyordunuz. Bu, temel olarak özel tasarım sistemi oluşturma yerelleri ile dalga efekti arasında bir entegrasyon noktasıydı. material-ripple, genel bir tema öğesi göstermek yerine artık bir createRippleModifierNode() işlevi gösteriyor. Bu işlev, tasarım sistemi kitaplıklarının daha yüksek düzeyde wrapper uygulaması oluşturmasına olanak tanır. Bu uygulama, tema değerlerini sorgular ve ardından dalga uygulamasını bu işlev tarafından oluşturulan düğüme devreder.

Bu sayede tasarım sistemleri, ihtiyaç duydukları bilgileri doğrudan sorgulayabilir ve material-ripple katmanında sağlananlara uymak zorunda kalmadan, kullanıcı tarafından yapılandırılabilen gerekli tema katmanlarını üstte gösterebilir. Bu değişiklik, dalganın hangi temaya/spesifikasyona uyduğunu da daha net bir şekilde gösterir. Çünkü bu sözleşmeyi tanımlayan, temadan dolaylı olarak türetilmek yerine dalga API'sinin kendisidir.

Yol göstermesi için Material kitaplıklarındaki dalga API'si uygulamasına bakın ve Material oluşturma yerel öğelerine yapılan çağrıları kendi tasarım sisteminiz için gerektiği şekilde değiştirin.

Indication'dan IndicationNodeFactory'a taşıma

Indication civarından geçiyor

Yalnızca başka bir yere aktarmak için Indication oluşturuyorsanız (ör. Modifier.clickable veya Modifier.indication'ye aktarmak için dalga oluşturuyorsanız) herhangi bir değişiklik yapmanız gerekmez. IndicationNodeFactory, Indication'den devralındığı için her şey derlenmeye ve çalışmaya devam eder.

Indication oluşturuluyor

Kendi Indication uygulamanızı oluşturuyorsanız taşıma işlemi çoğu durumda basit 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()
        }
    }
}

Bu verileri iki adımda taşıyabilirsiniz:

  1. ScaleIndicationInstance adlı öğeyi DrawModifierNode olarak taşıyın. DrawModifierNode için API yüzeyi, IndicationInstance ile çok benzerdir: İşlevsel olarak IndicationInstance#drawContent() ile eşdeğer olan bir ContentDrawScope#draw() işlevi 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 şu şekilde değiştirmeniz gerekir:

    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 özelliğini uygulamak için ScaleIndication özelliğini taşıyın. Toplama mantığı artık düğüme taşındığı için 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 şu şekilde değiştirmeniz gerekir:

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

IndicationInstance oluşturmak için Indication kullanma

Çoğu durumda, bir bileşen için Indication değerini göstermek üzere Modifier.indication kullanmanız gerekir. Ancak nadiren de olsa rememberUpdatedInstance kullanarak manuel olarak IndicationInstance oluşturuyorsanız daha basit bir uygulama kullanabilmek için Indication öğesinin IndicationNodeFactory olup olmadığını kontrol edecek şekilde uygulamanızı güncellemeniz gerekir. Örneğin, Modifier.indication, IndicationNodeFactory ise oluşturulan düğüme dahili olarak yetki verir. Aksi takdirde rememberUpdatedInstance numarasını aramak için Modifier.composed kullanılır.