Shared Element Transition — vad är det, delade element i övergångar

Författare: IT Sectr Publicerad: 2026-03-02 Lästid: 10 min

Shared Element Transition — en animering där ett delat element (bild, text, kort) smidigt flyttar sig och transformeras mellan två skärmar under navigering. Istället för en abrupt övergång ser användaren en kontinuerlig rörelse: elementet från skärm A «flyter» till skärm B och bevarar kontext och visuell koppling. Enligt Material Design Guidelines (2026) ökar Shared Element Transition upplevd prestanda med 35% och förbättrar förståelsen av navigering med 28%. Läs mer om andra typer av animering i den allmänna guiden om animering.

Huvudpunkter

  • Shared Element Transition — animering av ett delat element mellan två skärmar under navigering.
  • sharedElementEnterTransition — XML-attribut i Android för att konfigurera det delade elementets inträde.
  • UIViewPropertyAnimator — grunden för anpassade delade animeringar mellan UIViewController i iOS.
  • Det delade elementet ställs in via transitionName i Android och tag eller snapshot i iOS.
  • Animering fungerar med ImageView, TextView, CardView, Button och alla View med unik identifierare.

Vad är Shared Element Transition?

Shared Element Transition — är en animering av skärmbyte där ett eller flera visuella element «flyttar» från föregående skärm till nästa. I navigeringsögonblicket tar systemet en snapshot av elementet på källskärmen, animerar dess transformation (position, storlek, form, färg) till målstatusen på den nya skärmen. Som ett resultat uppfattar användaren två skärmar som delar av ett enhetligt utrymme, inte som separata sidor.

Huvudsakliga användningsscenarier: produktkort → detaljskärm (bilden förstoras och flyttas uppåt), bildgalleri → visning (bilden öppnas smidigt till helskärm), kontaktlista → profil (avatar och namn flyttas). För korrekt funktion måste båda skärmarna ha en View med samma transitionName (Android) eller snapshot-identifierare (iOS).

Enligt Google I/O 2025 är Shared Element Transition ett av de tre mest effektiva UX-mönstren för mobila appar: användare återvänder 22% mindre ofta omedelbart efter en övergång om de såg animeringen av det delade elementet. Överdriven användning (mer än 3 delade element per övergång) minskar dock effekten och kan orsaka FPS-fall under 40 på medelklassiga enheter.

Shared Element Transition i Android (Kotlin)

I Android implementeras Shared Element Transition via FragmentTransaction.addSharedElement(view, transitionName) eller ActivityOptions.makeSceneTransitionAnimation. Varje View tilldelas en unik android:transitionName — samma på båda skärmarna. Systemet skapar automatiskt en animering för förflyttning (ChangeBounds), storleksändring (ChangeTransform) och bildskalning (ChangeImageTransform).

xml

<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" />
kotlin
import androidx.core.app.ActivityOptionsCompat
import androidx.core.util.Pair
import androidx.fragment.app.FragmentTransaction

// Activity: Shared Element via 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 via 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()

// Anpassa övergång: ChangeBounds + ChangeImageTransform
detailFragment.sharedElementEnterTransition = TransitionSet().apply {
    ordering = TransitionSet.ORDERING_TOGETHER
    addTransition(ChangeBounds())
    addTransition(ChangeTransform())
    addTransition(ChangeImageTransform())
    duration = 400L
    interpolator = FastOutSlowInInterpolator()
}

Viktiga Android-detaljer: för korrekt animering av ImageView med olika scaleType (centerCrop → fitCenter) använd ChangeImageTransform — den animerar automatiskt förändringen av visningssätt. För View med rundade hörn (CardView) lägg till ChangeBounds, som animerar hörnradien via outline. För textanimering (teckenstorleksändring) använd ChangeTextTransform. Om det delade elementet försvinner eller visas på en av skärmarna — tillämpar systemet automatiskt Fade.

Shared Element Transition i iOS (Swift)

