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

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

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

साइज़ और पोज़िशन के मूवमेंट के लिए इस्तेमाल किए जाने वाले ऐनिमेशन की खास जानकारी बदलने के लिए, 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 को 'गलत' पर सेट करें.

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

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

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

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

PlaceholderSize.AnimatedSize

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