ভাগ করা উপাদান স্থানান্তর কাস্টমাইজ করুন

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

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

আকার এবং অবস্থান পরিবর্তনের জন্য ব্যবহৃত অ্যানিমেশন স্পেক পরিবর্তন করতে, আপনি 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 spec ব্যবহার করা হয়েছে।

চিত্র ১. বিভিন্ন boundsTransform পরামিতি প্রদর্শনকারী উদাহরণ

আকার পরিবর্তন মোড

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

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

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

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

ScaleToBounds

RemeasureToBounds

শেয়ার করা উপাদানগুলি গতিশীলভাবে সক্রিয় এবং নিষ্ক্রিয় করুন

ডিফল্টরূপে, sharedElement() এবং sharedBounds() এমনভাবে কনফিগার করা থাকে যে, টার্গেট স্টেটে কোনো মিল থাকা কী (key) খুঁজে পেলেই লেআউটের পরিবর্তনগুলো অ্যানিমেট হবে। তবে, আপনি নেভিগেশনের দিক বা বর্তমান 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() নেই - লক্ষ্য করুন "লোরেম ইপসাম" টেক্সট রিফ্লো হচ্ছে

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 মান false সেট করতে পারেন।

শেয়ার করা এলিমেন্টের আকারের পরিবর্তন সম্পর্কে সিibling লেআউটগুলিকে অবহিত করুন

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

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

PlaceholderSize.ContentSize (ডিফল্ট)

PlaceholderSize.AnimatedSize

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