शेयर किए गए एलिमेंट के ट्रांज़िशन को पसंद के मुताबिक बनाएं

शेयर किए गए एलिमेंट के ट्रांज़िशन ऐनिमेशन को पसंद के मुताबिक बनाने के लिए, कुछ पैरामीटर का इस्तेमाल किया जा सकता है. इनसे, शेयर किए गए एलिमेंट के ट्रांज़िशन के तरीके में बदलाव किया जा सकता है.

ऐनिमेशन की खास जानकारी

साइज़ और पोज़िशन में बदलाव करने के लिए इस्तेमाल किए जाने वाले ऐनिमेशन की खास जानकारी बदलने के लिए, Modifier.sharedElement() पर कोई दूसरा boundsTransform पैरामीटर तय किया जा सकता है. इससे शुरुआती Rect पोज़िशन और टारगेट Rect पोज़िशन की जानकारी मिलती है.

उदाहरण के लिए, पिछले उदाहरण में दिए गए टेक्स्ट को आर्क मोशन के साथ मूव करने के लिए, boundsTransform पैरामीटर की वैल्यू के तौर पर keyframes डालें:

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() को इस तरह कॉन्फ़िगर किया जाता है कि टारगेट स्टेट में मैच होने वाली कोई कुंजी मिलने पर, लेआउट में होने वाले बदलावों को ऐनिमेट किया जा सके. हालांकि, कुछ खास स्थितियों के आधार पर, इस ऐनिमेशन को डाइनैमिक तौर पर बंद किया जा सकता है. जैसे, नेविगेशन की दिशा या यूज़र इंटरफ़ेस (यूआई) की मौजूदा स्थिति.

शेयर किए गए एलिमेंट का ट्रांज़िशन होगा या नहीं, यह कंट्रोल करने के लिए, 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 को ओवरराइड करके, फ़ॉल्स वैल्यू रिटर्न की जा सकती है.

सीधे फ़ाइनल लेआउट पर जाएं

डिफ़ॉल्ट रूप से, दो लेआउट के बीच ट्रांज़िशन करते समय, लेआउट का साइज़ अपनी शुरुआती और आखिरी स्थिति के बीच ऐनिमेट होता है. टेक्स्ट जैसे कॉन्टेंट को ऐनिमेट करते समय, यह व्यवहार अनचाहा हो सकता है.

यहां दिए गए उदाहरण में, "Lorem Ipsum" टेक्स्ट को स्क्रीन पर दो अलग-अलग तरीकों से दिखाया गया है. पहले उदाहरण में, कंटेनर का साइज़ बढ़ने पर टेक्स्ट रीफ़्लो होता है. दूसरे उदाहरण में, टेक्स्ट बड़ा होने पर रीफ़्लो नहीं होता. Modifier.skipToLookaheadSize() को जोड़ने से, इसके बढ़ने पर रीफ़्लो नहीं होता.

कोई Modifier.skipToLookaheadSize() नहीं - "Lorem Ipsum" टेक्स्ट को फिर से फ़्लो होते हुए देखें

Modifier.skipToLookaheadSize() - ध्यान दें कि "Lorem Ipsum" टेक्स्ट, ऐनिमेशन की शुरुआत में अपनी फ़ाइनल स्टेट में रहता है

क्लिप और ओवरले

शेयर किए गए एलिमेंट को अलग-अलग कंपोज़ेबल के बीच शेयर करने के लिए, ट्रांज़िशन शुरू होने पर कंपोज़ेबल की रेंडरिंग को लेयर ओवरले में बदल दिया जाता है. ऐसा तब होता है, जब डेस्टिनेशन में मौजूद मैच के लिए ट्रांज़िशन शुरू होता है. इसका असर यह होगा कि यह पैरंट के बाउंड और लेयर ट्रांसफ़ॉर्मेशन (जैसे, ऐल्फ़ा और स्केल) से बाहर निकल जाएगा.

यह शेयर नहीं किए गए अन्य यूज़र इंटरफ़ेस (यूआई) एलिमेंट के ऊपर रेंडर होगा. ट्रांज़िशन पूरा होने के बाद, एलिमेंट को ओवरले से हटाकर उसके 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() से क्लिप पाथ का इस्तेमाल करता है.

शेयर किए गए एलिमेंट के ट्रांज़िशन के दौरान, बॉटम बार या फ़्लोट करने वाले कार्रवाई बटन जैसे कुछ यूज़र इंटरफ़ेस (यूआई) एलिमेंट को हमेशा सबसे ऊपर बनाए रखने के लिए, 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 पर सेट किया जा सकता है.

शेयर किए गए एलिमेंट के साइज़ में हुए बदलावों के बारे में, सिबलिंग लेआउट को सूचना दें

डिफ़ॉल्ट रूप से, लेआउट ट्रांज़िशन के दौरान साइज़ में होने वाले किसी भी बदलाव की सूचना, sharedBounds() और sharedElement() पैरंट कंटेनर को नहीं देते.

ट्रांज़िशन के दौरान, पैरंट कंटेनर में साइज़ में हुए बदलावों को लागू करने के लिए, placeholderSize पैरामीटर को PlaceholderSize.AnimatedSize में बदलें. ऐसा करने से, आइटम बड़ा या छोटा हो जाता है. लेआउट में मौजूद अन्य सभी आइटम, बदलाव के हिसाब से अडजस्ट हो जाते हैं.

PlaceholderSize.ContentSize (डिफ़ॉल्ट)

PlaceholderSize.AnimatedSize

(ध्यान दें कि सूची में मौजूद एक आइटम का साइज़ बढ़ने पर, अन्य आइटम कैसे नीचे की ओर खिसकते हैं)