Shared Element Transition — qu'est-ce que c'est, éléments partagés dans les transitions

Auteur : IT Sectr Publié le : 2026-03-02 Temps de lecture : 10 min

Shared Element Transition est une animation dans laquelle un élément partagé (image, texte, carte) se déplace et se transforme en douceur entre deux écrans lors de la navigation. Au lieu d'une transition brutale, l'utilisateur voit un mouvement continu : l'élément de l'écran A passe à l'écran B, préservant le contexte et la connexion visuelle. Selon les Material Design Guidelines (2026), Shared Element Transition augmente les performances perçues de 35% et améliore la compréhension de la navigation de 28%. En savoir plus sur les autres types d'animations dans le guide général d'animation.

Points clés

  • Shared Element Transition — animation d'un élément partagé entre deux écrans lors de la navigation.
  • sharedElementEnterTransition — attribut XML Android pour configurer l'entrée de l'élément partagé.
  • UIViewPropertyAnimator — base des animations partagées personnalisées entre UIViewControllers sous iOS.
  • L'élément partagé est défini via transitionName sous Android et tag ou snapshot sous iOS.
  • L'animation fonctionne avec ImageView, TextView, CardView, Button et toute View avec un identifiant unique.

Qu'est-ce que Shared Element Transition ?

Shared Element Transition est une animation de transition d'écran où un ou plusieurs éléments visuels se déplacent de l'écran précédent vers l'écran suivant. Au moment de la navigation, le système prend un instantané de l'élément sur l'écran source, anime sa transformation (position, taille, forme, couleur) vers l'état cible sur le nouvel écran. En conséquence, l'utilisateur perçoit les deux écrans comme faisant partie d'un seul espace plutôt que comme des pages séparées.

Principaux cas d'utilisation : carte produit → écran détail (l'image s'agrandit et monte), galerie d'images → visionneuse (l'image s'étend doucement en plein écran), liste de contacts → profil (avatar et nom se déplacent). Pour un fonctionnement correct, les deux écrans doivent avoir des Views avec le même transitionName (Android) ou identifiant d'instantané (iOS).

Selon Google I/O 2025, Shared Element Transition fait partie des trois modèles UX les plus efficaces pour les applications mobiles : les utilisateurs sont 22% moins susceptibles de revenir en arrière immédiatement après une transition s'ils ont vu l'animation de l'élément partagé. Cependant, une utilisation excessive (plus de 3 éléments partagés par transition) réduit l'effet et peut faire chuter le FPS en dessous de 40 sur les appareils de milieu de gamme.

Shared Element Transition sous Android (Kotlin)

Sous Android, Shared Element Transition est implémenté via FragmentTransaction.addSharedElement(view, transitionName) ou ActivityOptions.makeSceneTransitionAnimation. Chaque View reçoit un android:transitionName unique — le même sur les deux écrans. Le système crée automatiquement une animation de mouvement (ChangeBounds), une animation de changement de taille (ChangeTransform) et une animation de mise à l'échelle d'image (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()

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

Détails importants Android : pour l'animation correcte d'ImageView avec différents scaleType (centerCrop → fitCenter), utilisez ChangeImageTransform — il anime automatiquement le changement de mode d'affichage. Pour les Views avec coins arrondis (CardView), ajoutez ChangeBounds, qui anime le rayon des coins via le contour. Pour l'animation de texte (changement de taille de police), utilisez ChangeTextTransform. Si un élément partagé disparaît ou apparaît sur l'un des écrans, le système applique automatiquement Fade.

Shared Element Transition sous iOS (Swift)

iOS n'a pas d'équivalent intégré au sharedElementEnterTransition d'Android — les animations partagées sont implémentées manuellement via UIViewControllerAnimatedTransitioning en utilisant UIViewPropertyAnimator ou CASpringAnimation. La technique principale : dans animateTransition(using:), obtenez la View de l'écran source, prenez un instantané, ajoutez-le au containerView, masquez l'original et animez l'instantané vers la position cible. À la fin, affichez la View cible et supprimez l'instantané.

swift
import UIKit

"> Animateur Shared Element personnalisé
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
        }

        // Instantané de l'élément partagé
        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 — bibliothèque pour simplifier les transitions partagées
