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

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

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

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

অন্যদিকে, RemeasureToBounds টার্গেট সাইজের উপর ভিত্তি করে অ্যানিমেটেড ফিক্সড কনস্ট্রেন্ট সহ sharedBounds-এর চাইল্ড লেআউট আবার পরিমাপ করে এবং আবার লেআউট করে। The re-measurement is triggered by the bounds size change, which could potentially be every frame.

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

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

scaleToBounds()

RemeasureToBounds

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

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

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

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

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

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