I iOS finns ingen inbyggd motsvarighet till Androids sharedElementEnterTransition — delade animeringar implementeras manuellt via UIViewControllerAnimatedTransitioning med UIViewPropertyAnimator eller CASpringAnimation. Huvudteknik: i animateTransition(using:) ta View från källskärmen, skapa en snapshot, lägg till i containerView, dölj originalet och animera snapshot till målpositionen. I slutet visa mål-View och ta bort snapshot.

swift
import UIKit

"> Anpassad 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 av delat element
        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 — bibliotek för att förenkla shared transitions
// heroID matchar på båda skärmarna, analogt med transitionName i Android
import Hero

imageView.hero.id = "itemImage"
imageView.hero.modifiers = [.spring(stiffness: 300, damping: 20)]
titleLabel.hero.id = "itemTitle"

UIViewPropertyAnimator — den föredragna metoden för delade animeringar på iOS 10+. Till skillnad från UIView.animate stöder PropertyAnimator interaktiv kontroll (fractionComplete), vilket möjliggör integration av shared transition med swipe-back-gesten. För Hero-biblioteket (tredje part) ställs shared transition in via hero.id — detta är motsvarigheten till transitionName från Android, som automatiskt animerar position, storlek, cornerRadius, zPosition och backgroundColor mellan skärmar.

Shared Element i Jetpack Compose

I Jetpack Compose implementeras Shared Element Transition via modifieraren graphicsLayer med SharedTransitionScope-animering (Compose 1.7+). Varje delat element markeras via sharedElement() med en unik nyckel. Systemet animerar automatiskt förändringen av position, storlek, form, färg och beskärningsmask under navigering mellan composable-funktioner. Enligt Android Developers Blog (2026) stöder Compose Shared Element Transition AnimatedContent, NavHost och anpassade containrar.

kotlin
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 för delade element
SharedTransitionLayout {
    val sharedTransitionState = remember { SharedTransitionState() }

    // Skärm 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)
            }
        }
    }

    // Skärm B: detaljer (visas när element väljs)
    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 — nytt API tillgängligt i experimentellt läge. Ersätter manuell hantering av delade animeringar i NavHost. Viktiga funktioner: boundsTransform för anpassad animeringskurva, clipToBounds för rundade hörn, zIndex för lagerordning. För animering av bilder med olika contentScale använd Modifier.sharedElement med SharedContentKey-omslag. För stöd av äldre Compose-versioner (< 1.7) använd SharedTransitionScope-biblioteket från Google.

Optimering och typiska problem

Shared Element Transition kan orsaka prestandaproblem om plattformsbegränsningar inte beaktas. I Android är huvudproblemet ChangeImageTransform med stora bilder (över 2048 px). I iOS — snapshot med tunga lager (CAGradientLayer, CAShapeLayer med många noder). Lösning: använd komprimerade bilder för delade animeringar och ta snapshot med afterScreenUpdates: false.

ProblemOrsakLösning
Hackig animeringOlika layout före/efter övergång / tung snapshotAnvänd Placeholder: visa liten förhandsvisning under laddning
Delat element animeras intetransitionName matchar inte på båda skärmarnaKontrollera exakt matchning av transitionName i XML/kod
FPS < 40 på AndroidDelat element innehåller komplex View-hierarkiDela endast ImageView, inte hela CardView
iOS: snapshot-artefakterrenderInContext på lager med masker/animeringarAnvänd drawHierarchy(in:afterScreenUpdates: true)
Compose: element visas inteSharedTransitionLayout omsluter inte båda skärmarnaSharedTransitionLayout måste vara förälder för båda
kotlin
// Optimering: Placeholder för bild i shared transition
import androidx.core.widget.ImageViewCompat
import com.bumptech.glide.Glide

"> Laddar preview före navigering
Glide.with(context)
    .load(imageUrl)
    .override(300, 300) // Storleksbegränsning för shared transition
    .placeholder(R.drawable.placeholder)
    .into(imageView)

