Indication ve Ripple API'lerine taşıma

Modifier.clickable kullanan etkileşimli bileşenlerin bileşim performansını iyileştirmek için yeni API'leri kullanıma sunduk. Bu API'ler, dalgalar gibi daha verimli Indication uygulamalarına 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çeriyor:

Kullanımdan kaldırıldı

Vekil

Indication#rememberUpdatedInstance

IndicationNodeFactory

rememberRipple()

Bunun yerine Materyal kitaplıklarında yeni ripple() API'leri sağlandı.

Not: Bu bağlamda "Malzeme kitaplıkları", androidx.compose.material:material, androidx.compose.material3:material3, androidx.wear.compose:compose-material ve androidx.wear.compose:compose-material3. terimlerini ifade eder.

RippleTheme

Şu iki yöntemden birini kullanın:

  • Malzeme 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ş talimatları açıklanmaktadır.

Davranış değişikliği

Aşağıdaki kitaplık sürümleri dalga davranışı değişikliği içerir:

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

Materyal kitaplıklarının bu sürümlerinde artık rememberRipple() kullanılmaz; bunun yerine yeni Ripple API'leri kullanılır. Dolayısıyla bu kullanıcı LocalRippleTheme sorgusunu sorgulamaz. Dolayısıyla, uygulamanızda LocalRippleTheme değerini ayarlarsanız Materyal bileşenleri bu değerleri kullanmaz.

Aşağıdaki bölümde, geçiş yapmadan geçici olarak eski davranışı nasıl geri alabileceğiniz açıklanmaktadır. Ancak yeni API'lere geçiş yapmanızı öneririz. Taşıma talimatları için rememberRipple'den ripple'e taşıma başlıklı makaleye ve sonraki bölümlere göz atın.

Taşıma yapmadan Materyal Kitaplığı sürümünü yükseltme

Kitaplık sürümlerinin yükseltilmesini engellemek için geçici LocalUseFallbackRippleImplementation CompositionLocal API'sini kullanarak Materyal bileşenlerini eski davranışa geri dönecek şekilde yapılandırabilirsiniz:

CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) {
    MaterialTheme {
        App()
    }
}

Eski dalgaların LocalIndication aracılığıyla sağlanabilmesi için bunu MaterialTheme öğesinin dışında sağladığınızdan emin olun.

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

rememberRipple'den ripple'a taşıyın

Materyal kitaplığını kullanma

Malzeme kitaplığı kullanıyorsanız rememberRipple() kısmını doğrudan ilgili kitaplıktan ripple() çağrısıyla değiştirin. Bu API, Materyal tema API'lerinden türetilen değerleri kullanarak bir dalga oluşturur. Daha sonra, döndürülen nesneyi Modifier.clickable ve/veya diğer bileşenlere geçirin.

Örneğin, aşağıdaki snippet'te kullanımdan kaldırılmış API'ler kullanılmaktadır:

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

Yukarıdaki snippet'i şu şekilde değiştirmelisiniz:

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

ripple() işlevinin artık composable bir işlev olmadığını ve hatırlanması gerekmediğini unutmayın. Ayrıca, değiştiricilere benzer şekilde birden çok bileşende de yeniden kullanılabilir. Bu nedenle, ayırmaları kaydetmek için dalga oluşumunu üst düzey bir değere çıkarmanız önerilir.

Özel tasarım sistemini uygulama

Kendi tasarım sisteminizi uyguluyorsanız ve daha önce dalgayı yapılandırmak için özel bir RippleTheme ile birlikte rememberRipple() kullanıyorduysanız bunun yerine, material-ripple içinde sunulan dalga düğümü API'lerine yetki veren kendi Ripple API'nizi sağlamanız gerekir. Daha sonra bileşenleriniz, tema değerlerinizi doğrudan tüketen kendi dalganızı kullanabilir. Daha fazla bilgi için Taşıma kaynağı:RippleTheme bölümüne bakın.

RippleTheme sürümünden taşıma

Davranış değişikliğini geçici olarak devre dışı bırakma

Malzeme kitaplıklarında, tüm Materyal bileşenlerini rememberRipple kullanmaya geri dönecek şekilde yapılandırmak için kullanabileceğiniz geçici bir CompositionLocal (LocalUseFallbackRippleImplementation) bulunur. Bu şekilde, rememberRipple, LocalRippleTheme sorgusunu sorgulamaya devam eder.

Aşağıdaki kod snippet'i, LocalUseFallbackRippleImplementation CompositionLocal API'nin nasıl kullanılacağını gösterir:

CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) {
    MaterialTheme {
        App()
    }
}

Materyal üzerine inşa edilmiş özel bir uygulama teması kullanıyorsanız, kompozisyonu uygulamanızın temasının bir parçası olarak güvenle sağlayabilirsiniz:

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun MyAppTheme(content: @Composable () -> Unit) {
    CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) {
        MaterialTheme(content = content)
    }
}

Daha fazla bilgi için Taşımadan Materyal Kitaplığı sürümünü yükseltme bölümüne bakın.

