Shared Element Transition — navigatsiya vaqtida umumiy element (tasvir, matn, karta) ikki ekran o'rtasida silliq harakatlanadigan va transformatsiyalanadigan animatsiya. Keskin o'tish o'rniga foydalanuvchi uzluksiz harakatni ko'radi: element A ekrandan B ekranga «oqadi», kontekst va vizual bog'lanishni saqlaydi. Material Design Guidelines (2026) ma'lumotlariga ko'ra, Shared Element Transition qabul qilinadigan samaradorlikni 35% ga oshiradi va navigatsiyani tushunishni 28% ga yaxshilaydi. Animatsiyaning boshqa turlari haqida umumiy animatsiya qo'llanmasida o'qing.
Asosiy
Shared Element Transition — bu bir yoki bir nechta vizual elementlarning oldingi ekrandan keyingi ekranga «ko'chadigan» ekran almashish animatsiyasi. Navigatsiya paytida tizim elementni manba ekranda snapshot qiladi, uning transformatsiyasini (pozitsiya, o'lcham, shakl, rang) yangi ekranda maqsadli holatga animatsiya qiladi. Natijada foydalanuvchi ikki ekranni alohida sahifalar emas, balki yagona makonning qismlari sifatida qabul qiladi.
Asosiy foydalanish stsenariylari: mahsulot kartasi → tafsilotli ekran (tasvir kattalashadi va yuqoriga o'tadi), tasvir galereyasi → ko'rish (tasvir butun ekranga silliq ochiladi), kontaktlar ro'yxati → profil (avatar va ism ko'chadi). To'g'ri ishlash uchun ikkala ekranda bir xil transitionName (Android) yoki snapshot identifikatori (iOS) bo'lgan View bo'lishi kerak.
Google I/O 2025 ma'lumotlariga ko'ra, Shared Element Transition mobil ilovalar uchun eng samarali UX naqshlaridan biri hisoblanadi: umumiy element animatsiyasini ko'rgan foydalanuvchilar o'tishdan keyin darhol qaytishni 22% kamroq qiladi. Biroq, haddan tashqari foydalanish (o'tish boshiga 3 dan ortiq shared element) effektni kamaytiradi va o'rta segment qurilmalarda FPS 40 dan pastga tushishiga olib kelishi mumkin.
Android da Shared Element Transition FragmentTransaction.addSharedElement(view, transitionName) yoki ActivityOptions.makeSceneTransitionAnimation orqali amalga oshiriladi. Har bir View ga noyob android:transitionName beriladi — ikkala ekranda bir xil. Tizim avtomatik ravishda harakat (ChangeBounds), o'lcham o'zgarishi (ChangeTransform) va tasvir masshtablash (ChangeImageTransform) animatsiyasini yaratadi.
<androidx.cardview.widget.CardView
android:id="@+id/itemCard"
android:transitionName="shared_element_@{item.id}"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/itemImage"
android:transitionName="shared_image_@{item.id}" />
</androidx.cardview.widget.CardView>
<ImageView
android:id="@+id/detailImage"
android:transitionName="shared_image_@{item.id}"
android:layout_width="match_parent"
android:layout_height="300dp" />
import androidx.core.app.ActivityOptionsCompat
import androidx.core.util.Pair
import androidx.fragment.app.FragmentTransaction
// Activity: Shared Element ActivityOptions orqali
val options = ActivityOptionsCompat.makeSceneTransitionAnimation(
this,
Pair(imageView, getString(R.string.transition_name_image)),
Pair(titleView, getString(R.string.transition_name_title))
)
startActivity(intent, options.toBundle())
// Fragment: Shared Element FragmentTransaction orqali
val detailFragment = DetailFragment().apply {
sharedElementEnterTransition = TransitionInflater.from(context)
.inflateTransition(android.R.transition.move)
sharedElementReturnTransition = TransitionInflater.from(context)
.inflateTransition(android.R.transition.move)
}
supportFragmentManager.beginTransaction()
.addSharedElement(imageView, imageView.transitionName)
.addSharedElement(titleView, titleView.transitionName)
.replace(R.id.container, detailFragment)
.addToBackStack(null)
.commit()
// O'tishni moslashtirish: ChangeBounds + ChangeImageTransform
detailFragment.sharedElementEnterTransition = TransitionSet().apply {
ordering = TransitionSet.ORDERING_TOGETHER
addTransition(ChangeBounds())
addTransition(ChangeTransform())
addTransition(ChangeImageTransform())
duration = 400L
interpolator = FastOutSlowInInterpolator()
}
Android muhim tafsilotlari: turli scaleType (centerCrop → fitCenter) bilan ImageView ni to'g'ri animatsiya qilish uchun ChangeImageTransform dan foydalaning — u ko'rsatish usulining o'zgarishini avtomatik animatsiya qiladi. Dumaloq burchakli View (CardView) uchun ChangeBounds qo'shing, u burchak radiusini outline orqali animatsiya qiladi. Matn animatsiyasi (shrift hajmi o'zgarishi) uchun ChangeTextTransform dan foydalaning. Shared element ekranlardan birida yo'qolsa yoki paydo bo'lsa — tizim avtomatik ravishda Fade qo'llaydi.
iOS da Android sharedElementEnterTransition ning o'rnatilgan analogi yo'q — shared-animatsiyalar UIViewControllerAnimatedTransitioning orqali UIViewPropertyAnimator yoki CASpringAnimation yordamida qo'lda amalga oshiriladi. Asosiy texnika: animateTransition(using:) ichida manba ekranning View ni oling, uning snapshot ini yarating, containerView ga qo'shing, asl nusxani yashiring va snapshot ni maqsadli pozitsiyaga animatsiya qiling. Tugaganda maqsadli View ni ko'rsating va snapshot ni o'chiring.
import UIKit
"> Shared Element uchun custom animator
class SharedElementAnimator: NSObject, UIViewControllerAnimatedTransitioning {
let fromView: UIView
let toView: UIView
let fromFrame: CGRect
let toFrame: CGRect
let isPresenting: Bool
func transitionDuration(using ctx: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.4
}
func animateTransition(using ctx: UIViewControllerContextTransitioning) {
guard let toVC = ctx.viewController(forKey: .to),
let fromVC = ctx.viewController(forKey: .from)
else { return }
let container = ctx.containerView
if isPresenting {
container.addSubview(toVC.view)
toVC.view.layoutIfNeeded()
toVC.view.alpha = 0
}
// Umumiy elementning snapshot i
let snapshot = fromView.snapshotView(afterScreenUpdates: false)!
snapshot.frame = isPresenting ? fromFrame : toFrame
snapshot.layer.cornerRadius = isPresenting ? 12 : 0
container.addSubview(snapshot)
fromView.isHidden = true
toView.isHidden = true
UIView.animate(withDuration: transitionDuration(using: ctx),
delay: 0,
usingSpringWithDamping: 0.7,
initialSpringVelocity: 0) {
snapshot.frame = self.isPresenting ? self.toFrame : self.fromFrame
snapshot.layer.cornerRadius = self.isPresenting ? 0 : 12
toVC.view.alpha = 1
} completion: { _ in
fromView.isHidden = false
toView.isHidden = false
snapshot.removeFromSuperview()
ctx.completeTransition(!ctx.transitionWasCancelled)
}
}
}
// Hero — shared transitions soddalashtirish uchun kutubxona
// heroID ikkala ekranda mos keladi, Android dagi transitionName ga o'xshash
import Hero
imageView.hero.id = "itemImage"
imageView.hero.modifiers = [.spring(stiffness: 300, damping: 20)]
titleLabel.hero.id = "itemTitle"
UIViewPropertyAnimator — iOS 10+ da shared-animatsiyalar uchun afzal qilingan usul. UIView.animate dan farqli o'laroq, PropertyAnimator interaktiv boshqaruvni (fractionComplete) qo'llab-quvvatlaydi, bu shared transition ni swipe-back imo-ishorasi bilan integratsiya qilish imkonini beradi. Hero kutubxonasi (uchinchi tomon) uchun shared transition hero.id orqali o'rnatiladi — bu Android dagi transitionName ning analogi bo'lib, ekranlar o'rtasida pozitsiya, o'lcham, cornerRadius, zPosition va backgroundColor ni avtomatik animatsiya qiladi.
Jetpack Compose da Shared Element Transition graphicsLayer modifikatori va SharedTransitionScope (Compose 1.7+) animatsiyasi orqali amalga oshiriladi. Har bir shared element noyob kalit bilan sharedElement() orqali belgilanadi. Tizim composable funksiyalar o'rtasida navigatsiya vaqtida pozitsiya, o'lcham, shakl, rang va kesish maskasining o'zgarishini avtomatik animatsiya qiladi. Android Developers Blog (2026) ma'lumotlariga ko'ra, Compose Shared Element Transition AnimatedContent, NavHost va maxsus konteynerlarni qo'llab-quvvatlaydi.
import androidx.compose.animation.core.spring
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
import androidx.compose.animation.SharedTransitionLayout
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
// SharedTransitionLayout — shared elementlar uchun konteyner
SharedTransitionLayout {
val sharedTransitionState = remember { SharedTransitionState() }
// Ekran A: ro'yxat
LazyColumn {
items(items) { item ->
Card(
modifier = Modifier.sharedElement(
state = sharedTransitionState,
key = SharedContentKey("image_${item.id}"),
boundsTransform = { _, _ ->
spring(dampingRatio = Spring.DampingRatioMediumBouncy)
}
)
) {
AsyncImage(model = item.imageUrl, contentDescription = null)
}
}
}
// Ekran B: tafsilotlar (element tanlanganda ko'rsatiladi)
AnimatedContent(
targetState = selectedItem,
transitionSpec = {
EnterTransition.None using SizeTransform(clip = false)
}
) { item ->
item?.let {
Image(
modifier = Modifier.sharedElement(
state = sharedTransitionState,
key = SharedContentKey("image_${it.id}")
),
painter = rememberAsyncImagePainter(it.imageUrl),
contentDescription = null,
contentScale = ContentScale.FillWidth
)
}
}
}
Compose 1.7+ SharedTransitionScope — eksperimental rejimda mavjud bo'lgan yangi API. NavHost da shared-animatsiyalarni qo'lda boshqarishni almashtiradi. Asosiy imkoniyatlar: maxsus animatsiya egri chizig'i uchun boundsTransform, dumaloq burchaklar uchun clipToBounds, qoplama tartibi uchun zIndex. Turli contentScale bilan tasvirlarni animatsiya qilish uchun SharedContentKey o'rami bilan Modifier.sharedElement dan foydalaning. Compose ning eski versiyalarini (< 1.7) qo'llab-quvvatlash uchun Google ning SharedTransitionScope kutubxonasidan foydalaning.
Shared Element Transition platforma cheklovlari hisobga olinmasa, samaradorlik muammolariga olib kelishi mumkin. Android da asosiy muammo katta tasvirlar bilan (2048 px dan yuqori) ChangeImageTransform. iOS da — og'ir qatlamlar bilan (CAGradientLayer, ko'p sonli tugunli CAShapeLayer) snapshot. Yechim: shared-animatsiya uchun siqilgan tasvirlardan foydalaning va snapshot ni afterScreenUpdates: false bilan oling.
| Muammo | Sabab | Yechim |
|---|---|---|
| Sakrovchi animatsiya | O'tishdan oldin va keyin turlicha layout / og'ir snapshot | Placeholder dan foydalaning: yuklashda kichik oldindan ko'rishni ko'rsating |
| Shared element animatsiya qilinmaydi | transitionName ikkala ekranda mos kelmaydi | XML/kodda transitionName ning aniq mosligini tekshiring |
| FPS < 40 Android da | Shared element murakkab View iyerarxiyasini o'z ichiga oladi | Faqat ImageView ni shared qiling, butun CardView ni emas |
| iOS: snapshot artefaktlari | Maskalar/animatsiyalar bilan qatlamda renderInContext | drawHierarchy(in:afterScreenUpdates: true) dan foydalaning |
| Compose: element ko'rinmaydi | SharedTransitionLayout ikkala ekranni qamrab olmaydi | SharedTransitionLayout ikkalasi uchun ota-ona bo'lishi kerak |
// Optimallashtirish: shared transition da tasvir uchun Placeholder
import androidx.core.widget.ImageViewCompat
import com.bumptech.glide.Glide
"> Navigatsiyadan oldin preview yuklash
Glide.with(context)
.load(imageUrl)
.override(300, 300) // Shared transition uchun o'lcham cheklovi
.placeholder(R.drawable.placeholder)
.into(imageView)
> O'tishdan keyin full-resolution kechiktirilgan yuklash
val detailImage = findViewById<ImageView>(R.id.detailImage)
detailImage.doOnPreDraw {
Glide.with(context).load(imageUrl).into(detailImage)
}
// iOS: snapshot ni drawHierarchy bilan optimallashtirish
// snapshotView(afterScreenUpdates: false) — kutilayotgan yangilanishlarni e'tiborsiz qoldiradi, tezroq
// Murakkab View lar uchun: drawHierarchy(in: view.bounds, afterScreenUpdates: true)
Eng yaxshi amaliyotlar: bir o'tishda 3 tadan ortiq shared element qo'shmang — har bir qo'shimcha element render yukini oshiradi. Ro'yxatlar (RecyclerView/UICollectionView) uchun shared elementni butun ro'yxatga emas, balki bosilgan item ga qiling. Dumaloq burchakli kartalar uchun Android da clipToOutline yoki iOS da snapshot da cornerRadius dan foydalaning. Shared element ikkinchi ekranda ScrollView da bo'lsa, animatsiyani boshlashdan oldin layout tugashini kuting (view.doOnPreDraw).
Tez-tez beriladigan savollar
Bir o'tishda 3 shared elementdan ko'p bo'lmagan tavsiya etiladi. Har bir qo'shimcha element render yukini oshiradi va FPS tushishiga olib kelishi mumkin. Optimal: 1 tasvir + 1 matn (sarlavha). Android uchun har bir element addSharedElement(view, transitionName) orqali qo'shiladi. iOS uchun har bir snapshot containerView ga alohida qo'shiladi.
transitionName onBindViewHolder da dinamik o'rnatilganligiga ishonch hosil qiling: holder.imageView.transitionName = «image_${position}». FragmentTransaction.addSharedElement uchun holder dan aniq View ni uzating. ActivityOptions uchun Pair.create(holder.imageView, holder.imageView.transitionName) dan foydalaning. Shared transition ni postponeEnterTransition() va startPostponedEnterTransition() orqali layout tugaguncha kechiktiring.
Android da: TransitionSet ga ChangeBackground qo'shing yoki captureStartValues va captureEndValues ni bekor qiluvchi maxsus Transition dan foydalaning. iOS da: snapshot ning backgroundColor ni yoki UIViewPropertyAnimator ni rang o'zgarishi bilan animatsiya qiling. Jetpack Compose da: Modifier.sharedElement Color transition orqali background ni avtomatik animatsiya qiladi. Kartalar uchun elevation ni ham animatsiya qiling — ro'yxatda 2dp dan tafsilotli ekranda 8dp gacha.
Ha, Navigation Component bilan Shared Element NavOptions orqali qo'llab-quvvatlanadi: val navOptions = NavOptions.Builder().setEnterAnim(R.transition.move).applyNavOptions(fragment). Shared elementlarni uzatish uchun FragmentNavigatorExtras dan foydalaning: FragmentNavigatorExtras(view to transitionName). Navigation Compose uchun SharedTransitionScope (Compose 1.7+) dan Modifier.sharedElement dan foydalaning.
Android da Transition Debugging ni yoqing: adb shell setprop debug.transition 1 — logcat da shared elementlarni olish va animatsiya qilish haqida xabarlar paydo bo'ladi. Developer Options da «Show layout bounds» ni yoqib, oldin/keyin chegaralarni tekshiring. iOS da containerView dagi snapshot ni tekshirish uchun Debug View Hierarchy dan foydalaning. FPS kuzatuvi uchun Profile GPU Rendering (Android) yoki Core Animation instrument (iOS) dan foydalaning.
Xulosa
Biz kalit topshirig'i bilan mobil ilovani ishlab chiqamiz
IT Sectr 2017-yildan beri startaplar va korxonalar uchun iOS va Android ilovalarini yaratadi. Biz sizga maslahat beramiz va eng yaxshi yechimni taklif qilamiz.