// heroID correspond sur les deux écrans, similaire à transitionName dans Android
import Hero

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

UIViewPropertyAnimator est la méthode préférée pour les animations partagées sous iOS 10+. Contrairement à UIView.animate, PropertyAnimator prend en charge le contrôle interactif (fractionComplete), permettant d'intégrer la transition partagée avec le geste de balayage retour. Pour la bibliothèque Hero (tiers), la transition partagée est définie via hero.id — un analogue de transitionName d'Android, qui anime automatiquement la position, la taille, cornerRadius, zPosition et backgroundColor entre les écrans.

Shared Element dans Jetpack Compose

Dans Jetpack Compose, Shared Element Transition est implémenté via le modificateur graphicsLayer avec l'animation SharedTransitionScope (Compose 1.7+). Chaque élément partagé est marqué avec sharedElement() en utilisant une clé unique. Le système anime automatiquement les changements de position, taille, forme, couleur et masque de découpage lors de la navigation entre les fonctions composables. Selon Android Developers Blog (2026), Compose Shared Element Transition prend en charge AnimatedContent, NavHost et les conteneurs personnalisés.

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 — conteneur pour les éléments partagés
SharedTransitionLayout {
    val sharedTransitionState = remember { SharedTransitionState() }

    // Écran 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)
            }
        }
    }

    // Écran B : détails (affiché lors de la sélection d'un élément)
    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 — une nouvelle API disponible en mode expérimental. Elle remplace la gestion manuelle des animations partagées dans NavHost. Fonctionnalités clés : boundsTransform pour une courbe d'animation personnalisée, clipToBounds pour les coins arrondis, zIndex pour l'ordre d'empilement. Pour animer des images avec différents contentScale, utilisez Modifier.sharedElement avec le wrapper SharedContentKey. Pour les versions anciennes de Compose (< 1.7), utilisez la bibliothèque SharedTransitionScope de Google.

Optimisation et problèmes courants

Shared Element Transition peut causer des problèmes de performance si les limitations de la plateforme ne sont pas prises en compte. Sous Android, le principal problème est ChangeImageTransform avec les grandes images (plus de 2048 px). Sous iOS — les instantanés avec des couches lourdes (CAGradientLayer, CAShapeLayer avec de nombreux nœuds). Solution : utilisez des images compressées pour l'animation partagée et prenez l'instantané avec afterScreenUpdates: false.

ProblèmeCauseSolution
Animation saccadéeDisposition différente avant/après la transition / instantané lourdUtilisez Placeholder : affichez un petit aperçu pendant le chargement
L'élément partagé ne s'anime pastransitionName ne correspond pas sur les deux écransVérifiez la correspondance exacte de transitionName dans le XML/le code
FPS < 40 sur AndroidL'élément partagé contient une hiérarchie de View complexePartagez uniquement ImageView, pas la CardView entière
iOS : artefacts d'instantanérenderInContext sur une couche avec masques/animationsUtilisez drawHierarchy(in:afterScreenUpdates: true)
Compose : l'élément n'apparaît pasSharedTransitionLayout n'encadre pas les deux écransSharedTransitionLayout doit être le parent des deux
kotlin
// Optimisation : Placeholder pour l'image dans la transition partagée
import androidx.core.widget.ImageViewCompat
import com.bumptech.glide.Glide

"> Chargement de l'aperçu avant la navigation
Glide.with(context)
    .load(imageUrl)
    .override(300, 300) // Limite de taille pour la transition partagée
    .placeholder(R.drawable.placeholder)
    .into(imageView)

> Chargement différé de la full-resolution après la transition
val detailImage = findViewById<ImageView>(R.id.detailImage)
detailImage.doOnPreDraw {
    Glide.with(context).load(imageUrl).into(detailImage)
}

// iOS : optimisation d'instantané avec drawHierarchy
// snapshotView(afterScreenUpdates: false) — plus rapide, ignore les mises à jour en attente
// Pour les Views complexes : drawHierarchy(in: view.bounds, afterScreenUpdates: true)

