শেয়ার করা এলিমেন্ট ট্রানজিশন কাস্টমাইজ করা

শেয়ার করা এলিমেন্ট ট্রানজিশন অ্যানিমেশন কীভাবে রান করে তা কাস্টমাইজ করতে, শেয়ার করা এলিমেন্ট ট্রানজিশন কীভাবে হয় তা পরিবর্তন করার জন্য কয়েকটি প্যারামিটার ব্যবহার করা যেতে পারে।

অ্যানিমেশন স্পেক

সাইজ ও পজিশন মুভমেন্টের জন্য ব্যবহৃত অ্যানিমেশন স্পেসিফিকেশন পরিবর্তন করতে, আপনি Modifier.sharedElement()-এ অন্য একটি boundsTransform প্যারামিটার নির্দিষ্ট করতে পারেন। এটি প্রাথমিক Rect পজিশন ও টার্গেট Rect পজিশন প্রদান করে।

যেমন, পূর্ববর্তী উদাহরণের টেক্সটকে আর্ক মোশনে মুভ করাতে, keyframes স্পেসিফিকেশন ব্যবহার করার জন্য boundsTransform প্যারামিটার নির্দিষ্ট করুন:

val textBoundsTransform = BoundsTransform { initialBounds, targetBounds ->
    keyframes {
        durationMillis = boundsAnimationDurationMillis
        initialBounds at 0 using ArcMode.ArcBelow using FastOutSlowInEasing
        targetBounds at boundsAnimationDurationMillis
    }
}
Text(
    "Cupcake", fontSize = 28.sp,
    modifier = Modifier.sharedBounds(
        rememberSharedContentState(key = "title"),
        animatedVisibilityScope = animatedVisibilityScope,
        boundsTransform = textBoundsTransform
    )
)

আপনি যেকোনও AnimationSpec ব্যবহার করতে পারবেন। এই উদাহরণে keyframes স্পেক ব্যবহার করা হয়েছে।

ছবি ১. বিভিন্ন boundsTransform প্যারামিটার দেখানো উদাহরণ

ছোট-বড় করার মোড

দুটি শেয়ার করা বাউন্ডের মধ্যে অ্যানিমেট করার সময়, আপনি resizeMode প্যারামিটার RemeasureToBounds অথবা ScaleToBounds হিসেবে সেট করতে পারবেন। এই প্যারামিটার নির্ধারণ করে যে কীভাবে শেয়ার করা এলিমেন্ট দুটি অবস্থার মধ্যে ট্রানজিশন করে। ScaleToBounds প্রথমে লুকঅ্যাহেড (বা টার্গেট) কনস্ট্রেন্টের মাধ্যমে চাইল্ড লেআউট পরিমাপ করে। তারপর, চাইল্ডের স্টেবল লেআউট শেয়ার করা বাউন্ডে ফিট করার জন্য স্কেল করা হয়। ScaleToBounds-কে স্টেটগুলির মধ্যে "গ্রাফিক্যাল স্কেল" হিসেবে ভাবা যেতে পারে।

এর বিপরীতে, RemeasureToBounds টার্গেট সাইজের উপর ভিত্তি করে অ্যানিমেটেড ফিক্সড কনস্ট্রেন্ট সহ sharedBounds-এর চাইল্ড লেআউট আবার পরিমাপ ও লেআউট করে। বাউন্ড সাইজ পরিবর্তনের কারণে আবার পরিমাপ করার প্রসেস ট্রিগার হয়, যা সম্ভাব্যভাবে প্রতিটি ফ্রেম হতে পারে।

Text কম্পোজেবলের জন্য, ScaleToBounds সাজেস্ট করা হয়, কারণ এটি রিলেআউট এবং বিভিন্ন লাইনে টেক্সটের রিফ্লো এড়ায়। RemeasureToBounds সাজেস্ট করা হয় এমন বাউন্ডের জন্য যেগুলি আলাদা অ্যাস্পেক্ট রেশিও, এবং আপনি যদি দুটি শেয়ার করা এলিমেন্টের মধ্যে ফ্লুইড কন্টিনিউটি চান।

দুটি ছোট বড় করার মোডের মধ্যে পার্থক্য নিম্নলিখিত উদাহরণে দেখা যেতে পারে:

ScaleToBounds

RemeasureToBounds

শেয়ার করা এলিমেন্ট ডায়নামিকভাবে চালু ও বন্ধ করা

ডিফল্ট হিসেবে, টার্গেট স্টেটে ম্যাচিং কী খুঁজে পাওয়া গেলেই লেআউটের পরিবর্তন অ্যানিমেট করার জন্য sharedElement() এবং sharedBounds() কনফিগার করা হয়। তবে, আপনি নির্দিষ্ট শর্তের উপর ভিত্তি করে ডাইনামিকভাবে এই অ্যানিমেশন বন্ধ করতে চাইতে পারেন, যেমন নেভিগেশনের দিক বা বর্তমান UI-এর অবস্থা।