Belirli bir bileşende dalgayı devre dışı bırakmak için RippleTheme kullanma

material ve material3 kitaplıkları, bir alt ağaç içindeki dalgaların görünümünü yapılandırmanıza olanak tanıyan RippleConfiguration ve LocalRippleConfiguration öğelerini kullanıma sunar. RippleConfiguration ve LocalRippleConfiguration öğelerinin deneysel olduğunu ve yalnızca bileşen başına özelleştirme için tasarlandığını unutmayın. Bu API'lerle genel/tema genelinde özelleştirme desteklenmez. Bu API'ler hakkında daha fazla bilgi edinmek için Bir uygulamadaki tüm dalgaları genel olarak değiştirmek için RippleTheme kullanma bölümüne bakın.

Örneğin, aşağıdaki snippet'te kullanımdan kaldı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ştirmelisiniz:

@OptIn(ExperimentalMaterialApi::class)
private val DisabledRippleConfiguration =
    RippleConfiguration(isEnabled = false)

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

Belirli bir bileşen için bir dalganın rengini/alfasını değiştirmek üzere RippleTheme kullanma

Önceki bölümde açıklandığı gibi RippleConfiguration ve LocalRippleConfiguration, deneysel API'lerdir ve yalnızca bileşen başına özelleştirmeye yöneliktir.

Örneğin, aşağıdaki snippet'te kullanımdan kaldı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ştirmelisiniz:

@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

Önceden, dalga davranışını tema genelinde tanımlamak için LocalRippleTheme kullanılabiliyordu. Bu, temelde özel tasarım sistemi bileşimi yerelleri ile Ripple arasındaki entegrasyon noktasıydı. Genel bir temel tema oluşturmak yerine, material-ripple artık bir createRippleModifierNode() işlevini kullanıma sunuyor. Bu işlev, tasarım sistemi kitaplıklarının tema değerlerini sorgulayan ve ardından dalga uygulamasını bu işlev tarafından oluşturulan düğüme atayan daha üst düzey wrapper uygulaması oluşturmasına olanak tanır.

Bu, tasarım sistemlerinin ihtiyaç duydukları şeyi doğrudan sorgulamasına ve material-ripple katmanında sağlananlara uyum sağlamak zorunda kalmadan, kullanıcı tarafından yapılandırılabilen gerekli tema katmanlarını en üstte açığa çıkarmasına olanak tanır. Bu değişiklik, dolaylı bir şekilde temadan türetilmek yerine, söz konusu sözleşmeyi tanımlayan Dalga API'sı olduğu için, dalganın hangi temaya/spesifikasyona uyduğunu daha açık bir şekilde ortaya koyar.

Yol gösterici olarak Materyal kitaplıklarındaki dalga API uygulamasına bakın ve kendi tasarım sisteminiz için gereken şekilde Malzeme bileşimi yerellerine yapılan çağrıları değiştirin.

Indication'den IndicationNodeFactory'a taşıyın

Indication çevresinden geçiyor

Yalnızca geçiş için bir Indication oluşturuyorsanız (örneğin, Modifier.clickable veya Modifier.indication öğesine geçirmek için dalga oluşturmak) herhangi bir değişiklik yapmanız gerekmez. IndicationNodeFactory öğesi, Indication öğesinden devralınır. Dolayısıyla her şey derlemeye ve çalışmaya devam eder.

Indication oluşturuluyor

Kendi Indication uygulamanızı oluşturuyorsanız taşıma işlemi çoğu durumda kolay olacaktır. Örneğin, basına ö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 hesabını DrawModifierNode taşımak için taşıyın. DrawModifierNode API yüzeyi IndicationInstance ile çok benzerdir: IndicationInstance#drawContent() ile işlevsel olarak eşdeğer olan bir ContentDrawScope#draw() işlevi sunar. Bu işlevi değiştirmeniz ve daha sonra, Indication yerine doğrudan düğümün içinde collectLatest mantığını uygulamanız gerekir.

    Örneğin, aşağıdaki snippet'te kullanımdan kaldı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ş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 uygulamasını uygulamak için ScaleIndication taşıyın. Toplama mantığı artık düğüme taşındığı için bu, tek sorumluluğu düğüm örneği oluşturmak olan çok basit bir fabrika nesnesidir.

    Örneğin, aşağıdaki snippet'te kullanımdan kaldı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ş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
    }

IndicationInstance oluşturmak için Indication kullanılıyor

Çoğu durumda, bir bileşen için Indication öğesini görüntülemek üzere Modifier.indication kullanmanız gerekir. Bununla birlikte, rememberUpdatedInstance kullanarak manuel olarak IndicationInstance oluşturduğunuz bazı nadir durumlarda, daha basit bir uygulama kullanabilmeniz için Indication öğesinin IndicationNodeFactory olup olmadığını kontrol etmek üzere uygulamanızı güncellemeniz gerekir. Örneğin Modifier.indication, oluşturulan düğüm IndicationNodeFactory ise dahili olarak bu düğüme yetki verir. Aksi takdirde rememberUpdatedInstance işlemini çağırmak için Modifier.composed kullanılır.