Shared Element Transition — isang animation kung saan ang isang shared na elemento (larawan, teksto, card) ay maayos na gumagalaw at nag-transform sa pagitan ng dalawang screen habang nag-navigate. Sa halip na biglaang paglipat, nakikita ng user ang tuluy-tuloy na paggalaw: ang elemento mula sa screen A ay «dumadaloy» patungo sa screen B, pinapanatili ang konteksto at visual na koneksyon. Ayon sa Material Design Guidelines (2026), pinapataas ng Shared Element Transition ang perceived na performance ng 35% at pinapabuti ang pag-unawa sa navigation ng 28%. Magbasa pa tungkol sa iba pang uri ng animation sa pangkalahatang gabay sa animation.
Mga Pangunahing Punto
Shared Element Transition ay isang animation ng pagpapalit ng screen kung saan ang isa o higit pang visual na elemento ay «lumilipat» mula sa nakaraang screen patungo sa susunod. Sa sandali ng navigation, gumagawa ang system ng snapshot ng elemento sa source screen, ina-animate ang pagbabago nito (posisyon, laki, hugis, kulay) patungo sa target na estado sa bagong screen. Bilang resulta, nakikita ng user ang dalawang screen bilang bahagi ng iisang espasyo, hindi bilang magkahiwalay na pahina.
Mga pangunahing senaryo ng paggamit: card ng produkto → detail screen (lumalaki ang larawan at pumupunta sa itaas), gallery ng larawan → view (bumubukas nang maayos ang larawan sa buong screen), listahan ng contact → profile (gumagalaw ang avatar at pangalan). Para sa tamang paggana, ang parehong screen ay dapat may View na may parehong transitionName (Android) o snapshot identifier (iOS).
Ayon sa Google I/O 2025, ang Shared Element Transition ay isa sa tatlong pinaka-epektibong UX pattern para sa mobile apps: 22% mas madalang bumalik kaagad ang mga user pagkatapos ng transition kung nakita nila ang animation ng shared na elemento. Gayunpaman, ang sobrang paggamit (higit sa 3 shared na elemento bawat transition) ay nagbabawas ng epekto at maaaring magdulot ng pagbaba ng FPS sa ibaba 40 sa mga mid-range na device.
Sa Android, ang Shared Element Transition ay ipinapatupad sa pamamagitan ng FragmentTransaction.addSharedElement(view, transitionName) o ActivityOptions.makeSceneTransitionAnimation. Ang bawat View ay binibigyan ng natatanging android:transitionName — pareho sa parehong screen. Awtomatikong gumagawa ang system ng animation para sa paggalaw (ChangeBounds), pagbabago ng laki (ChangeTransform) at pag-scale ng larawan (ChangeImageTransform).
<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 sa pamamagitan ng ActivityOptions
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 sa pamamagitan ng FragmentTransaction
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()
// Pag-customize ng transition: ChangeBounds + ChangeImageTransform
detailFragment.sharedElementEnterTransition = TransitionSet().apply {
ordering = TransitionSet.ORDERING_TOGETHER
addTransition(ChangeBounds())
addTransition(ChangeTransform())
addTransition(ChangeImageTransform())
duration = 400L
interpolator = FastOutSlowInInterpolator()
}
Mahahalagang Detalye sa Android: para sa tamang animation ng ImageView na may ibang scaleType (centerCrop → fitCenter) gamitin ang ChangeImageTransform — awtomatiko nitong ina-animate ang pagbabago ng paraan ng pagpapakita. Para sa View na may bilugang sulok (CardView) magdagdag ng ChangeBounds, na nag-a-animate ng radius ng sulok sa pamamagitan ng outline. Para sa animation ng teksto (pagbabago ng laki ng font) gamitin ang ChangeTextTransform. Kung ang shared na elemento ay nawawala o lumilitaw sa isa sa mga screen — awtomatikong nag-aapply ang system ng Fade.
Sa iOS, walang built-in na katumbas ng sharedElementEnterTransition ng Android — ang mga shared-animation ay manu-manong ipinapatupad sa pamamagitan ng UIViewControllerAnimatedTransitioning gamit ang UIViewPropertyAnimator o CASpringAnimation. Pangunahing teknik: sa animateTransition(using:) kunin ang View ng source screen, gumawa ng snapshot nito, idagdag sa containerView, itago ang orihinal at i-animate ang snapshot patungo sa target na posisyon. Sa dulo, ipakita ang target na View at alisin ang snapshot.
import UIKit
"> Custom na Shared Element 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
}
// Snapshot ng shared na elemento
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 — library para gawing simple ang shared transitions
// heroID ay tugma sa parehong screen, katulad ng transitionName sa Android
import Hero
imageView.hero.id = "itemImage"
imageView.hero.modifiers = [.spring(stiffness: 300, damping: 20)]
titleLabel.hero.id = "itemTitle"
UIViewPropertyAnimator — ang ginustong paraan para sa shared-animation sa iOS 10+. Hindi tulad ng UIView.animate, sinusuportahan ng PropertyAnimator ang interactive na kontrol (fractionComplete), na nagpapahintulot sa pagsasama ng shared transition sa swipe-back gesture. Para sa Hero library (third-party), ang shared transition ay itinatakda sa pamamagitan ng hero.id — ito ang katumbas ng transitionName mula sa Android, na awtomatikong nag-a-animate ng posisyon, laki, cornerRadius, zPosition at backgroundColor sa pagitan ng mga screen.
Sa Jetpack Compose, ang Shared Element Transition ay ipinapatupad sa pamamagitan ng modifier na graphicsLayer na may SharedTransitionScope animation (Compose 1.7+). Ang bawat shared na elemento ay minamarkahan sa pamamagitan ng sharedElement() na may natatanging key. Awtomatikong ina-animate ng system ang pagbabago ng posisyon, laki, hugis, kulay at clipping mask habang nag-navigate sa pagitan ng mga composable function. Ayon sa Android Developers Blog (2026), sinusuportahan ng Compose Shared Element Transition ang AnimatedContent, NavHost at custom na container.
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 — container para sa mga shared na elemento
SharedTransitionLayout {
val sharedTransitionState = remember { SharedTransitionState() }
// Screen A: listahan
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)
}
}
}
// Screen B: detalye (ipinapakita kapag pumili ng elemento)
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 — bagong API na available sa experimental mode. Pinapalitan nito ang manual na pamamahala ng shared-animation sa NavHost. Mga pangunahing kakayahan: boundsTransform para sa custom na animation curve, clipToBounds para sa bilugang sulok, zIndex para sa pagkakasunud-sunod ng layer. Para sa animation ng mga larawan na may ibang contentScale, gamitin ang Modifier.sharedElement na may SharedContentKey wrapper. Para sa suporta ng mas lumang bersyon ng Compose (< 1.7) gamitin ang SharedTransitionScope library mula sa Google.
Shared Element Transition ay maaaring magdulot ng problema sa performance kung hindi isasaalang-alang ang mga limitasyon ng platform. Sa Android ang pangunahing problema ay ChangeImageTransform na may malalaking larawan (higit sa 2048 px). Sa iOS — snapshot na may mabibigat na layer (CAGradientLayer, CAShapeLayer na maraming node). Solusyon: gumamit ng naka-compress na larawan para sa shared-animation at gumawa ng snapshot na may afterScreenUpdates: false.
| Problema | Dahilan | Solusyon |
|---|---|---|
| Naalog na animation | Ibang layout bago at pagkatapos ng transition / mabigat na snapshot | Gumamit ng Placeholder: magpakita ng maliit na preview habang naglo-load |
| Hindi nag-a-animate ang shared na elemento | Hindi tugma ang transitionName sa parehong screen | Suriin ang eksaktong tugma ng transitionName sa XML/code |
| FPS < 40 sa Android | Ang shared na elemento ay naglalaman ng kumplikadong View hierarchy | Gawing shared lamang ang ImageView, hindi ang buong CardView |
| iOS: artifact ng snapshot | renderInContext sa layer na may mask/animation | Gamitin ang drawHierarchy(in:afterScreenUpdates: true) |
| Compose: hindi lumilitaw ang elemento | Hindi binabalot ng SharedTransitionLayout ang parehong screen | Ang SharedTransitionLayout ay dapat na magulang para sa pareho |
// Pag-optimize: Placeholder para sa larawan sa shared transition
import androidx.core.widget.ImageViewCompat
import com.bumptech.glide.Glide
"> Pag-load ng preview bago mag-navigate
Glide.with(context)
.load(imageUrl)
.override(300, 300) // Limitasyon sa laki para sa shared transition
.placeholder(R.drawable.placeholder)
.into(imageView)
> Naantalang pag-load ng full-resolution pagkatapos ng transition
val detailImage = findViewById<ImageView>(R.id.detailImage)
detailImage.doOnPreDraw {
Glide.with(context).load(imageUrl).into(detailImage)
}
// iOS: pag-optimize ng snapshot gamit ang drawHierarchy
// snapshotView(afterScreenUpdates: false) — mas mabilis, hindi pinapansin ang mga nakabinbing update
// Para sa kumplikadong View: drawHierarchy(in: view.bounds, afterScreenUpdates: true)
Pinakamahuhusay na Kasanayan: huwag magdagdag ng higit sa 3 shared na elemento bawat transition — bawat karagdagang elemento ay nagpapataas ng render load. Para sa mga listahan (RecyclerView/UICollectionView) gawing shared na elemento ang na-click na item, hindi ang buong listahan. Para sa mga card na may bilugang sulok gamitin ang clipToOutline sa Android o cornerRadius sa snapshot sa iOS. Kung ang shared na elemento sa pangalawang screen ay nasa ScrollView, maghintay na matapos ang layout (view.doOnPreDraw) bago simulan ang animation.
Mga Madalas Itanong
Inirerekomenda na hindi hihigit sa 3 shared na elemento bawat transition. Ang bawat karagdagang elemento ay nagpapataas ng render load at maaaring magdulot ng pagbaba ng FPS. Optimal: 1 larawan + 1 teksto (titulo). Para sa Android bawat elemento ay idinaragdag sa pamamagitan ng addSharedElement(view, transitionName). Para sa iOS bawat snapshot ay idinaragdag sa containerView nang hiwalay.
Tiyakin na ang transitionName ay itinakda nang dynamic sa onBindViewHolder: holder.imageView.transitionName = «image_${position}». Para sa FragmentTransaction.addSharedElement ibigay ang partikular na View mula sa holder. Para sa ActivityOptions gamitin ang Pair.create(holder.imageView, holder.imageView.transitionName). Ipagpaliban ang shared transition hanggang matapos ang layout sa pamamagitan ng postponeEnterTransition() at startPostponedEnterTransition().
Sa Android: magdagdag ng ChangeBackground sa TransitionSet o gumamit ng custom na Transition na nag-o-override sa captureStartValues at captureEndValues. Sa iOS: i-animate ang backgroundColor ng snapshot o UIViewPropertyAnimator na may pagbabago ng kulay. Sa Jetpack Compose: awtomatikong ina-animate ng Modifier.sharedElement ang background sa pamamagitan ng Color transition. Para sa mga card, i-animate din ang elevation — mula 2dp sa listahan hanggang 8dp sa detail screen.
Oo, sa Navigation Component sinusuportahan ang Shared Element sa pamamagitan ng NavOptions: val navOptions = NavOptions.Builder().setEnterAnim(R.transition.move).applyNavOptions(fragment). Gamitin ang FragmentNavigatorExtras para ipasa ang mga shared na elemento: FragmentNavigatorExtras(view to transitionName). Para sa Navigation Compose gamitin ang Modifier.sharedElement mula sa SharedTransitionScope (Compose 1.7+).
Sa Android i-activate ang Transition Debugging: adb shell setprop debug.transition 1 — sa logcat lilitaw ang mga mensahe tungkol sa pag-capture at animation ng mga shared na elemento. I-activate ang «Show layout bounds» sa Developer Options para suriin ang mga hangganan bago/pagkatapos. Sa iOS gamitin ang Debug View Hierarchy para suriin ang snapshot sa containerView. Para sa pag-monitor ng FPS gamitin ang Profile GPU Rendering (Android) o Core Animation instrument (iOS).
Buod
Gagawa kami ng mobile application na turnkey
Gumagawa ang IT Sectr ng mga iOS at Android application para sa mga startup at negosyo mula noong 2017. Magpapayo kami sa iyo at magmumungkahi ng pinakamahusay na solusyon.
Basahin din