Shared Element Transition — cos'è, elementi condivisi nelle transizioni

Autore: IT Sectr Pubblicato: 2026-03-02 Tempo di lettura: 10 min

Shared Element Transition è un'animazione in cui un elemento condiviso (immagine, testo, carta) si muove e si trasforma senza soluzione di continuità tra due schermate durante la navigazione. Invece di una transizione brusca, l'utente vede un movimento continuo: l'elemento dalla schermata A fluisce verso la schermata B, preservando il contesto e la connessione visiva. Secondo le Material Design Guidelines (2026), Shared Element Transition aumenta le prestazioni percepite del 35% e migliora la comprensione della navigazione del 28%. Ulteriori informazioni sugli altri tipi di animazione nella guida generale all'animazione.

Punti chiave

  • Shared Element Transition — animazione di un elemento condiviso tra due schermate durante la navigazione.
  • sharedElementEnterTransition — attributo XML Android per configurare l'ingresso dell'elemento condiviso.
  • UIViewPropertyAnimator — base per animazioni condivise personalizzate tra UIViewController in iOS.
  • L'elemento condiviso viene definito tramite transitionName in Android e tag o snapshot in iOS.
  • L'animazione funziona con ImageView, TextView, CardView, Button e qualsiasi View con identificatore univoco.

Cos'è Shared Element Transition?

Shared Element Transition è un'animazione di transizione dello schermo in cui uno o più elementi visivi si spostano dallo schermo precedente a quello successivo. Al momento della navigazione, il sistema acquisisce un'istantanea dell'elemento sullo schermo di origine, ne anima la trasformazione (posizione, dimensione, forma, colore) verso lo stato di destinazione sul nuovo schermo. Di conseguenza, l'utente percepisce i due schermi come parti di un unico spazio anziché pagine separate.

Principali casi d'uso: scheda prodotto → schermata dettagli (l'immagine si ingrandisce e si sposta verso l'alto), galleria immagini → visualizzatore (l'immagine si espande dolcemente a schermo intero), elenco contatti → profilo (avatar e nome si spostano). Per un funzionamento corretto, entrambi gli schermi devono avere Views con lo stesso transitionName (Android) o identificatore di snapshot (iOS).

Secondo Google I/O 2025, Shared Element Transition è tra i tre modelli UX più efficaci per le applicazioni mobili: gli utenti hanno il 22% in meno di probabilità di tornare indietro immediatamente dopo una transizione se hanno visto l'animazione dell'elemento condiviso. Tuttavia, un uso eccessivo (più di 3 elementi condivisi per transizione) riduce l'effetto e può causare un calo del FPS al di sotto di 40 su dispositivi di fascia media.

Shared Element Transition in Android (Kotlin)

