कंपोज़ में शेयर किए गए एलिमेंट का ट्रांज़िशन

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

उदाहरण के लिए, नीचे दिए गए वीडियो में, स्नैक को लिस्टिंग पेज से लेकर ज़्यादा जानकारी वाले पेज तक शेयर किया जाता है.

पहली इमेज. JetSnacks के शेयर किए गए एलिमेंट का डेमो

Compose में कुछ हाई लेवल एपीआई हैं जिनकी मदद से, शेयर किया जा सकता है एलिमेंट:

  • SharedTransitionLayout: शेयर किए गए एक्सटेंशन को लागू करने के लिए, सबसे बाहरी लेआउट ज़रूरी है एलिमेंट ट्रांज़िशन के बारे में ज़्यादा जानें. इससे SharedTransitionScope मिलता है. कंपोज़ेबल की ज़रूरत शेयर किए गए एलिमेंट मॉडिफ़ायर का इस्तेमाल करने के लिए, SharedTransitionScope में होना ज़रूरी है.
  • Modifier.sharedElement(): वह मॉडिफ़ायर जो SharedTransitionScope कंपोज़ेबल जो किसी अन्य कंपोज़ेबल से मेल खाना चाहिए कंपोज़ेबल.
  • Modifier.sharedBounds(): वह मॉडिफ़ायर जो SharedTransitionScope कि इस कंपोज़ेबल के बाउंड का इस्तेमाल, कंटेनर बाउंड है, जहां ट्रांज़िशन होने चाहिए. इसके उलट sharedElement(), sharedBounds() को अलग-अलग विज़ुअल के लिए डिज़ाइन किया गया है कॉन्टेंट.

Compose में शेयर किए गए एलिमेंट बनाते समय एक अहम बात यह है कि वे कैसे काम करते हैं वीडियो क्लिप बनाने की सुविधा जोड़ी गई है. इन क्लिप पर एक नज़र डालें और ओवरले सेक्शन देखें.

बुनियादी इस्तेमाल

इस सेक्शन में, नीचे दिया गया ट्रांज़िशन बनाया जाएगा. यह ट्रांज़िशन छोटी "सूची" आइटम, बड़े विवरण वाले आइटम में:

दूसरी इमेज. दो कंपोज़ेबल के बीच शेयर किए गए एलिमेंट के ट्रांज़िशन का सामान्य उदाहरण.

Modifier.sharedElement() का इस्तेमाल करने का सबसे अच्छा तरीका यह है कि AnimatedContent, AnimatedVisibility या NavHost, क्योंकि यह मैनेज करता है आपके लिए, कंपोज़ेबल के बीच अपने-आप ट्रांज़िशन हो.

