انتقال عناصر مشترک روشی یکپارچه برای انتقال بین ترکیباتی است که محتوای آنها با هم سازگار است. آنها اغلب برای پیمایش استفاده میشوند و به شما امکان میدهند هنگام پیمایش کاربر بین صفحات مختلف، آنها را به صورت بصری به هم متصل کنید.
برای مثال، در ویدیوی زیر، میتوانید تصویر و عنوان میان وعده را که از صفحه فهرست به صفحه جزئیات به اشتراک گذاشته شده است، مشاهده کنید.
در Compose، چند API سطح بالا وجود دارد که به شما در ایجاد عناصر مشترک کمک میکنند:
-  
SharedTransitionLayout: بیرونیترین طرح مورد نیاز برای پیادهسازی انتقال عناصر مشترک. این طرح یکSharedTransitionScopeفراهم میکند. Composableها برای استفاده از اصلاحکنندههای عنصر مشترک باید در یکSharedTransitionScopeباشند. -  
Modifier.sharedElement(): اصلاحکنندهای که composable ای را که باید با composable دیگری مطابقت داشته باشد، بهSharedTransitionScopeعلامتگذاری میکند. -  
Modifier.sharedBounds(): اصلاحکنندهای که بهSharedTransitionScopeعلامتگذاری میکند که مرزهای این composable باید به عنوان مرزهای نگهدارنده برای جایی که انتقال باید انجام شود، استفاده شوند. برخلافsharedElement()،sharedBounds()برای محتوای بصری متفاوت طراحی شده است. 
یک مفهوم مهم هنگام ایجاد عناصر مشترک در Compose، نحوه کار آنها با روکشها و برش است. برای کسب اطلاعات بیشتر در مورد این موضوع مهم، به بخش برش و پوششها مراجعه کنید.
کاربرد اولیه
گذار زیر در این بخش ساخته خواهد شد که از آیتم «لیست» کوچکتر به آیتم جزئیات بزرگتر منتقل میشود:

 بهترین روش برای استفاده از Modifier.sharedElement() همراه با AnimatedContent ، AnimatedVisibility یا NavHost است، زیرا این کار انتقال بین composableها را به طور خودکار برای شما مدیریت میکند.
 نقطه شروع یک AnimatedContent پایه موجود است که قبل از افزودن عناصر مشترک، دارای MainContent و DetailsContent قابل ترکیب است: 
AnimatedContent بدون هیچ گونه انتقال عنصر مشترک.
برای اینکه عناصر مشترک بین دو طرحبندی متحرک شوند،
AnimatedContentcomposable را با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 ) } } }
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()برای پشتیبانی از تغییرات فونت مانند تغییر بین حالت ایتالیک و بولد یا تغییر رنگ ترجیح داده میشود. 
 از مثال قبلی، اضافه کردن Modifier.sharedBounds() به Row و 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() ، عنصر composable باید در یک SharedTransitionScope باشد. عنصر composable SharedTransitionLayout عنصر SharedTransitionScope را فراهم میکند. مطمئن شوید که عنصر composable را در همان نقطه سطح بالایی از سلسله مراتب رابط کاربری خود قرار میدهید که شامل عناصری است که میخواهید به اشتراک بگذارید.
 به طور کلی، composableها باید درون یک AnimatedVisibilityScope نیز قرار گیرند. این امر معمولاً با استفاده از AnimatedContent برای جابجایی بین composableها یا هنگام استفاده مستقیم از AnimatedVisibility یا توسط تابع composable NavHost فراهم میشود، مگر اینکه شما قابلیت مشاهده را به صورت دستی مدیریت کنید . برای استفاده از چندین scope، scopeهای مورد نیاز خود را در یک CompositionLocal ذخیره کنید، از context receivers در Kotlin استفاده کنید، یا scopeها را به عنوان پارامتر به توابع خود ارسال کنید.
 در سناریویی که چندین scope برای پیگیری دارید یا یک سلسله مراتب تو در تو دارید، از CompositionLocals استفاده کنید. CompositionLocal به شما امکان میدهد scopeهای دقیقی را برای ذخیره و استفاده انتخاب کنید. از طرف دیگر، وقتی از گیرندههای context استفاده میکنید، سایر layoutها در سلسله مراتب شما ممکن است به طور تصادفی scopeهای ارائه شده را override کنند. به عنوان مثال، اگر چندین AnimatedContent تو در تو دارید، scopeها میتوانند override شوند. 
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 نیز کار میکنند.
 برای مثال، در این مثال lazy grid، هر عنصر در 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( sharedContentState = 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، اهمیت دارد. قرار دادن نادرست اصلاحکنندههای مؤثر بر اندازه میتواند باعث پرشهای بصری غیرمنتظره در طول تطبیق عنصر مشترک شود.
برای مثال، اگر یک اصلاحکنندهی فاصلهگذاری (padding modifier) را در موقعیتهای مختلف روی دو عنصر مشترک قرار دهید، یک تفاوت بصری در انیمیشن ایجاد میشود.
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() یا برای یک composable از Modifier.skipToLookaheadSize() استفاده کنید. در این حالت، Compose فرزند را با استفاده از محدودیتهای هدف طرحبندی میکند و در عوض به جای تغییر اندازه خود طرحبندی، از یک عامل مقیاس برای اجرای انیمیشن استفاده میکند.
کلیدهای منحصر به فرد
هنگام کار با عناصر مشترک پیچیده، ایجاد کلیدی که رشته نباشد، تمرین خوبی است، زیرا رشتهها میتوانند مستعد خطا در تطابق باشند. هر کلید باید منحصر به فرد باشد تا تطابقها رخ دهند. برای مثال، در Jetsnack عناصر مشترک زیر را داریم:

 شما میتوانید یک 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) } }
محدودیتهای فعلی
این APIها چند محدودیت دارند. مهمترین آنها:
-  هیچ قابلیت همکاری بین Views و Compose پشتیبانی نمیشود. این شامل هر composable که 
AndroidViewرا در بر میگیرد، مانندDialogیاModalBottomSheetنیز میشود. -  پشتیبانی خودکار از انیمیشن برای موارد زیر وجود ندارد:
-  ترکیبات تصویر مشترک :
-  
ContentScaleبه طور پیشفرض متحرک نیست. به انتهای تنظیمشدهیContentScaleمیچسبد. 
 -  
 - برش شکل - هیچ پشتیبانی داخلی برای انیمیشن خودکار بین شکلها وجود ندارد - برای مثال، انیمیشن از یک مربع به یک دایره هنگام انتقال آیتم.
 -  برای موارد پشتیبانی نشده، به جای 
sharedElement()ازModifier.sharedBounds()استفاده کنید وModifier.animateEnterExit()به موارد اضافه کنید. 
 -  ترکیبات تصویر مشترک :