In Android, Shared Element Transition viene implementato tramite FragmentTransaction.addSharedElement(view, transitionName) o ActivityOptions.makeSceneTransitionAnimation. A ciascuna View viene assegnato un android:transitionName univoco — lo stesso su entrambi gli schermi. Il sistema crea automaticamente un'animazione di movimento (ChangeBounds), un'animazione di cambio dimensione (ChangeTransform) e un'animazione di ridimensionamento dell'immagine (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 tramite 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 tramite 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()

// Personalizzazione transizione: ChangeBounds + ChangeImageTransform
detailFragment.sharedElementEnterTransition = TransitionSet().apply {
    ordering = TransitionSet.ORDERING_TOGETHER
    addTransition(ChangeBounds())
    addTransition(ChangeTransform())
    addTransition(ChangeImageTransform())
    duration = 400L
    interpolator = FastOutSlowInInterpolator()
}

Dettagli importanti Android: per l'animazione corretta di ImageView con scaleType diverso (centerCrop → fitCenter), utilizzare ChangeImageTransform — anima automaticamente il cambio della modalità di visualizzazione. Per Views con angoli arrotondati (CardView), aggiungere ChangeBounds, che anima il raggio degli angoli tramite outline. Per l'animazione del testo (cambio dimensione carattere), utilizzare ChangeTextTransform. Se un elemento condiviso scompare o appare su uno degli schermi, il sistema applica automaticamente Fade.

Shared Element Transition in iOS (Swift)

iOS non ha un equivalente integrato dello sharedElementEnterTransition di Android — le animazioni condivise vengono implementate manualmente tramite UIViewControllerAnimatedTransitioning utilizzando UIViewPropertyAnimator o CASpringAnimation. La tecnica principale: in animateTransition(using:), ottenere la View dello schermo di origine, fare uno snapshot, aggiungerlo al containerView, nascondere l'originale e animare lo snapshot verso la posizione di destinazione. Alla fine, mostrare la View di destinazione e rimuovere lo snapshot.

swift
import UIKit

"> Animatore Shared Element personalizzato
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 dell'elemento condiviso
        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 — libreria per semplificare le transizioni condivise
// heroID corrisponde su entrambi gli schermi, simile a transitionName in Android
import Hero

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

UIViewPropertyAnimator è il metodo preferito per le animazioni condivise su iOS 10+. A differenza di UIView.animate, PropertyAnimator supporta il controllo interattivo (fractionComplete), consentendo di integrare la transizione condivisa con il gesto di scorrimento all'indietro. Per la libreria Hero (terze parti), la transizione condivisa viene impostata tramite hero.id — un analogo di transitionName di Android, che anima automaticamente posizione, dimensione, cornerRadius, zPosition e backgroundColor tra gli schermi.

Shared Element in Jetpack Compose

In Jetpack Compose, Shared Element Transition viene implementato tramite il modificatore graphicsLayer con l'animazione SharedTransitionScope (Compose 1.7+). Ciascun elemento condiviso viene contrassegnato con sharedElement() utilizzando una chiave univoca. Il sistema anima automaticamente i cambiamenti di posizione, dimensione, forma, colore e maschera di ritaglio durante la navigazione tra le funzioni composable. Secondo Android Developers Blog (2026), Compose Shared Element Transition supporta AnimatedContent, NavHost e contenitori personalizzati.

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 — contenitore per elementi condivisi
SharedTransitionLayout {
    val sharedTransitionState = remember { SharedTransitionState() }

    // Schermata A: elenco
    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)
            }
        }
    }

    // Schermata B: dettagli (mostrata quando si seleziona un 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 — una nuova API disponibile in modalità sperimentale. Sostituisce la gestione manuale delle animazioni condivise in NavHost. Caratteristiche principali: boundsTransform per una curva di animazione personalizzata, clipToBounds per angoli arrotondati, zIndex per l'ordine di sovrapposizione. Per animare immagini con contentScale diverso, utilizzare Modifier.sharedElement con il wrapper SharedContentKey. Per le versioni precedenti di Compose (< 1.7), utilizzare la libreria SharedTransitionScope di Google.

Ottimizzazione e problemi comuni

Shared Element Transition può causare problemi di prestazioni se non si considerano i limiti della piattaforma. Su Android, il problema principale è ChangeImageTransform con immagini grandi (oltre 2048 px). Su iOS — snapshot con livelli pesanti (CAGradientLayer, CAShapeLayer con molti nodi). Soluzione: utilizzare immagini compresse per l'animazione condivisa e acquisire lo snapshot con afterScreenUpdates: false.

ProblemaCausaSoluzione
Animazione a scattiLayout diverso prima/dopo la transizione / snapshot pesanteUtilizzare Placeholder: mostrare un'anteprima piccola durante il caricamento
L'elemento condiviso non si animatransitionName non corrisponde su entrambi gli schermiVerificare la corrispondenza esatta di transitionName in XML/codice
FPS < 40 su AndroidL'elemento condiviso contiene una gerarchia di View complessaCondividere solo ImageView, non l'intera CardView
iOS: artefatti dello snapshotrenderInContext su layer con maschere/animazioniUtilizzare drawHierarchy(in:afterScreenUpdates: true)
Compose: l'elemento non appareSharedTransitionLayout non avvolge entrambi gli schermiSharedTransitionLayout deve essere il genitore di entrambi
kotlin
// Ottimizzazione: Placeholder per l'immagine nella transizione condivisa
import androidx.core.widget.ImageViewCompat
import com.bumptech.glide.Glide

"> Caricamento dell'anteprima prima della navigazione
Glide.with(context)
    .load(imageUrl)
    .override(300, 300) // Limite di dimensione per la transizione condivisa
    .placeholder(R.drawable.placeholder)
    .into(imageView)

> Caricamento ritardato della full-resolution dopo la transizione
val detailImage = findViewById<ImageView>(R.id.detailImage)
detailImage.doOnPreDraw {
    Glide.with(context).load(imageUrl).into(detailImage)
}