शुरुआत की जगह एक मौजूदा बेसिक AnimatedContent है, जिसमें शेयर किए गए एलिमेंट जोड़ने से पहले MainContent और DetailsContent कंपोज़ेबल:

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

  1. शेयर किए गए एलिमेंट को दो लेआउट के बीच ऐनिमेट करने के लिए, AnimatedContent कंपोज़ेबल को SharedTransitionLayout से घेरें. कॉन्टेंट बनाने SharedTransitionLayout और AnimatedContent के दायरे पास कर दिए गए हैं MainContent और DetailsContent के लिए:

    var showDetails by remember {
        mutableStateOf(false)
    }
    SharedTransitionLayout {
        AnimatedContent(
            showDetails,
            label = "basic_transition"
        ) { targetState ->
            if (!targetState) {
                MainContent(
                    onShowDetails = {
                        showDetails = true
                    },
                    animatedVisibilityScope = this@AnimatedContent,
                    sharedTransitionScope = this@SharedTransitionLayout
                )
            } else {
                DetailsContent(
                    onBack = {
                        showDetails = false
                    },
                    animatedVisibilityScope = this@AnimatedContent,
                    sharedTransitionScope = this@SharedTransitionLayout
                )
            }
        }
    }

  2. Modifier.sharedElement() को मेल खाने वाले दो कंपोज़ेबल. कोई SharedContentState ऑब्जेक्ट बनाएं और rememberSharedContentState() के साथ इसे याद रखें. कॉन्टेंट बनाने SharedContentState ऑब्जेक्ट यूनीक कुंजी को सेव कर रहा है. इससे यह तय होता है कि शेयर किए गए एलिमेंट. कॉन्टेंट की पहचान करने के लिए कोई खास कुंजी दें और आइटम को याद रखने के लिए rememberSharedContentState() का इस्तेमाल करें. कॉन्टेंट बनाने AnimatedContentScope को मॉडिफ़ायर में पास किया जाता है. इसका इस्तेमाल इन कामों के लिए किया जाता है ऐनिमेशन को सिंक करें.

    @Composable
    private fun MainContent(
        onShowDetails: () -> Unit,
        modifier: Modifier = Modifier,
        sharedTransitionScope: SharedTransitionScope,
        animatedVisibilityScope: AnimatedVisibilityScope
    ) {
        Row(
            // ...
        ) {
            with(sharedTransitionScope) {
                Image(
                    painter = painterResource(id = R.drawable.cupcake),
                    contentDescription = "Cupcake",
                    modifier = Modifier
                        .sharedElement(
                            rememberSharedContentState(key = "image"),
                            animatedVisibilityScope = animatedVisibilityScope
                        )
                        .size(100.dp)
                        .clip(CircleShape),
                    contentScale = ContentScale.Crop
                )
                // ...
            }
        }
    }
    
    @Composable
    private fun DetailsContent(
        modifier: Modifier = Modifier,
        onBack: () -> Unit,
        sharedTransitionScope: SharedTransitionScope,
        animatedVisibilityScope: AnimatedVisibilityScope
    ) {
        Column(
            // ...
        ) {
            with(sharedTransitionScope) {
                Image(
                    painter = painterResource(id = R.drawable.cupcake),
                    contentDescription = "Cupcake",
                    modifier = Modifier
                        .sharedElement(
                            rememberSharedContentState(key = "image"),
                            animatedVisibilityScope = animatedVisibilityScope
                        )
                        .size(200.dp)
                        .clip(CircleShape),
                    contentScale = ContentScale.Crop
                )
                // ...
            }
        }
    }

यह जानकारी पाने के लिए कि शेयर किए गए एलिमेंट का मिलान हुआ है या नहीं, एक्सट्रैक्ट करें rememberSharedContentState() को वैरिएबल में बदलें और isMatchFound के लिए क्वेरी करें.

जिसके कारण नीचे दिए गए अपने आप चलने वाले ऐनिमेशन होते हैं:

चौथी इमेज. दो कंपोज़ेबल के बीच शेयर किए गए एलिमेंट के ट्रांज़िशन का सामान्य उदाहरण.

शायद आपको दिखे कि पूरे कंटेनर के बैकग्राउंड का रंग और साइज़ अब भी दिख रहा है AnimatedContent की डिफ़ॉल्ट सेटिंग का इस्तेमाल करता है.

शेयर की गई सीमाएं बनाम शेयर किए गए एलिमेंट

Modifier.sharedBounds(), Modifier.sharedElement() से मिलता-जुलता है. हालांकि, मॉडिफ़ायर इस तरह से अलग-अलग होते हैं:

  • sharedBounds() ऐसे कॉन्टेंट के लिए है जो दिखने में अलग है, लेकिन शेयर होना चाहिए राज्यों के बीच समान क्षेत्र है, जबकि sharedElement() के हिसाब से कॉन्टेंट में एक जैसा रहेगा.
  • sharedBounds() इस्तेमाल करने पर, स्क्रीन पर दिखने वाला कॉन्टेंट किस तरह दिखेगा दो स्थितियों के बीच संक्रमण के दौरान दृश्यमान होता है, जबकि बदलाव के दौरान सिर्फ़ sharedElement() टारगेट कॉन्टेंट रेंडर किया गया है सीमाओं. Modifier.sharedBounds() में इसके लिए enter और exit पैरामीटर हैं कॉन्टेंट को ट्रांसफ़र करने का तरीका तय करते हुए, AnimatedContent काम करता है.
  • sharedBounds() के लिए इस्तेमाल किया जाने वाला सबसे आम उदाहरण कंटेनर ट्रांसफ़ॉर्म है पैटर्न, जबकि sharedElement() के लिए उदाहरण उपयोग का उदाहरण हीरो ट्रांज़िशन है.
  • Text कंपोज़ेबल का इस्तेमाल करते समय, फ़ॉन्ट के साथ काम करने के लिए sharedBounds() को प्राथमिकता दी जाती है इटैलिक और बोल्ड या रंग के बीच ट्रांज़िशन जैसे बदलाव.

