Indication ও Ripple API-তে মাইগ্রেট করা

Modifier.clickable ব্যবহার করে এমন ইন্ট্যার‍্যাক্টিভ কম্পোনেন্টের কম্পোজিশন পারফর্ম্যান্স উন্নত করতে আমরা নতুন API নিয়ে এসেছি। এইসব API আরও দক্ষ Indication প্রয়োগের অনুমতি দেয়, যেমন রিপেল।

androidx.compose.foundation:foundation:1.7.0+ এবং androidx.compose.material:material-ripple:1.7.0+-এ নিম্নলিখিত API পরিবর্তন অন্তর্ভুক্ত:

বন্ধ করে দেওয়া হয়েছে

পরিবর্তন

Indication#rememberUpdatedInstance

IndicationNodeFactory

rememberRipple()

নতুন ripple() API-এর পরিবর্তে Material লাইব্রেরিতে প্রদান করা হয়েছে।

মনে রাখবেন: এই প্রসঙ্গে, "মেটেরিয়াল লাইব্রেরি" বলতে androidx.compose.material:material, androidx.compose.material3:material3, androidx.wear.compose:compose-material ও androidx.wear.compose:compose-material3. বোঝানো হয়েছে

RippleTheme

যেকোনও একটি:

  • Material লাইব্রেরি RippleConfiguration API ব্যবহার করুন অথবা
  • আপনার নিজস্ব ডিজাইন সিস্টেম রিপেল ইমপ্লিমেন্টেশন তৈরি করা

এই পৃষ্ঠায় আচরণগত পরিবর্তনের প্রভাব এবং নতুন API-তে মাইগ্রেট করার নির্দেশাবলী বর্ণনা করা হয়েছে।

আচরণগত পরিবর্তন

নিম্নলিখিত লাইব্রেরি ভার্সনে রিপেল আচরণ সংক্রান্ত পরিবর্তন অন্তর্ভুক্ত আছে:

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

Material লাইব্রেরির এই ভার্সনগুলি আর rememberRipple() ব্যবহার করে না; পরিবর্তে, এগুলি নতুন রিপল API ব্যবহার করে। ফলে, তারা LocalRippleTheme কোয়েরি করেন না। তাই, আপনার অ্যাপ্লিকেশনে LocalRippleTheme সেট করলে, Material কম্পোনেন্ট এই ভ্যালু ব্যবহার করবে না।

নতুন API-তে কীভাবে মাইগ্রেট করবেন তা নিম্নলিখিত বিভাগে বর্ণনা করা হয়েছে।

rememberRipple থেকে ripple-এ মাইগ্রেট করুন

মেটেরিয়াল লাইব্রেরি ব্যবহার করা

আপনি কোনও মেটেরিয়াল লাইব্রেরি ব্যবহার করলে, সরাসরি rememberRipple()-এর পরিবর্তে সংশ্লিষ্ট লাইব্রেরি থেকে ripple()-কে কল করুন। এই API, Material থিম API থেকে প্রাপ্ত মান ব্যবহার করে একটি রিপেল তৈরি করে। তারপরে, Modifier.clickable এবং/অথবা অন্যান্য কম্পোনেন্টে রিটার্ন করা অবজেক্টটি পাস করুন।

যেমন, নিম্নলিখিত স্নিপেটটি অননুমোদিত API ব্যবহার করে:

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

আপনাকে উপরের স্নিপেটটি পরিবর্তন করে এগুলি করতে হবে:

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

মনে রাখবেন, ripple() আর কম্পোজ করা যায় না এবং এটি মনে রাখার প্রয়োজন নেই। এটি একাধিক কম্পোনেন্ট জুড়েও আবার ব্যবহার করা যেতে পারে, যেমন মডিফায়ার, তাই অ্যালোকেশন সেভ করতে, টপ-লেভেল ভ্যালুতে রিপেল ক্রিয়েশন এক্সট্র্যাক্ট করার কথা বিবেচনা করুন।

কাস্টম ডিজাইন সিস্টেম প্রয়োগ করা