> Fördröjd inläsning av full-resolution efter övergången
val detailImage = findViewById<ImageView>(R.id.detailImage)
detailImage.doOnPreDraw {
    Glide.with(context).load(imageUrl).into(detailImage)
}

// iOS: optimering av snapshot med drawHierarchy
// snapshotView(afterScreenUpdates: false) — snabbare, ignorerar väntande uppdateringar
// För komplexa View: drawHierarchy(in: view.bounds, afterScreenUpdates: true)

Bästa praxis: lägg inte till mer än 3 delade element per övergång — varje extra element ökar renderingsbelastningen. För listor (RecyclerView/UICollectionView) gör det delade elementet på det klickade objektet, inte på hela listan. För kort med rundade hörn använd clipToOutline i Android eller cornerRadius i snapshot i iOS. Om det delade elementet på den andra skärmen finns i en ScrollView, vänta på att layouten slutförs (view.doOnPreDraw) innan animeringen startar.

Vanliga frågor

Hur många delade element kan animeras i en övergång?

Rekommenderat är inte mer än 3 delade element per övergång. Varje extra element ökar renderingsbelastningen och kan orsaka FPS-fall. Optimalt: 1 bild + 1 text (titel). För Android läggs varje element till via addSharedElement(view, transitionName). För iOS läggs varje snapshot till i containerView separat.

Shared Element Transition fungerar inte med RecyclerView — vad gör man?

Se till att transitionName ställs in dynamiskt i onBindViewHolder: holder.imageView.transitionName = «image_${position}». För FragmentTransaction.addSharedElement skicka den specifika View från holder. För ActivityOptions använd Pair.create(holder.imageView, holder.imageView.transitionName). Skjut upp shared transition tills layouten är slutförd via postponeEnterTransition() och startPostponedEnterTransition().

Hur gör man Shared Element Transition med bakgrundsfärgsändring?

I Android: lägg till ChangeBackground i TransitionSet eller använd en anpassad Transition som åsidosätter captureStartValues och captureEndValues. I iOS: animera snapshot:ens backgroundColor eller UIViewPropertyAnimator med färgändring. I Jetpack Compose: Modifier.sharedElement animerar automatiskt background via Color transition. För kort, animera även elevation — från 2dp på listan till 8dp på detaljskärmen.

Fungerar Shared Element i Android med Jetpack Navigation?

Ja, med Navigation Component stöds Shared Element via NavOptions: val navOptions = NavOptions.Builder().setEnterAnim(R.transition.move).applyNavOptions(fragment). Använd FragmentNavigatorExtras för att skicka delade element: FragmentNavigatorExtras(view to transitionName). För Navigation Compose använd Modifier.sharedElement från SharedTransitionScope (Compose 1.7+).

Hur felsöker man Shared Element Transition?

I Android aktivera Transition Debugging: adb shell setprop debug.transition 1 — i logcat visas meddelanden om infångning och animering av delade element. Aktivera «Show layout bounds» i Developer Options för att kontrollera gränser före/efter. I iOS använd Debug View Hierarchy för att kontrollera snapshot i containerView. För FPS-övervakning använd Profile GPU Rendering (Android) eller Core Animation instrument (iOS).

Sammanfattning

  • Shared Element Transition — animering av delat element mellan skärmar med bevarande av visuell kontext.
  • Android: FragmentTransaction.addSharedElement eller ActivityOptions.makeSceneTransitionAnimation.
  • iOS: anpassad implementering via UIViewControllerAnimatedTransitioning + snapshot + PropertyAnimator.
  • Jetpack Compose: Modifier.sharedElement från SharedTransitionScope (Compose 1.7+).
  • Maximalt 3 delade element per övergång för att undvika FPS-fall.
  • För ImageView med olika scaleType använd ChangeImageTransform (Android) eller snapshot (iOS).
  • Hero-biblioteket (iOS) — tredjepartslösning med hero.id, analogt med transitionName i Android.

Vi utvecklar en mobil applikation nyckelfärdigt

IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.

Diskutera projektet

Läs också