पिछले उदाहरण से, Row में Modifier.sharedBounds() जोड़कर और दो अलग-अलग स्थितियों में Column हमें उन सीमाओं को शेयर करने की अनुमति देगा दूसरे चरण और ट्रांज़िशन ऐनिमेशन का इस्तेमाल करके, उन्हें एक-दूसरे के बीच में:

@Composable
private fun MainContent(
    onShowDetails: () -> Unit,
    modifier: Modifier = Modifier,
    sharedTransitionScope: SharedTransitionScope,
    animatedVisibilityScope: AnimatedVisibilityScope
) {
    with(sharedTransitionScope) {
        Row(
            modifier = Modifier
                .padding(8.dp)
                .sharedBounds(
                    rememberSharedContentState(key = "bounds"),
                    animatedVisibilityScope = animatedVisibilityScope,
                    enter = fadeIn(),
                    exit = fadeOut(),
                    resizeMode = SharedTransitionScope.ResizeMode.ScaleToBounds()
                )
                // ...
        ) {
            // ...
        }
    }
}

@Composable
private fun DetailsContent(
    modifier: Modifier = Modifier,
    onBack: () -> Unit,
    sharedTransitionScope: SharedTransitionScope,
    animatedVisibilityScope: AnimatedVisibilityScope
) {
    with(sharedTransitionScope) {
        Column(
            modifier = Modifier
                .padding(top = 200.dp, start = 16.dp, end = 16.dp)
                .sharedBounds(
                    rememberSharedContentState(key = "bounds"),
                    animatedVisibilityScope = animatedVisibilityScope,
                    enter = fadeIn(),
                    exit = fadeOut(),
                    resizeMode = SharedTransitionScope.ResizeMode.ScaleToBounds()
                )
                // ...

        ) {
            // ...
        }
    }
}

पांचवी इमेज. दो कंपोज़ेबल के बीच शेयर की गई सीमाएं.

दायरों को समझें

Modifier.sharedElement() का इस्तेमाल करने के लिए, कंपोज़ेबल को SharedTransitionScope. SharedTransitionLayout कंपोज़ेबल, SharedTransitionScope. अपने यूज़र इंटरफ़ेस (यूआई) हैरारकी, जिसमें ऐसे एलिमेंट शामिल हैं जिन्हें आपको शेयर करना है.

आम तौर पर, कंपोज़ेबल को किसी AnimatedVisibilityScope. आम तौर पर, यह जानकारी AnimatedContent का इस्तेमाल करके दी जाती है कंपोज़ेबल के बीच स्विच करने के लिए या AnimatedVisibility का इस्तेमाल करते समय NavHost कंपोज़ेबल फ़ंक्शन, जब तक कि प्रॉडक्ट के दिखने की सेटिंग को मैनेज नहीं किया जाता मैन्युअल तरीके से. ऑर्डर में शामिल है कई स्कोप का इस्तेमाल करने के लिए, अपने ज़रूरी दायरे को ComposeLocal, Kotlin की मदद से कॉन्टेक्स्ट रिसीवर में स्कोप का इस्तेमाल आपके फ़ंक्शन के पैरामीटर के रूप में करता है.

उस स्थिति में CompositionLocals का इस्तेमाल करें जहां आपको एक से ज़्यादा स्कोप रखने हों ट्रैक कर सकते हैं, या एक गहराई से नेस्ट किए हुए क्रम के बारे में सोच सकते हैं. CompositionLocal की मदद से, सेव करने और इस्तेमाल करने के लिए सटीक दायरे. वहीं दूसरी ओर, जब कॉन्टेक्स्ट रिसीवर का इस्तेमाल किया जाता है, आपकी हैरारकी के अन्य लेआउट, गलती से दिए गए स्कोप को बदल सकते हैं. उदाहरण के लिए, अगर आपके पास नेस्ट किए गए एक से ज़्यादा AnimatedContent हैं, तो दायरे ओवरराइड किया गया.