শেয়ার করা এলিমেন্ট ট্রানজিশন হবে কিনা তা নিয়ন্ত্রণ করতে, আপনি rememberSharedContentState()-এ পাস করা SharedContentConfig কাস্টমাইজ করতে পারবেন। শেয়ার করা এলিমেন্ট অ্যাক্টিভ আছে কিনা তা isEnabled প্রপার্টি নির্ধারণ করে।

নিম্নলিখিত উদাহরণটি দেখায় যে কীভাবে একটি কনফিগারেশন সংজ্ঞায়িত করা যায় যা শুধুমাত্র নির্দিষ্ট স্ক্রিনের মধ্যে নেভিগেট করার সময় শেয়ার করা ট্রানজিশন সক্ষম করে (যেমন, শুধুমাত্র A থেকে B পর্যন্ত), অন্যদের জন্য এটি বন্ধ করার সময়।

SharedTransitionLayout {
    val transition = updateTransition(currentState)
    transition.AnimatedContent { targetState ->
        // Create the configuration that depends on state changing.
        fun animationConfig() : SharedTransitionScope.SharedContentConfig {
            return object : SharedTransitionScope.SharedContentConfig {
                override val SharedTransitionScope.SharedContentState.isEnabled: Boolean
                    // For this example, we only enable the transition in one direction
                    // from A -> B and not the other way around.
                    get() =
                        transition.currentState == "A" && transition.targetState == "B"
            }
        }
        when (targetState) {
            "A" -> Box(
                modifier = Modifier
                    .sharedElement(
                        rememberSharedContentState(
                            key = "shared_box",
                            config = animationConfig()
                        ),
                        animatedVisibilityScope = this
                    )
                    // ...
            ) {
                // Your content
            }
            "B" -> {
                Box(
                    modifier = Modifier
                        .sharedElement(
                            rememberSharedContentState(
                                key = "shared_box",
                                config = animationConfig()
                            ),
                            animatedVisibilityScope = this
                        )
                        // ...
                ) {
                    // Your content
                }
            }
        }
    }
}

ডিফল্ট হিসেবে, কোনও অ্যানিমেশন চলাকালীন শেয়ার করা এলিমেন্ট বন্ধ করা হলে, এটি এখনও বর্তমান ইন-প্রোগ্রেস অ্যানিমেশন সম্পূর্ণ করে যাতে ভুলবশত ইন-ফ্লাইট অ্যানিমেশন সরিয়ে দেওয়া আটকানো যায়। অ্যানিমেশন চলাকালীন আপনাকে এলিমেন্ট সরাতে হলে, আপনি SharedContentConfig ইন্টারফেসে shouldKeepEnabledForOngoingAnimation ওভাররাইড করে false রিটার্ন করতে পারেন।

সরাসরি ফাইনাল লেআউটে যান

ডিফল্ট হিসেবে, দুটি লেআউটের মধ্যে ট্রানজিশন করার সময়, লেআউটের সাইজ শুরু ও শেষ অবস্থার মধ্যে অ্যানিমেট করে। টেক্সটের মতো কন্টেন্ট অ্যানিমেট করার সময় এটি অবাঞ্ছিত আচরণ হতে পারে।

নিচের উদাহরণে দেখানো হয়েছে যে কীভাবে "Lorem Ipsum" বিবরণ টেক্সট স্ক্রিনে দুটি আলাদা উপায়ে প্রবেশ করে। প্রথম উদাহরণে, কন্টেনারের সাইজ বড় হওয়ার সাথে সাথে টেক্সট রিফ্লো করে। দ্বিতীয় উদাহরণে টেক্সট বড় হওয়ার সাথে সাথে রিফ্লো হয় না। Modifier.skipToLookaheadSize() যোগ করলে রিফ্লো আটকে যায়।

Modifier.skipToLookaheadSize() নেই - "Lorem Ipsum" টেক্সট রিফ্লো লক্ষ্য করুন

Modifier.skipToLookaheadSize() - লক্ষ্য করুন যে "Lorem Ipsum" টেক্সট অ্যানিমেশনের শুরুতে তার চূড়ান্ত অবস্থা বজায় রাখে

ক্লিপ ও ওভারলে

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

এটি অন্যান্য শেয়ার না করা UI এলিমেন্টের উপরে রেন্ডার করবে। ট্রানজিশন শেষ হয়ে গেলে, এলিমেন্টটি ওভারলে থেকে ড্রপ করে তার নিজস্ব DrawScope-এ নিয়ে যাওয়া হবে।