// iOS: ottimizzazione dello snapshot con drawHierarchy
// snapshotView(afterScreenUpdates: false) — più veloce, ignora gli aggiornamenti in sospeso
// Per Views complesse: drawHierarchy(in: view.bounds, afterScreenUpdates: true)

Best practice: non aggiungere più di 3 elementi condivisi per transizione — ogni elemento aggiuntivo aumenta il carico di rendering. Per gli elenchi (RecyclerView/UICollectionView), creare l'elemento condiviso sull'elemento cliccato, non sull'intero elenco. Per le carte con angoli arrotondati, utilizzare clipToOutline su Android o cornerRadius nello snapshot su iOS. Se l'elemento condiviso sul secondo schermo si trova all'interno di uno ScrollView, attendere il completamento del layout (view.doOnPreDraw) prima di avviare l'animazione.

Domande frequenti

Quanti elementi condivisi possono essere animati in una transizione?

Si consiglia di non utilizzare più di 3 elementi condivisi per transizione. Ogni elemento aggiuntivo aumenta il carico di rendering e può causare cali di FPS. Ottimale: 1 immagine + 1 testo (titolo). Per Android, ogni elemento viene aggiunto tramite addSharedElement(view, transitionName). Per iOS, ogni snapshot viene aggiunto separatamente al containerView.

Shared Element Transition non funziona con RecyclerView — cosa fare?

Assicurarsi che transitionName sia impostato dinamicamente in onBindViewHolder: holder.imageView.transitionName = "image_${position}". Per FragmentTransaction.addSharedElement, passare la View specifica dal holder. Per ActivityOptions, utilizzare Pair.create(holder.imageView, holder.imageView.transitionName). Ritardare la transizione condivisa fino al completamento del layout tramite postponeEnterTransition() e startPostponedEnterTransition().

Come fare Shared Element Transition con cambio di colore di sfondo?

Su Android: aggiungere ChangeBackground a TransitionSet o utilizzare una Transition personalizzata che sovrascriva captureStartValues e captureEndValues. Su iOS: animare il backgroundColor dello snapshot o utilizzare UIViewPropertyAnimator con cambio di colore. In Jetpack Compose: Modifier.sharedElement anima automaticamente lo sfondo tramite la transizione Color. Per le carte, animare anche l'elevazione — da 2dp nell'elenco a 8dp nella schermata dettagli.

Shared Element funziona su Android con Jetpack Navigation?

Sì, con Navigation Component, Shared Element è supportato tramite NavOptions: val navOptions = NavOptions.Builder().setEnterAnim(R.transition.move).applyNavOptions(fragment). Utilizzare FragmentNavigatorExtras per passare gli elementi condivisi: FragmentNavigatorExtras(view to transitionName). Per Navigation Compose, utilizzare Modifier.sharedElement da SharedTransitionScope (Compose 1.7+).

Come eseguire il debug di Shared Element Transition?

Su Android, attivare il Transition Debugging: adb shell setprop debug.transition 1 — in logcat appariranno messaggi sull'acquisizione e l'animazione degli elementi condivisi. Attivare "Show layout bounds" in Developer Options per verificare i confini prima/dopo. Su iOS, utilizzare Debug View Hierarchy per verificare lo snapshot in containerView. Per FPS, utilizzare Profile GPU Rendering (Android) o lo strumento Core Animation (iOS).

Riepilogo

  • Shared Element Transition — animazione di un elemento condiviso tra schermate preservando il contesto visivo.
  • Android: FragmentTransaction.addSharedElement o ActivityOptions.makeSceneTransitionAnimation.
  • iOS: implementazione personalizzata tramite UIViewControllerAnimatedTransitioning + snapshot + PropertyAnimator.
  • Jetpack Compose: Modifier.sharedElement da SharedTransitionScope (Compose 1.7+).
  • Massimo 3 elementi condivisi per transizione per evitare cali di FPS.
  • Per ImageView con scaleType diverso, utilizzare ChangeImageTransform (Android) o snapshot (iOS).
  • Libreria Hero (iOS) — soluzione di terze parti con hero.id, simile a transitionName in Android.

Svilupperemo un'applicazione mobile chiavi in mano

IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.

Discuti il progetto

Leggi anche