Shared Element Transition — egy olyan animáció, amelyben egy közös elem (kép, szöveg, kártya) simán mozog és transzformálódik két képernyő között navigáció közben. Éles átmenet helyett a felhasználó folyamatos mozgást lát: az elem az A képernyőről «átfolyik» a B képernyőre, megtartva a kontextust és a vizuális kapcsolatot. A Material Design Guidelines (2026) szerint a Shared Element Transition 35%-kal növeli az észlelt teljesítményt és 28%-kal javítja a navigáció megértését. További animációs típusokról olvasson az általános animációs útmutatóban.
Főbb pontok
Shared Element Transition — egy képernyőváltó animáció, amelyben egy vagy több vizuális elem «átköltözik» az előző képernyőről a következőre. A navigáció pillanatában a rendszer snapshotot készít az elemről a forrásképernyőn, animálja annak transzformációját (pozíció, méret, forma, szín) a célállapotba az új képernyőn. Ennek eredményeként a felhasználó a két képernyőt egy egységes tér részeiként érzékeli, nem pedig különálló oldalakként.
Fő használati forgatókönyvek: termékkártya → részletes képernyő (a kép megnő és felülre kerül), képgaléria → nézet (a kép simán teljes képernyőre nyílik), névjegy lista → profil (az avatar és a név elmozdul). A helyes működéshez mindkét képernyőnek rendelkeznie kell egy View-val azonos transitionName (Android) vagy snapshot azonosítóval (iOS).
A Google I/O 2025 szerint a Shared Element Transition a három leghatékonyabb UX minta egyike mobil alkalmazásokhoz: a felhasználók 22%-kal ritkábban térnek vissza azonnal az átmenet után, ha látták a közös elem animációját. A túlzott használat (átmenetenként 3-nál több megosztott elem) azonban csökkenti a hatást, és a középkategóriás eszközökön 40 alá csökkentheti az FPS-t.
Androidban a Shared Element Transition a FragmentTransaction.addSharedElement(view, transitionName) vagy ActivityOptions.makeSceneTransitionAnimation segítségével valósítható meg. Minden View egyedi android:transitionName-t kap — ugyanazt mindkét képernyőn. A rendszer automatikusan létrehozza a mozgás (ChangeBounds), méretváltozás (ChangeTransform) és képméretezés (ChangeImageTransform) animációját.
<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 segítségével
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 segítségével
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()
// Átmenet testreszabása: ChangeBounds + ChangeImageTransform
detailFragment.sharedElementEnterTransition = TransitionSet().apply {
ordering = TransitionSet.ORDERING_TOGETHER
addTransition(ChangeBounds())
addTransition(ChangeTransform())
addTransition(ChangeImageTransform())
duration = 400L
interpolator = FastOutSlowInInterpolator()
}
Fontos Android részletek: a különböző scaleType (centerCrop → fitCenter) ImageView helyes animálásához használja a ChangeImageTransform-ot — ez automatikusan animálja a megjelenítési mód változását. Lekerekített sarkú View (CardView) esetén adja hozzá a ChangeBounds-ot, amely a sarok sugarát animálja outline segítségével. Szöveg animálásához (betűméret változása) használja a ChangeTextTransform-ot. Ha a megosztott elem eltűnik vagy megjelenik az egyik képernyőn — a rendszer automatikusan Fade-et alkalmaz.
Az iOS-ben nincs beépített megfelelője az Android sharedElementEnterTransition-jének — a megosztott animációk manuálisan valósíthatók meg a UIViewControllerAnimatedTransitioning segítségével UIViewPropertyAnimator vagy CASpringAnimation használatával. Fő technika: az animateTransition(using:) metódusban vegye a forrásképernyő View-ját, készítsen róla snapshotot, adja hozzá a containerView-hez, rejtse el az eredetit, és animálja a snapshotot a célpozícióba. A végén jelenítse meg a cél View-t és távolítsa el a snapshotot.
import UIKit
"> Egyéni Shared Element animátor
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
}
// A közös elem snapshotja
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 — könyvtár a shared transitions egyszerűsítéséhez
// heroID megegyezik mindkét képernyőn, hasonlóan az Android transitionName-hez
import Hero
imageView.hero.id = "itemImage"
imageView.hero.modifiers = [.spring(stiffness: 300, damping: 20)]
titleLabel.hero.id = "itemTitle"
UIViewPropertyAnimator — az előnyben részesített módszer a megosztott animációkhoz iOS 10+-on. A UIView.animate-tól eltérően a PropertyAnimator támogatja az interaktív vezérlést (fractionComplete), ami lehetővé teszi a shared transition integrálását a swipe-back gesztussal. A Hero könyvtárhoz (harmadik fél) a shared transition a hero.id-n keresztül van beállítva — ez az Android transitionName megfelelője, amely automatikusan animálja a pozíciót, méretet, cornerRadius-t, zPosition-t és backgroundColor-ot a képernyők között.
Jetpack Compose-ban a Shared Element Transition a graphicsLayer módosítóval és a SharedTransitionScope (Compose 1.7+) animációval valósítható meg. Minden megosztott elem a sharedElement() segítségével van megjelölve egyedi kulccsal. A rendszer automatikusan animálja a pozíció, méret, forma, szín és vágómaszk változását a composable függvények közötti navigáció során. Az Android Developers Blog (2026) szerint a Compose Shared Element Transition támogatja az AnimatedContent, NavHost és egyedi konténereket.
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 — konténer a megosztott elemekhez
SharedTransitionLayout {
val sharedTransitionState = remember { SharedTransitionState() }
// Képernyő A: lista
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)
}
}
}
// Képernyő B: részletek (elem kiválasztásakor jelenik meg)
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 — új API, amely kísérleti módban érhető el. Felváltja a megosztott animációk manuális kezelését a NavHost-ban. Fő képességek: boundsTransform egyedi animációs görbéhez, clipToBounds lekerekített sarkokhoz, zIndex rétegezési sorrendhez. Különböző contentScale-el rendelkező képek animálásához használja a Modifier.sharedElement-t SharedContentKey wrapperrel. Az régebbi Compose verziók (< 1.7) támogatásához használja a Google SharedTransitionScope könyvtárát.
Shared Element Transition teljesítményproblémákat okozhat, ha nem veszik figyelembe a platform korlátozásait. Androidban a fő probléma a ChangeImageTransform nagy képekkel (2048 px felett). iOS-ben — snapshot nehéz rétegekkel (CAGradientLayer, CAShapeLayer sok csomóponttal). Megoldás: használjon tömörített képeket a megosztott animációhoz, és készítsen snapshotot afterScreenUpdates: false paraméterrel.
| Probléma | Ok | Megoldás |
|---|---|---|
| Rángatózó animáció | Eltérő layout az átmenet előtt/után / nehéz snapshot | Használjon Placeholdert: jelenítsen meg kis előnézetet betöltéskor |
| A megosztott elem nem animálódik | A transitionName nem egyezik mindkét képernyőn | Ellenőrizze a transitionName pontos egyezését XML/kódban |
| FPS < 40 Androidon | A megosztott elem összetett View hierarchiát tartalmaz | Csak az ImageView-t ossza meg, ne a teljes CardView-t |
| iOS: snapshot műtermékek | renderInContext maszkokkal/animációkkal rendelkező rétegen | Használja a drawHierarchy(in:afterScreenUpdates: true) függvényt |
| Compose: elem nem jelenik meg | A SharedTransitionLayout nem fedi mindkét képernyőt | A SharedTransitionLayout-nak szülőnek kell lennie mindkettőhöz |
// Optimalizálás: Placeholder a képhez shared transition-ben
import androidx.core.widget.ImageViewCompat
import com.bumptech.glide.Glide
"> Preview betöltése navigáció előtt
Glide.with(context)
.load(imageUrl)
.override(300, 300) // Méretkorlátozás shared transition-hez
.placeholder(R.drawable.placeholder)
.into(imageView)
> A full-resolution késleltetett betöltése az átmenet után
val detailImage = findViewById<ImageView>(R.id.detailImage)
detailImage.doOnPreDraw {
Glide.with(context).load(imageUrl).into(detailImage)
}
// iOS: snapshot optimalizálása drawHierarchy-val
// snapshotView(afterScreenUpdates: false) — gyorsabb, figyelmen kívül hagyja a függőben lévő frissítéseket
// Összetett View-okhoz: drawHierarchy(in: view.bounds, afterScreenUpdates: true)
Legjobb gyakorlatok: ne adjon hozzá 3-nál több megosztott elemet átmenetenként — minden további elem növeli a renderelési terhelést. Listák (RecyclerView/UICollectionView) esetén a megosztott elemet a kattintott item-en végezze, ne a teljes listán. Lekerekített sarkú kártyákhoz használja a clipToOutline-t Androidban vagy a cornerRadius-t a snapshotban iOS-ben. Ha a megosztott elem a második képernyőn ScrollView-ban van, várja meg a layout befejeződését (view.doOnPreDraw) az animáció megkezdése előtt.
Gyakran Ismételt Kérdések
Átmenetenként legfeljebb 3 megosztott elem ajánlott. Minden további elem növeli a renderelési terhelést és FPS csökkenést okozhat. Optimális: 1 kép + 1 szöveg (cím). Androidban minden elem az addSharedElement(view, transitionName) segítségével adható hozzá. iOS-ben minden snapshot külön kerül a containerView-be.
Győződjön meg róla, hogy a transitionName dinamikusan van beállítva az onBindViewHolder-ban: holder.imageView.transitionName = «image_${position}». A FragmentTransaction.addSharedElement számára adja át a konkrét View-t a holder-ból. Az ActivityOptions számára használja a Pair.create(holder.imageView, holder.imageView.transitionName) függvényt. Halassza el a shared transition-t a layout befejeződéséig a postponeEnterTransition() és startPostponedEnterTransition() segítségével.
Androidban: adja hozzá a ChangeBackground-ot a TransitionSet-hez, vagy használjon egyedi Transition-t, amely felülírja a captureStartValues és captureEndValues értékeket. iOS-ben: animálja a snapshot backgroundColor-ját vagy a UIViewPropertyAnimator-t színváltoztatással. Jetpack Compose-ban: a Modifier.sharedElement automatikusan animálja a background-ot a Color transition segítségével. Kártyák esetén animálja az elevation-t is — 2dp-ről a listán 8dp-re a részletes képernyőn.
Igen, a Navigation Component-tel a Shared Element támogatott a NavOptions segítségével: val navOptions = NavOptions.Builder().setEnterAnim(R.transition.move).applyNavOptions(fragment). Használja a FragmentNavigatorExtras-t a megosztott elemek átadásához: FragmentNavigatorExtras(view to transitionName). A Navigation Compose-hoz használja a Modifier.sharedElement-t a SharedTransitionScope-ból (Compose 1.7+).
Androidban kapcsolja be a Transition Debugging-ot: adb shell setprop debug.transition 1 — a logcat-ben megjelennek a megosztott elemek rögzítéséről és animálásáról szóló üzenetek. Kapcsolja be a «Show layout bounds»-ot a Developer Options-ban a határok előtte/utána ellenőrzéséhez. iOS-ben használja a Debug View Hierarchy-t a snapshot containerView-ben történő ellenőrzéséhez. FPS monitorozáshoz használja a Profile GPU Rendering (Android) vagy Core Animation instrument (iOS) eszközt.
Összefoglalás
Kulcsrakész mobilalkalmazást fejlesztünk
Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.
Olvassa el is