কোনও শেপে শেয়ার করা এলিমেন্ট ক্লিপ করতে, স্ট্যান্ডার্ড Modifier.clip() ফাংশন ব্যবহার করুন। sharedElement()-এর পরে এটি যোগ করুন:

Image(
    painter = painterResource(id = R.drawable.cupcake),
    contentDescription = "Cupcake",
    modifier = Modifier
        .size(100.dp)
        .sharedElement(
            rememberSharedContentState(key = "image"),
            animatedVisibilityScope = this@AnimatedContent
        )
        .clip(RoundedCornerShape(16.dp)),
    contentScale = ContentScale.Crop
)

আপনি যদি নিশ্চিত করতে চান যে কোনও শেয়ার করা এলিমেন্ট কখনই পেরেন্ট কন্টেনারের বাইরে রেন্ডার হবে না, তাহলে আপনি sharedElement()-এ clipInOverlayDuringTransition সেট করতে পারেন। নেস্টেড শেয়ার্ড বাউন্ডের জন্য, ডিফল্ট হিসেবে, clipInOverlayDuringTransition পেরেন্ট sharedBounds() থেকে ক্লিপ পাথ ব্যবহার করে।

শেয়ার করা এলিমেন্ট ট্রানজিশনের সময় বটম বার বা ফ্লোটিং অ্যাকশন বোতামের মতো নির্দিষ্ট UI এলিমেন্ট সবসময় উপরে রাখার জন্য Modifier.renderInSharedTransitionScopeOverlay() ব্যবহার করুন। ডিফল্ট হিসেবে, এই মডিফায়ার শেয়ার করা ট্রানজিশন অ্যাক্টিভ থাকার সময় ওভারলেতে কন্টেন্ট রাখে।

যেমন, Jetsnack-এ, স্ক্রিন অদৃশ্য না হওয়া পর্যন্ত BottomAppBar-কে শেয়ার করা এলিমেন্টের উপরে রাখতে হবে। কম্পোজেবলে মডিফায়ার যোগ করলে এটি এলিভেটেড থাকে।

Modifier.renderInSharedTransitionScopeOverlay() ছাড়া

Modifier.renderInSharedTransitionScopeOverlay() থাকলে

আপনি হয়ত চাইবেন যে আপনার শেয়ার না করা কম্পোজেবল অ্যানিমেট হয়ে চলে যাক এবং ট্রানজিশনের আগে অন্যান্য কম্পোজেবলের উপরে থাকুক। এই ধরনের ক্ষেত্রে, শেয়ার করা এলিমেন্ট ট্রানজিশন রান করার সময় কম্পোজেবল আউট অ্যানিমেট করতে renderInSharedTransitionScopeOverlay().animateEnterExit() ব্যবহার করুন:

JetsnackBottomBar(
    modifier = Modifier
        .renderInSharedTransitionScopeOverlay(
            zIndexInOverlay = 1f,
        )
        .animateEnterExit(
            enter = fadeIn() + slideInVertically {
                it
            },
            exit = fadeOut() + slideOutVertically {
                it
            }
        )
)

ছবি ২. অ্যানিমেশন ট্রানজিশন হওয়ার সময় স্ক্রিনের নিচের দিকে থাকা অ্যাপ বার স্লাইড করে ভিতরে ও বাইরে যাচ্ছে।

বিরল ক্ষেত্রে, আপনি যদি চান যে আপনার শেয়ার করা এলিমেন্ট ওভারলেতে রেন্ডার না হোক, তাহলে আপনি sharedElement()-এ renderInOverlayDuringTransition-কে 'মিথ্যা' হিসেবে সেট করতে পারেন।

শেয়ার করা এলিমেন্টের সাইজে পরিবর্তন হলে, একই লেভেলের লেআউটকে বিজ্ঞপ্তি পাঠান

ডিফল্ট হিসেবে, লেআউট ট্রানজিশন হওয়ার সময় সাইজে কোনও পরিবর্তন হলে, sharedBounds() এবং sharedElement() পেরেন্ট কন্টেনারকে জানায় না।

ট্রানজিশন হওয়ার সময় পেরেন্ট কন্টেনারে সাইজের পরিবর্তন প্রয়োগ করতে, placeholderSize প্যারামিটার পরিবর্তন করে PlaceholderSize.AnimatedSize করুন। এটি করলে আইটেমটি বড় বা ছোট হয়। লেআউটের অন্যান্য সব আইটেম এই পরিবর্তনের প্রতিক্রিয়া দেখায়।

PlaceholderSize.ContentSize (ডিফল্ট)

PlaceholderSize.AnimatedSize

(লক্ষ্য করুন যে তালিকার একটি আইটেম বড় হওয়ার সাথে সাথে তালিকার অন্যান্য আইটেমগুলি কীভাবে নিচে নেমে যায়)