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 প্রদান করা হয়েছে।

মনে রাখবেন: এই প্রসঙ্গে, "মেটেরিয়াল লাইব্রেরি" বলতে 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+

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

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

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

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

আপনি যদি মেটেরিয়াল লাইব্রেরি ব্যবহার করেন, তাহলে সরাসরি rememberRipple()-এর পরিবর্তে সংশ্লিষ্ট লাইব্রেরি থেকে ripple()-এ কল করুন। এই API, মেটিরিয়াল থিম 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 ব্যবহার করা হচ্ছে

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