val LocalNavAnimatedVisibilityScope = compositionLocalOf<AnimatedVisibilityScope?> { null }
val LocalSharedTransitionScope = compositionLocalOf<SharedTransitionScope?> { null }

@Composable
private fun SharedElementScope_CompositionLocal() {
    // An example of how to use composition locals to pass around the shared transition scope, far down your UI tree.
    // ...
    SharedTransitionLayout {
        CompositionLocalProvider(
            LocalSharedTransitionScope provides this
        ) {
            // This could also be your top-level NavHost as this provides an AnimatedContentScope
            AnimatedContent(state, label = "Top level AnimatedContent") { targetState ->
                CompositionLocalProvider(LocalNavAnimatedVisibilityScope provides this) {
                    // Now we can access the scopes in any nested composables as follows:
                    val sharedTransitionScope = LocalSharedTransitionScope.current
                        ?: throw IllegalStateException("No SharedElementScope found")
                    val animatedVisibilityScope = LocalNavAnimatedVisibilityScope.current
                        ?: throw IllegalStateException("No AnimatedVisibility found")
                }
                // ...
            }
        }
    }
}

इसके अलावा, अगर आपका क्रम गहराई से नेस्ट नहीं किया गया है, तो आप स्कोप पास कर सकते हैं नीचे पैरामीटर के रूप में रखें:

@Composable
fun MainContent(
    animatedVisibilityScope: AnimatedVisibilityScope,
    sharedTransitionScope: SharedTransitionScope
) {
}

@Composable
fun Details(
    animatedVisibilityScope: AnimatedVisibilityScope,
    sharedTransitionScope: SharedTransitionScope
) {
}

AnimatedVisibility के साथ शेयर किए गए एलिमेंट

पिछले उदाहरणों में AnimatedContent के साथ शेयर किए गए एलिमेंट को इस्तेमाल करने का तरीका दिखाया गया था, लेकिन शेयर किए गए एलिमेंट AnimatedVisibility के साथ भी काम करते हैं.

उदाहरण के लिए, इस लेज़ी ग्रिड उदाहरण में हर एलिमेंट को रैप किया गया है AnimatedVisibility. जब किसी आइटम पर क्लिक किया जाता है - तो कॉन्टेंट में यूज़र इंटरफ़ेस (यूआई) से बाहर निकाले गए, एक डायलॉग जैसे कॉम्पोनेंट में दिखने का विज़ुअल इफ़ेक्ट.

var selectedSnack by remember { mutableStateOf<Snack?>(null) }

SharedTransitionLayout(modifier = Modifier.fillMaxSize()) {
    LazyColumn(
        // ...
    ) {
        items(listSnacks) { snack ->
            AnimatedVisibility(
                visible = snack != selectedSnack,
                enter = fadeIn() + scaleIn(),
                exit = fadeOut() + scaleOut(),
                modifier = Modifier.animateItem()
            ) {
                Box(
                    modifier = Modifier
                        .sharedBounds(
                            sharedContentState = rememberSharedContentState(key = "${snack.name}-bounds"),
                            // Using the scope provided by AnimatedVisibility
                            animatedVisibilityScope = this,
                            clipInOverlayDuringTransition = OverlayClip(shapeForSharedElement)
                        )
                        .background(Color.White, shapeForSharedElement)
                        .clip(shapeForSharedElement)
                ) {
                    SnackContents(
                        snack = snack,
                        modifier = Modifier.sharedElement(
                            state = rememberSharedContentState(key = snack.name),
                            animatedVisibilityScope = this@AnimatedVisibility
                        ),
                        onClick = {
                            selectedSnack = snack
                        }
                    )
                }
            }
        }
    }
    // Contains matching AnimatedContent with sharedBounds modifiers.
    SnackEditDetails(
        snack = selectedSnack,
        onConfirmClick = {
            selectedSnack = null
        }
    )
}