আপনি যদি নিজের ডিজাইন সিস্টেম প্রয়োগ করেন এবং আগে কাস্টম RippleTheme-এর সাথে rememberRipple() ব্যবহার করে রিপেল কনফিগার করে থাকেন, তাহলে আপনাকে পরিবর্তে নিজের রিপেল API প্রদান করতে হবে যা material-ripple-এ এক্সপোজ করা রিপেল নোড API-তে ডেলিগেট করে। তারপরে, আপনার কম্পোনেন্ট আপনার নিজস্ব রিপেল ব্যবহার করতে পারবে যা সরাসরি আপনার থিমের ভ্যালু ব্যবহার করে। আরও তথ্যের জন্য, থেকে মাইগ্রেট করুনRippleTheme দেখুন।

RippleTheme থেকে মাইগ্রেট করা

নির্দিষ্ট কম্পোনেন্টের জন্য রিপেল বন্ধ করতে RippleTheme ব্যবহার করা

material ও material3 লাইব্রেরি RippleConfiguration ও LocalRippleConfiguration এক্সপোজ করে, যা আপনাকে সাবট্রিয়ের মধ্যে রিপলের চেহারা কনফিগার করতে দেয়। মনে রাখবেন, RippleConfiguration এবং LocalRippleConfiguration শুধুমাত্র পার-কম্পোনেন্ট কাস্টমাইজেশনের জন্য ব্যবহার করা হয়। এইসব API-এর সাথে গ্লোবাল/থিম-ওয়াইড কাস্টমাইজেশন কাজ করে না; এই ব্যবহারের ক্ষেত্রে আরও তথ্যের জন্য কোনও অ্যাপ্লিকেশনের RippleTheme সমস্ত রিপেল গ্লোবালি পরিবর্তন করা দেখুন।

যেমন, নিম্নলিখিত স্নিপেটটি অননুমোদিত API ব্যবহার করে:

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 {
            // ...
        }
    }

আপনাকে উপরের স্নিপেটটি পরিবর্তন করে এগুলি করতে হবে:

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

কোনও কম্পোনেন্টের জন্য রিপেলের রঙ/আলফা পরিবর্তন করতে RippleTheme ব্যবহার করা

আগের বিভাগে বর্ণনা করা হয়েছে, RippleConfiguration এবং LocalRippleConfiguration শুধুমাত্র প্রতিটি কম্পোনেন্ট কাস্টমাইজ করার জন্য ব্যবহার করা হয়।

যেমন, নিম্নলিখিত স্নিপেটটি অননুমোদিত API ব্যবহার করে:

private object DisabledRippleThemeColorAndAlpha : RippleTheme {

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

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

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

আপনাকে উপরের স্নিপেটটি পরিবর্তন করে এগুলি করতে হবে:

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

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

কোনও অ্যাপ্লিকেশনের সব রিপেল বিশ্বব্যাপী পরিবর্তন করতে RippleTheme ব্যবহার করা

আগে, আপনি থিম-ওয়াইড লেভেলে রিপেল বিহেভিয়ার সংজ্ঞায়িত করতে LocalRippleTheme ব্যবহার করতে পারতেন। এটি মূলত কাস্টম ডিজাইন সিস্টেম কম্পোজিশন লোকাল ও রিপলের মধ্যে ইন্টিগ্রেশন পয়েন্ট ছিল। সাধারণ থিমিং প্রিমিটিভ প্রকাশ করার পরিবর্তে, material-ripple এখন একটি createRippleModifierNode() ফাংশন প্রকাশ করে। এই ফাংশনটি ডিজাইন সিস্টেম লাইব্রেরিকে উচ্চতর অর্ডার wrapper প্রয়োগ তৈরি করতে দেয়, যা তাদের থিমের মান কোয়েরি করে এবং তারপরে এই ফাংশন দ্বারা তৈরি নোডে রিপেল প্রয়োগ ডেলিগেট করে।

এর ফলে ডিজাইন সিস্টেম সরাসরি যা প্রয়োজন তা কোয়েরি করতে পারে এবং material-ripple লেয়ারে যা প্রদান করা হয়েছে তা মেনে না চলেই উপরে প্রয়োজনীয় ব্যবহারকারী-কনফিগারযোগ্য থিমিং লেয়ার প্রকাশ করতে পারে। এছাড়াও, এই পরিবর্তনের ফলে, রিপেল কোন থিম/স্পেসিফিকেশন মেনে চলছে তা আরও স্পষ্টভাবে বোঝা যায়, কারণ এটি হল রিপেল API যা সেই কন্ট্র্যাক্টকে ডিফাইন করে, থিম থেকে ইমপ্লিসিটভাবে পাওয়া যায় না।

নির্দেশাবলীর জন্য, Material লাইব্রেরিতে রিপল API প্রয়োগ দেখুন এবং আপনার নিজস্ব ডিজাইন সিস্টেমের প্রয়োজন অনুযায়ী Material কম্পোজিশন লোকালকে কল করার পরিবর্তে অন্য কিছু ব্যবহার করুন।

Indication থেকে IndicationNodeFactory-এ মাইগ্রেট করুন

Indication-এর আশেপাশে

আপনি যদি শুধুমাত্র Indication তৈরি করে তা শেয়ার করতে চান, যেমন Modifier.clickable বা Modifier.indication-এর সাথে শেয়ার করার জন্য রিপল তৈরি করা, তাহলে আপনাকে কোনও পরিবর্তন করতে হবে না। IndicationNodeFactory থেকে ইনহেরিট করে Indication, তাই সবকিছু কম্পাইল ও কাজ করা চালিয়ে যাবে।

Indication তৈরি করা হচ্ছে

আপনি নিজের Indication প্রয়োগ তৈরি করলে, বেশিরভাগ ক্ষেত্রে মাইগ্রেশন সহজ হবে। যেমন, এমন একটি Indication-এর কথা বিবেচনা করুন যা প্রেস করলে স্কেল এফেক্ট প্রয়োগ করে:

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

আপনি দুটি ধাপে এটি মাইগ্রেট করতে পারবেন:

