Shared Element Transition — naviqasiya zamanı ümumi elementin (şəkil, mətn, kart) iki ekran arasında hamar şəkildə hərəkət etdiyi və transformasiya olduğu animasiyadır. Kəskin keçid əvəzinə istifadəçi fasiləsiz hərəkət görür: element A ekranından B ekranına «axır», konteksti və vizual əlaqəni qoruyur. Material Design Guidelines (2026) məlumatına görə, Shared Element Transition qavranılan performansı 35% artırır və naviqasiyanı başa düşməni 28% yaxşılaşdırır. Digər animasiya növləri haqqında ümumi animasiya bələdçisində oxuyun.
Əsas məqamlar
Shared Element Transition — bir və ya bir neçə vizual elementin əvvəlki ekrandan növbəti ekrana «köçdüyü» ekran dəyişmə animasiyasıdır. Naviqasiya anında sistem elementin snapshot-nı götürür, onun transformasiyasını (mövqe, ölçü, forma, rəng) yeni ekranda hədəf vəziyyətə animasiya edir. Nəticədə istifadəçi iki ekranı ayrı səhifələr deyil, vahid məkanın hissələri kimi qavrayır.
Əsas istifadə ssenariləri: məhsul kartı → detallı ekran (şəkil böyüyür və yuxarı keçir), şəkil qalereyası → baxış (şəkil hamar şəkildə tam ekrana açılır), əlaqə siyahısı → profil (avatar və ad köçür). Düzgün işləmə üçün hər iki ekranda eyni transitionName (Android) və ya snapshot identifikatoru (iOS) olan View olmalıdır.
Google I/O 2025 məlumatına görə, Shared Element Transition mobil tətbiqlər üçün ən effektiv UX nümunələrindən biridir: ümumi elementin animasiyasını görən istifadəçilər keçiddən dərhal sonra geri qayıtmağı 22% daha az edirlər. Bununla belə, həddindən artıq istifadə (keçid başına 3-dən çox shared element) effekti azaldır və orta seqment cihazlarda FPS-in 40-dan aşağı düşməsinə səbəb ola bilər.
Android-də Shared Element Transition FragmentTransaction.addSharedElement(view, transitionName) və ya ActivityOptions.makeSceneTransitionAnimation vasitəsilə həyata keçirilir. Hər View-ə unikal android:transitionName təyin edilir — hər iki ekranda eyni. Sistem avtomatik olaraq hərəkət (ChangeBounds), ölçü dəyişmə (ChangeTransform) və şəkil miqyaslama (ChangeImageTransform) animasiyası yaradır.
<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 vasitəsilə
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 vasitəsilə
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()
// Keçidin fərdiləşdirilməsi: ChangeBounds + ChangeImageTransform
detailFragment.sharedElementEnterTransition = TransitionSet().apply {
ordering = TransitionSet.ORDERING_TOGETHER
addTransition(ChangeBounds())
addTransition(ChangeTransform())
addTransition(ChangeImageTransform())
duration = 400L
interpolator = FastOutSlowInInterpolator()
}
Android vacib detalları: müxtəlif scaleType (centerCrop → fitCenter) olan ImageView-in düzgün animasiyası üçün ChangeImageTransform istifadə edin — o, göstərmə üsulunun dəyişməsini avtomatik animasiya edir. Dairəvi küncləri olan View (CardView) üçün ChangeBounds əlavə edin, o, künc radiusunu outline vasitəsilə animasiya edir. Mətn animasiyası (şrift ölçüsünün dəyişməsi) üçün ChangeTextTransform istifadə edin. Shared element ekranlardan birində yox olur və ya görünürsə — sistem avtomatik olaraq Fade tətbiq edir.
iOS-da Android sharedElementEnterTransition-un daxili analoqu yoxdur — shared-animasiyalar UIViewControllerAnimatedTransitioning vasitəsilə UIViewPropertyAnimator və ya CASpringAnimation istifadə edərək əl ilə həyata keçirilir. Əsas texnika: animateTransition(using:) daxilində mənbə ekranın View-ni götürün, onun snapshot-nı yaradın, containerView-ə əlavə edin, orijinalı gizlədin və snapshot-ı hədəf mövqeyə animasiya edin. Bitdikdə hədəf View-i göstərin və snapshot-ı silin.
import UIKit
"> Shared Element üçün 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
}
// Ümumi elementin snapshot-ı
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 sadələşdirmək üçün kitabxana
// heroID hər iki ekranda uyğun gəlir, Android-də transitionName-ə bənzər
import Hero
imageView.hero.id = "itemImage"
imageView.hero.modifiers = [.spring(stiffness: 300, damping: 20)]
titleLabel.hero.id = "itemTitle"
UIViewPropertyAnimator — iOS 10+ üçün shared-animasiyalar üçün üstünlük verilən üsuldur. UIView.animate-dən fərqli olaraq, PropertyAnimator interaktiv nəzarəti (fractionComplete) dəstəkləyir ki, bu da shared transition-ı swipe-back jesti ilə inteqrasiya etməyə imkan verir. Hero kitabxanası (üçüncü tərəf) üçün shared transition hero.id vasitəsilə təyin edilir — bu, Android-dəki transitionName-in analoqudur və ekranlar arasında mövqeni, ölçünü, cornerRadius, zPosition və backgroundColor-ı avtomatik animasiya edir.
Jetpack Compose-da Shared Element Transition graphicsLayer modifikatoru və SharedTransitionScope (Compose 1.7+) animasiyası vasitəsilə həyata keçirilir. Hər shared element unikal açar ilə sharedElement() vasitəsilə qeyd olunur. Sistem composable funksiyalar arasında naviqasiya zamanı mövqe, ölçü, forma, rəng və kəsmə maskasının dəyişməsini avtomatik animasiya edir. Android Developers Blog (2026) məlumatına görə, Compose Shared Element Transition AnimatedContent, NavHost və fərdi konteynerləri dəstəkləyir.
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 elementlər üçün konteyner
SharedTransitionLayout {
val sharedTransitionState = remember { SharedTransitionState() }
// Ekran A: siyahı
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: detallar (element seçildikdə göstərilir)
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 rejimdə mövcud olan yeni API-dir. NavHost-da shared-animasiyaların əl ilə idarə edilməsini əvəz edir. Əsas imkanlar: fərdi animasiya əyrisi üçün boundsTransform, dairəvi künclər üçün clipToBounds, üst-üstə düşmə qaydası üçün zIndex. Müxtəlif contentScale olan şəkillərin animasiyası üçün SharedContentKey sarğısı ilə Modifier.sharedElement istifadə edin. Köhnə Compose versiyalarını (< 1.7) dəstəkləmək üçün Google-dan SharedTransitionScope kitabxanasından istifadə edin.
Shared Element Transition platforma məhdudiyyətləri nəzərə alınmazsa, performans problemlərinə səbəb ola bilər. Android-də əsas problem böyük şəkillərlə (2048 px-dən yuxarı) ChangeImageTransform-dır. iOS-da — ağır təbəqələrlə (CAGradientLayer, çox sayda düyünü olan CAShapeLayer) snapshot. Həll yolu: shared-animasiya üçün sıxılmış şəkillərdən istifadə edin və snapshot-ı afterScreenUpdates: false ilə çəkin.
| Problem | Səbəb | Həll |
|---|---|---|
| Sıçrayan animasiya | Keçiddən əvvəl və sonra fərqli layout / ağır snapshot | Placeholder istifadə edin: yükləmə zamanı kiçik ön izləmə göstərin |
| Shared element animasiya olunmur | transitionName hər iki ekranda uyğun gəlmir | XML/kodda transitionName-in dəqiq uyğunluğunu yoxlayın |
| Android-də FPS < 40 | Shared element mürəkkəb View iyerarxiyası ehtiva edir | Yalnız ImageView-i shared edin, bütün CardView-i yox |
| iOS: snapshot artefaktları | Maskalar/animasiyalar olan təbəqədə renderInContext | drawHierarchy(in:afterScreenUpdates: true) istifadə edin |
| Compose: element görünmür | SharedTransitionLayout hər iki ekranı əhatə etmir | SharedTransitionLayout hər ikisi üçün valideyn olmalıdır |
// Optimallaşdırma: shared transition-da şəkil üçün Placeholder
import androidx.core.widget.ImageViewCompat
import com.bumptech.glide.Glide
"> Naviqasiyadan əvvəl preview yüklənməsi
Glide.with(context)
.load(imageUrl)
.override(300, 300) // Shared transition üçün ölçü məhdudiyyəti
.placeholder(R.drawable.placeholder)
.into(imageView)
> Keçiddən sonra full-resolution-un gecikmiş yüklənməsi
val detailImage = findViewById<ImageView>(R.id.detailImage)
detailImage.doOnPreDraw {
Glide.with(context).load(imageUrl).into(detailImage)
}
// iOS: drawHierarchy ilə snapshot optimallaşdırması
// snapshotView(afterScreenUpdates: false) — gözləyən yeniləmələri nəzərə almır, daha sürətli
// Mürəkkəb View-lər üçün: drawHierarchy(in: view.bounds, afterScreenUpdates: true)
Ən yaxşı təcrübələr: bir keçiddə 3-dən çox shared element əlavə etməyin — hər əlavə element render yükünü artırır. Siyahılar (RecyclerView/UICollectionView) üçün shared elementi bütün siyahıya deyil, kliklənmiş item-ə edin. Dairəvi küncləri olan kartlar üçün Android-də clipToOutline və ya iOS-da snapshot-da cornerRadius istifadə edin. Shared element ikinci ekranda ScrollView-dədirsə, animasiyaya başlamazdan əvvəl layout-un bitməsini gözləyin (view.doOnPreDraw).
Tez-tez verilən suallar
Bir keçiddə 3 shared elementdən çox olmaması tövsiyə olunur. Hər əlavə element render yükünü artırır və FPS düşməsinə səbəb ola bilər. Optimal: 1 şəkil + 1 mətn (başlıq). Android üçün hər element addSharedElement(view, transitionName) vasitəsilə əlavə edilir. iOS üçün hər snapshot containerView-ə ayrıca əlavə edilir.
transitionName-in onBindViewHolder-da dinamik olaraq təyin edildiyinə əmin olun: holder.imageView.transitionName = «image_${position}». FragmentTransaction.addSharedElement üçün holder-dən konkret View ötürün. ActivityOptions üçün Pair.create(holder.imageView, holder.imageView.transitionName) istifadə edin. Shared transition-ı postponeEnterTransition() və startPostponedEnterTransition() vasitəsilə layout bitənə qədər təxirə salın.
Android-də: TransitionSet-ə ChangeBackground əlavə edin və ya captureStartValues və captureEndValues-ı ləğv edən fərdi Transition istifadə edin. iOS-da: snapshot-ın backgroundColor-nı və ya UIViewPropertyAnimator-ı rəng dəyişməsi ilə animasiya edin. Jetpack Compose-da: Modifier.sharedElement Color transition vasitəsilə background-u avtomatik animasiya edir. Kartlar üçün həmçinin elevation-ı animasiya edin — siyahıda 2dp-dən detallı ekranda 8dp-ə qədər.
Bəli, Navigation Component ilə Shared Element NavOptions vasitəsilə dəstəklənir: val navOptions = NavOptions.Builder().setEnterAnim(R.transition.move).applyNavOptions(fragment). Shared elementləri ötürmək üçün FragmentNavigatorExtras istifadə edin: FragmentNavigatorExtras(view to transitionName). Navigation Compose üçün SharedTransitionScope-dən (Compose 1.7+) Modifier.sharedElement istifadə edin.
Android-də Transition Debugging-i aktivləşdirin: adb shell setprop debug.transition 1 — logcat-də shared elementlərin tutulması və animasiyası haqqında mesajlar görünəcək. Developer Options-da «Show layout bounds»-ı aktivləşdirərək əvvəl/sonra sərhədləri yoxlayın. iOS-da containerView-də snapshot-ı yoxlamaq üçün Debug View Hierarchy istifadə edin. FPS monitorinqi üçün Profile GPU Rendering (Android) və ya Core Animation instrument (iOS) istifadə edin.
Nəticə
Açar təslim mobil tətbiq hazırlayacağıq
IT Sectr 2017-ci ildən startaplar və bizneslər üçün iOS və Android tətbiqləri yaradır. Sizə məsləhət verəcəyik və ən yaxşı həlli təklif edəcəyik.
Həm də oxuyun