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

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

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

সাইজ ও পজিশন মুভমেন্টের জন্য ব্যবহৃত অ্যানিমেশন স্পেক পরিবর্তন করতে, আপনি 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 প্রথম লুকঅহেড (বা টার্গেট) সীমাবদ্ধতা সহ চাইল্ড লেআউট পরিমাপ করে। তারপর, শেয়ার করা বাউন্ডের মধ্যে ফিট করার জন্য বাচ্চার</0x0A> স্টেবল লেআউট স্কেল করা হয়। ScaleToBounds-কে বিভিন্ন স্টেটের মধ্যে "গ্রাফিক্যাল স্কেল" হিসেবে বিবেচনা করা যেতে পারে।

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

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

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

ScaleToBounds

RemeasureToBounds

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

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

শেয়ার করা এলিমেন্ট ট্রানজিশন হবে কিনা তা নিয়ন্ত্রণ করতে, আপনি SharedContentConfig rememberSharedContentState()-এ পাস করা কাস্টমাইজ করতে পারবেন। শেয়ার করা এলিমেন্ট অ্যাক্টিভ আছে কিনা তা 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
                }
            }
        }
    }
}

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

সরাসরি শেষ লেআউটে যান

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

নিচের উদাহরণে "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
            }
        )
)

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

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

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

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

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

PlaceholderSize.ContentSize (ডিফল্ট)

PlaceholderSize.AnimatedSize

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