छठी इमेज.AnimatedVisibility के साथ शेयर किए गए एलिमेंट.

संशोधक ऑर्डरिंग

Modifier.sharedElement() और Modifier.sharedBounds() के साथ आपके मॉडिफ़ायर चेन मायने रखती है, यह Compose की बाकी सुविधाओं की तरह है. साइज़ पर असर डालने वाले मॉडिफ़ायर का गलत प्लेसमेंट शेयर किए गए एलिमेंट मैचिंग के दौरान, अनचाहे विज़ुअल जंपिंग की वजह बन सकती है.

उदाहरण के लिए, अगर पैडिंग मॉडिफ़ायर को दो हिस्सों में किसी अलग पोज़िशन पर रखा जाता है शेयर किए जाने वाले एलिमेंट के साथ, ऐनिमेशन में एक विज़ुअल अंतर है.

var selectFirst by remember { mutableStateOf(true) }
val key = remember { Any() }
SharedTransitionLayout(
    Modifier
        .fillMaxSize()
        .padding(10.dp)
        .clickable {
            selectFirst = !selectFirst
        }
) {
    AnimatedContent(targetState = selectFirst, label = "AnimatedContent") { targetState ->
        if (targetState) {
            Box(
                Modifier
                    .padding(12.dp)
                    .sharedBounds(
                        rememberSharedContentState(key = key),
                        animatedVisibilityScope = this@AnimatedContent
                    )
                    .border(2.dp, Color.Red)
            ) {
                Text(
                    "Hello",
                    fontSize = 20.sp
                )
            }
        } else {
            Box(
                Modifier
                    .offset(180.dp, 180.dp)
                    .sharedBounds(
                        rememberSharedContentState(
                            key = key,
                        ),
                        animatedVisibilityScope = this@AnimatedContent
                    )
                    .border(2.dp, Color.Red)
                    // This padding is placed after sharedBounds, but it doesn't match the
                    // other shared elements modifier order, resulting in visual jumps
                    .padding(12.dp)

            ) {
                Text(
                    "Hello",
                    fontSize = 36.sp
                )
            }
        }
    }
}

मैच होने वाली सीमाएं

मेल न खाने वाली सीमा: ध्यान दें कि शेयर किए गए एलिमेंट का ऐनिमेशन थोड़ा अलग दिख रहा है, क्योंकि इसका साइज़ गलत सीमाओं के हिसाब से बदलना होगा

शेयर किए गए एलिमेंट मॉडिफ़ायर से पहले इस्तेमाल किए गए मॉडिफ़ायर से कंस्ट्रेंट मिलता है को साझा तत्व संशोधकों से जोड़ा जा सकता है, जिसका उपयोग फिर प्रारंभिक बिंदु प्राप्त करने के लिए किया जाता है और टारगेट बाउंड और बाद में बाउंड ऐनिमेशन.

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

इसका अपवाद यह है कि आपresizeMode = ScaleToBounds() एनिमेशन या कंपोज़ेबल में Modifier.skipToLookaheadSize(). इसमें केस, कंपोज़, टारगेट कंस्ट्रेंट का इस्तेमाल करके बच्चे के लिए लेआउट बनाता है. इसके बजाय, लेआउट का साइज़ बदलने के बजाय ऐनिमेशन करने के लिए एक स्केल फ़ैक्टर वह भी ऐसा कर सकता है.

खास कुंजियां

जटिल शेयर किए गए एलिमेंट के साथ काम करते समय, कुंजी बनाना अच्छा होता है यह एक स्ट्रिंग नहीं है, क्योंकि स्ट्रिंग के मेल खाने में गड़बड़ी हो सकती है. हर कुंजी के लिए ज़रूरी है कि मिलान होने के लिए अद्वितीय होना चाहिए. उदाहरण के लिए, JetSnap में हमारे पास ये चीज़ें हैं शेयर किए गए एलिमेंट:

सातवीं इमेज. यूज़र इंटरफ़ेस (यूआई) के हर हिस्से के लिए, एनोटेशन के साथ Jetspark दिखा रही इमेज.