  1. ScaleIndicationInstance থেকে মাইগ্রেট করে DrawModifierNode হয়ে যান। DrawModifierNode-এর API সারফেস IndicationInstance-এর সাথে খুব মিল আছে: এটি একটি ContentDrawScope#draw() ফাংশন এক্সপোজ করে যা ফাংশনালি IndicationInstance#drawContent()-এর সমতুল্য। আপনাকে সেই ফাংশন পরিবর্তন করতে হবে এবং তারপরে Indication-এর পরিবর্তে সরাসরি নোডের মধ্যে collectLatest লজিক ইমপ্লিমেন্ট করতে হবে।

    যেমন, নিম্নলিখিত স্নিপেটটি অননুমোদিত API ব্যবহার করে:

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

    আপনাকে উপরের স্নিপেটটি পরিবর্তন করে এগুলি করতে হবে:

    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 প্রয়োগ করতে ScaleIndication মাইগ্রেট করুন। কারণ, এখন সংগ্রহ সংক্রান্ত লজিক নোডে সরানো হয়েছে, এটি একটি খুব সাধারণ ফ্যাক্টরি অবজেক্ট যার একমাত্র দায়িত্ব হল নোড ইনস্ট্যান্স তৈরি করা।

    যেমন, নিম্নলিখিত স্নিপেটটি অননুমোদিত API ব্যবহার করে:

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

    আপনাকে উপরের স্নিপেটটি পরিবর্তন করে এগুলি করতে হবে:

    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 তৈরি করতে Indication ব্যবহার করা

বেশিরভাগ ক্ষেত্রে, কোনও কম্পোনেন্টের জন্য Modifier.indication Indication ব্যবহার করা উচিত। তবে, আপনি যদি ম্যানুয়ালি কোনও IndicationInstance তৈরি করে থাকেন rememberUpdatedInstance, তাহলে আপনাকে নিজের ইমপ্লিমেন্টেশন আপডেট করে দেখতে হবে যে Indication হল IndicationNodeFactory কিনা, যাতে আপনি আরও হালকা ইমপ্লিমেন্টেশন ব্যবহার করতে পারেন। যেমন, Modifier.indication ইন্টার্নালি তৈরি করা নোডে ডেলিগেট করবে যদি এটি একটি IndicationNodeFactory হয়। না থাকলে, rememberUpdatedInstance-কে কল করার জন্য Modifier.composed ব্যবহার করা হবে।