Shared Element Transition, gezinme sırasında ortak bir öğenin (resim, metin, kart) iki ekran arasında yumuşak bir şekilde hareket ettiği ve dönüştüğü bir animasyondur. Ani bir geçiş yerine kullanıcı sürekli bir hareket görür: A ekranındaki öğe B ekranına akar, bağlamı ve görsel bağlantıyı korur. Material Design Guidelines'a (2026) göre Shared Element Transition, algılanan performansı %35 artırır ve gezinme anlayışını %28 iyileştirir. Diğer animasyon türleri hakkında daha fazla bilgi için genel animasyon kılavuzuna bakın.
Önemli Noktalar
Shared Element Transition, bir veya daha fazla görsel öğenin önceki ekrandan sonraki ekrana taşındığı bir ekran geçiş animasyonudur. Gezinme anında sistem, kaynak ekrandaki öğenin bir anlık görüntüsünü alır, yeni ekrandaki hedef duruma dönüşümünü (konum, boyut, şekil, renk) canlandırır. Sonuç olarak kullanıcı, iki ekranı ayrı sayfalar yerine tek bir alanın parçaları olarak algılar.
Ana kullanım durumları: ürün kartı → detay ekranı (resim büyür ve yukarı hareket eder), resim galerisi → görüntüleyici (resim tüm ekrana yumuşakça genişler), kişi listesi → profil (avatar ve ad taşınır). Doğru çalışma için her iki ekranda da aynı transitionName (Android) veya anlık görüntü tanımlayıcısına (iOS) sahip View'ler bulunmalıdır.
Google I/O 2025'e göre Shared Element Transition, mobil uygulamalar için en etkili üç UX modelinden biridir: Ortak öğe animasyonunu gören kullanıcıların geçişten hemen sonra geri dönme olasılığı %22 daha düşüktür. Ancak aşırı kullanım (geçiş başına 3'ten fazla ortak öğe) etkiyi azaltır ve orta sınıf cihazlarda FPS'nin 40'ın altına düşmesine neden olabilir.
Android'de Shared Element Transition, FragmentTransaction.addSharedElement(view, transitionName) veya ActivityOptions.makeSceneTransitionAnimation aracılığıyla uygulanır. Her View'e benzersiz bir android:transitionName atanır — her iki ekranda aynı. Sistem otomatik olarak bir hareket animasyonu (ChangeBounds), bir boyut değiştirme animasyonu (ChangeTransform) ve bir resim ölçekleme animasyonu (ChangeImageTransform) oluşturur.
<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: ActivityOptions ile Shared Element
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: FragmentTransaction ile Shared Element
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()
// Geçiş Özelleştirmesi: ChangeBounds + ChangeImageTransform
detailFragment.sharedElementEnterTransition = TransitionSet().apply {
ordering = TransitionSet.ORDERING_TOGETHER
addTransition(ChangeBounds())
addTransition(ChangeTransform())
addTransition(ChangeImageTransform())
duration = 400L
interpolator = FastOutSlowInInterpolator()
}
Önemli Android Detayları: farklı scaleType (centerCrop → fitCenter) ile ImageView'ün doğru animasyonu için ChangeImageTransform kullanın — görüntüleme modu değişikliğini otomatik olarak canlandırır. Yuvarlak köşeli Views (CardView) için, anahattı kullanarak köşe yarıçapını canlandıran ChangeBounds ekleyin. Metin animasyonu (yazı tipi boyutu değişikliği) için ChangeTextTransform kullanın. Ortak bir öğe ekranlardan birinde kaybolur veya görünürse, sistem otomatik olarak Fade uygular.
iOS, Android'in sharedElementEnterTransition'ının yerleşik bir eşdeğerine sahip değildir — ortak animasyonlar UIViewControllerAnimatedTransitioning aracılığıyla UIViewPropertyAnimator veya CASpringAnimation kullanılarak manuel olarak uygulanır. Ana teknik: animateTransition(using:) içinde kaynak ekranın View'ini alın, bir anlık görüntü alın, containerView'a ekleyin, orijinali gizleyin ve anlık görüntüyü hedef konuma canlandırın. Sonunda hedef View'ı gösterin ve anlık görüntüyü kaldırın.
import UIKit
"> Özel Shared Element animatörü
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
}
// Ortak öğe anlık görüntüsü
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 — ortak geçişleri basitleştirmek için kitaplık
// heroID her iki ekranda eşleşir, Android'deki transitionName'e benzer
import Hero
imageView.hero.id = "itemImage"
imageView.hero.modifiers = [.spring(stiffness: 300, damping: 20)]
titleLabel.hero.id = "itemTitle"
UIViewPropertyAnimator, iOS 10+'ta ortak animasyonlar için tercih edilen yöntemdir. UIView.animate'in aksine, PropertyAnimator etkileşimli kontrolü (fractionComplete) destekleyerek ortak geçişin geri kaydırma hareketiyle entegre edilmesini sağlar. Hero kitaplığı (üçüncü taraf) için ortak geçiş, hero.id aracılığıyla ayarlanır — bu, Android'deki transitionName'in bir benzeridir ve ekranlar arasında konumu, boyutu, cornerRadius'u, zPosition'ı ve backgroundColor'yi otomatik olarak canlandırır.
Jetpack Compose'ta Shared Element Transition, graphicsLayer değiştiricisi aracılığıyla SharedTransitionScope animasyonu (Compose 1.7+) ile uygulanır. Her ortak öğe, benzersiz bir anahtarla sharedElement() kullanılarak işaretlenir. Sistem, composable işlevler arasında gezinirken konum, boyut, şekil, renk ve kırpma maskesi değişikliklerini otomatik olarak canlandırır. Android Developers Blog'a (2026) göre Compose Shared Element Transition, AnimatedContent, NavHost ve özel kapsayıcıları destekler.
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 — ortak öğeler için kapsayıcı
SharedTransitionLayout {
val sharedTransitionState = remember { SharedTransitionState() }
// Ekran A: liste
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: detaylar (bir öğe seçildiğinde gösterilir)
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 — deneysel modda kullanılabilen yeni bir API. NavHost'ta manuel ortak animasyon yönetiminin yerini alır. Temel özellikler: özel animasyon eğrisi için boundsTransform, yuvarlak köşeler için clipToBounds, yığınlama sırası için zIndex. Farklı contentScale ile görüntüleri canlandırmak için SharedContentKey sarmalayıcısıyla Modifier.sharedElement kullanın. Eski Compose sürümleri (< 1.7) için Google'ın SharedTransitionScope kitaplığını kullanın.
Shared Element Transition, platform sınırlamaları dikkate alınmazsa performans sorunlarına neden olabilir. Android'de ana sorun, büyük resimlerle (2048 px üzeri) ChangeImageTransform'dur. iOS'ta — ağır katmanlarla (CAGradientLayer, çok sayıda düğüme sahip CAShapeLayer) anlık görüntü. Çözüm: ortak animasyon için sıkıştırılmış resimler kullanın ve afterScreenUpdates: false ile anlık görüntü alın.
| Sorun | Neden | Çözüm |
|---|---|---|
| Takılma animasyonu | Geçiş öncesi/sonrası farklı düzen / ağır anlık görüntü | Placeholder kullanın: yüklenirken küçük önizleme gösterin |
| Ortak öğe canlandırılmıyor | transitionName her iki ekranda eşleşmiyor | XML/kodda transitionName'in tam eşleşmesini kontrol edin |
| Android'de FPS < 40 | Ortak öğe karmaşık View hiyerarşisi içeriyor | Tüm CardView'ı değil, yalnızca ImageView'ı paylaşın |
| iOS: anlık görüntü yapaylıkları | Maskeler/animasyonlar içeren katmanda renderInContext | drawHierarchy(in:afterScreenUpdates: true) kullanın |
| Compose: öğe görünmüyor | SharedTransitionLayout her iki ekranı sarmıyor | SharedTransitionLayout her ikisinin de üst öğesi olmalıdır |
// Optimizasyon: Ortak geçişte resim için Placeholder
import androidx.core.widget.ImageViewCompat
import com.bumptech.glide.Glide
"> Navigasyondan önce preview yükleme
Glide.with(context)
.load(imageUrl)
.override(300, 300) // Ortak geçiş için boyut sınırı
.placeholder(R.drawable.placeholder)
.into(imageView)
> Geçişten sonra full-resolution gecikmeli yükleme
val detailImage = findViewById<ImageView>(R.id.detailImage)
detailImage.doOnPreDraw {
Glide.with(context).load(imageUrl).into(detailImage)
}
// iOS: drawHierarchy ile anlık görüntü optimizasyonu
// snapshotView(afterScreenUpdates: false) — daha hızlı, bekleyen güncellemeleri yok sayar
// Karmaşık View'ler için: drawHierarchy(in: view.bounds, afterScreenUpdates: true)
En iyi uygulamalar: geçiş başına 3'ten fazla ortak öğe eklemeyin — her ek öğe işleme yükünü artırır. Listeler (RecyclerView/UICollectionView) için ortak öğeyi tüm liste yerine tıklanan öğede yapın. Yuvarlak köşeli kartlar için Android'de clipToOutline veya iOS'ta anlık görüntüde cornerRadius kullanın. İkinci ekrandaki ortak öğe bir ScrollView içindeyse, animasyonu başlatmadan önce düzenin tamamlanmasını bekleyin (view.doOnPreDraw).
Sıkça Sorulan Sorular
Geçiş başına 3 ortak öğeden fazla kullanılmaması önerilir. Her ek öğe işleme yükünü artırır ve FPS düşüşlerine neden olabilir. Optimal: 1 resim + 1 metin (başlık). Android için her öğe addSharedElement(view, transitionName) ile eklenir. iOS için her anlık görüntü containerView'a ayrı ayrı eklenir.
transitionName'in onBindViewHolder'da dinamik olarak ayarlandığından emin olun: holder.imageView.transitionName = "image_${position}". FragmentTransaction.addSharedElement için holder'dan belirli View'ı iletin. ActivityOptions için Pair.create(holder.imageView, holder.imageView.transitionName) kullanın. postponeEnterTransition() ve startPostponedEnterTransition() ile düzen tamamlanana kadar ortak geçişi geciktirin.
Android'de: TransitionSet'e ChangeBackground ekleyin veya captureStartValues ve captureEndValues'ı geçersiz kılan özel bir Transition kullanın. iOS'ta: anlık görüntünün backgroundColor'ını canlandırın veya renk değişikliğiyle UIViewPropertyAnimator kullanın. Jetpack Compose'ta: Modifier.sharedElement, Color geçişi aracılığıyla arka planı otomatik olarak canlandırır. Kartlar için elevation'ı da canlandırın — listede 2dp'den detay ekranında 8dp'ye.
Evet, Navigation Component ile Shared Element, NavOptions aracılığıyla desteklenir: val navOptions = NavOptions.Builder().setEnterAnim(R.transition.move).applyNavOptions(fragment). Ortak öğeleri iletmek için FragmentNavigatorExtras kullanın: FragmentNavigatorExtras(view to transitionName). Navigation Compose için SharedTransitionScope'den (Compose 1.7+) Modifier.sharedElement kullanın.
Android'de Transition Debugging'i etkinleştirin: adb shell setprop debug.transition 1 — logcat'te ortak öğe yakalama ve animasyonu hakkında mesajlar görünecektir. Sınırları kontrol etmek için Developer Options'da "Show layout bounds" seçeneğini etkinleştirin. iOS'ta containerView'daki anlık görüntüyü kontrol etmek için Debug View Hierarchy'yi kullanın. FPS için Profile GPU Rendering (Android) veya Core Animation aracını (iOS) kullanın.
Özet
Anahtar teslim bir mobil uygulama geliştireceğiz
IT Sectr, 2017'den beri girişimler ve işletmeler için iOS ve Android uygulamaları oluşturmaktadır. Size danışmanlık yapacak ve en iyi çözümü önereceğiz.
Ayrıca okuyun