शेयर किए गए एलिमेंट टाइप को दिखाने के लिए, एक enum बनाया जा सकता है. इस उदाहरण में पूरे स्नैक कार्ड को होम में अलग-अलग जगहों से भी दिखाया जा सकता है स्क्रीन, उदाहरण के लिए "लोकप्रिय" में और "सुझाया गया" सेक्शन में जाएं. आपके पास ऐसी कुंजी जिसमें snackId, origin ("लोकप्रिय" / "सुझाया गया") और शेयर किए जाने वाले शेयर किए गए एलिमेंट का type:

data class SnackSharedElementKey(
    val snackId: Long,
    val origin: String,
    val type: SnackSharedElementType
)

enum class SnackSharedElementType {
    Bounds,
    Image,
    Title,
    Tagline,
    Background
}

@Composable
fun SharedElementUniqueKey() {
    // ...
            Box(
                modifier = Modifier
                    .sharedElement(
                        rememberSharedContentState(
                            key = SnackSharedElementKey(
                                snackId = 1,
                                origin = "latest",
                                type = SnackSharedElementType.Image
                            )
                        ),
                        animatedVisibilityScope = this@AnimatedVisibility
                    )
            )
            // ...
}

कुंजियों के लिए डेटा क्लास का सुझाव दिया जाता है, क्योंकि ये hashCode() और isEquals().

शेयर किए गए एलिमेंट को दिखाने की सुविधा को मैन्युअल तरीके से मैनेज करें

उन मामलों में जहां हो सकता है कि आप AnimatedVisibility या AnimatedContent का इस्तेमाल न कर रहे हों, आप साझा तत्व की दृश्यता को स्वयं प्रबंधित कर सकते हैं. इस्तेमाल की जाने वाली चीज़ें Modifier.sharedElementWithCallerManagedVisibility() और अपनी जानकारी दें कंडिशनल, जो यह तय करता है कि आइटम कब दिखना चाहिए या नहीं:

var selectFirst by remember { mutableStateOf(true) }
val key = remember { Any() }
SharedTransitionLayout(
    Modifier
        .fillMaxSize()
        .padding(10.dp)
        .clickable {
            selectFirst = !selectFirst
        }
) {
    Box(
        Modifier
            .sharedElementWithCallerManagedVisibility(
                rememberSharedContentState(key = key),
                !selectFirst
            )
            .background(Color.Red)
            .size(100.dp)
    ) {
        Text(if (!selectFirst) "false" else "true", color = Color.White)
    }
    Box(
        Modifier
            .offset(180.dp, 180.dp)
            .sharedElementWithCallerManagedVisibility(
                rememberSharedContentState(
                    key = key,
                ),
                selectFirst
            )
            .alpha(0.5f)
            .background(Color.Blue)
            .size(180.dp)
    ) {
        Text(if (selectFirst) "false" else "true", color = Color.White)
    }
}

मौजूदा सीमाएं

इन एपीआई की कुछ सीमाएं होती हैं. सबसे ज़्यादा ध्यान देने वाली बातें:

  • व्यू और कंपोज़ की सुविधा के बीच इंटरऑपरेबिलिटी (दूसरे सिस्टम के साथ काम करने की सुविधा) काम नहीं करती. इसमें ये शामिल हैं कोई भी कंपोज़ेबल जिसमें AndroidView शामिल हो, जैसे कि Dialog.
  • इनके लिए कोई स्वचालित ऐनिमेशन समर्थन नहीं है:
    • शेयर की गई इमेज कंपोज़ेबल:
      • ContentScale डिफ़ॉल्ट रूप से ऐनिमेट नहीं होता है. यह तय किए गए समय पर सही तरह से काम करता है ContentScale.
    • आकार की क्लिपिंग - ऑटोमैटिक करने की सुविधा पहले से मौजूद नहीं है आकृतियों के बीच ऐनिमेशन - उदाहरण के लिए, स्क्वेयर से गोले को आइटम ट्रांज़िशन के तौर पर लिखें.
    • काम न करने वाले मामलों में, इसके बजाय Modifier.sharedBounds() का इस्तेमाल करें sharedElement() और आइटम में Modifier.animateEnterExit() जोड़ें.