Meilleures pratiques : n'ajoutez pas plus de 3 éléments partagés par transition — chaque élément supplémentaire augmente la charge de rendu. Pour les listes (RecyclerView/UICollectionView), créez l'élément partagé sur l'élément cliqué, pas sur la liste entière. Pour les cartes avec coins arrondis, utilisez clipToOutline sur Android ou cornerRadius dans l'instantané sur iOS. Si l'élément partagé sur le deuxième écran se trouve dans un ScrollView, attendez la fin de la disposition (view.doOnPreDraw) avant de démarrer l'animation.

Questions fréquentes

Combien d'éléments partagés peuvent être animés en une seule transition ?

Il est recommandé de ne pas utiliser plus de 3 éléments partagés par transition. Chaque élément supplémentaire augmente la charge de rendu et peut provoquer des chutes de FPS. Optimal : 1 image + 1 texte (titre). Pour Android, chaque élément est ajouté via addSharedElement(view, transitionName). Pour iOS, chaque instantané est ajouté séparément au containerView.

Shared Element Transition ne fonctionne pas avec RecyclerView — que faire ?

Assurez-vous que transitionName est défini dynamiquement dans onBindViewHolder : holder.imageView.transitionName = "image_${position}". Pour FragmentTransaction.addSharedElement, transmettez la View spécifique du holder. Pour ActivityOptions, utilisez Pair.create(holder.imageView, holder.imageView.transitionName). Retardez la transition partagée jusqu'à la fin de la disposition via postponeEnterTransition() et startPostponedEnterTransition().

Comment faire Shared Element Transition avec un changement de couleur d'arrière-plan ?

Sur Android : ajoutez ChangeBackground à TransitionSet ou utilisez une Transition personnalisée redéfinissant captureStartValues et captureEndValues. Sur iOS : animez le backgroundColor de l'instantané ou utilisez UIViewPropertyAnimator avec changement de couleur. Dans Jetpack Compose : Modifier.sharedElement anime automatiquement l'arrière-plan via la transition Color. Pour les cartes, animez également l'élévation — de 2dp dans la liste à 8dp sur l'écran détail.

Shared Element fonctionne-t-il sur Android avec Jetpack Navigation ?

Oui, avec le Navigation Component, Shared Element est pris en charge via NavOptions : val navOptions = NavOptions.Builder().setEnterAnim(R.transition.move).applyNavOptions(fragment). Utilisez FragmentNavigatorExtras pour passer les éléments partagés : FragmentNavigatorExtras(view to transitionName). Pour Navigation Compose, utilisez Modifier.sharedElement de SharedTransitionScope (Compose 1.7+).

Comment déboguer Shared Element Transition ?

Sur Android, activez le Transition Debugging : adb shell setprop debug.transition 1 — dans logcat, des messages apparaîtront sur la capture et l'animation des éléments partagés. Activez « Show layout bounds » dans Developer Options pour vérifier les limites avant/après. Sur iOS, utilisez Debug View Hierarchy pour vérifier l'instantané dans containerView. Pour les FPS, utilisez Profile GPU Rendering (Android) ou l'instrument Core Animation (iOS).

Résumé

  • Shared Element Transition — animation d'un élément partagé entre les écrans en préservant le contexte visuel.
  • Android : FragmentTransaction.addSharedElement ou ActivityOptions.makeSceneTransitionAnimation.
  • iOS : implémentation personnalisée via UIViewControllerAnimatedTransitioning + instantané + PropertyAnimator.
  • Jetpack Compose : Modifier.sharedElement de SharedTransitionScope (Compose 1.7+).
  • Maximum de 3 éléments partagés par transition pour éviter les chutes de FPS.
  • Pour ImageView avec différents scaleType, utilisez ChangeImageTransform (Android) ou instantané (iOS).
  • Bibliothèque Hero (iOS) — solution tierce avec hero.id, similaire à transitionName sous Android.

Nous développerons une application mobile clé en main

IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.

Discuter du projet

Lisez aussi