Shared Element Transition — o que é, elementos compartilhados em transições

Autor: IT Sectr Publicado: 2026-03-02 Tempo de leitura: 10 min

Shared Element Transition é uma animação onde um elemento compartilhado (imagem, texto, cartão) se move e se transforma suavemente entre duas telas durante a navegação. Em vez de uma transição abrupta, o usuário vê um movimento contínuo: o elemento da tela A flui para a tela B, preservando o contexto e a conexão visual. De acordo com as Material Design Guidelines (2026), o Shared Element Transition aumenta o desempenho percebido em 35% e melhora a compreensão da navegação em 28%. Saiba mais sobre outros tipos de animação no guia geral de animação.

Pontos principais

  • Shared Element Transition — animação de um elemento compartilhado entre duas telas durante a navegação.
  • sharedElementEnterTransition — atributo XML do Android para configurar a entrada do elemento compartilhado.
  • UIViewPropertyAnimator — base para animações compartilhadas personalizadas entre UIViewControllers no iOS.
  • O elemento compartilhado é definido via transitionName no Android e tag ou snapshot no iOS.
  • A animação funciona com ImageView, TextView, CardView, Button e qualquer View com identificador único.

O que é Shared Element Transition?

Shared Element Transition é uma animação de transição de tela onde um ou mais elementos visuais se movem da tela anterior para a seguinte. No momento da navegação, o sistema tira um snapshot do elemento na tela de origem, anima sua transformação (posição, tamanho, forma, cor) para o estado de destino na nova tela. Como resultado, o usuário percebe as duas telas como partes de um único espaço, em vez de páginas separadas.

Principais casos de uso: cartão de produto → tela de detalhes (a imagem amplia e sobe), galeria de imagens → visualizador (a imagem se expande suavemente para tela cheia), lista de contatos → perfil (avatar e nome se movem). Para funcionar corretamente, ambas as telas devem ter Views com o mesmo transitionName (Android) ou identificador de snapshot (iOS).

De acordo com o Google I/O 2025, o Shared Element Transition está entre os três padrões de UX mais eficazes para aplicativos móveis: os usuários têm 22% menos probabilidade de voltar imediatamente após uma transição se viram a animação do elemento compartilhado. No entanto, o uso excessivo (mais de 3 elementos compartilhados por transição) reduz o efeito e pode fazer o FPS cair abaixo de 40 em dispositivos de gama média.

Shared Element Transition no Android (Kotlin)

No Android, o Shared Element Transition é implementado via FragmentTransaction.addSharedElement(view, transitionName) ou ActivityOptions.makeSceneTransitionAnimation. Cada View recebe um android:transitionName único — o mesmo em ambas as telas. O sistema cria automaticamente uma animação de movimento (ChangeBounds), uma animação de mudança de tamanho (ChangeTransform) e uma animação de escalonamento de imagem (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()

// Personalização de transição: ChangeBounds + ChangeImageTransform
detailFragment.sharedElementEnterTransition = TransitionSet().apply {
    ordering = TransitionSet.ORDERING_TOGETHER
    addTransition(ChangeBounds())
    addTransition(ChangeTransform())
    addTransition(ChangeImageTransform())
    duration = 400L
    interpolator = FastOutSlowInInterpolator()
}

Detalhes importantes do Android: para a animação correta de ImageView com scaleType diferente (centerCrop → fitCenter), use ChangeImageTransform — ele anima automaticamente a mudança do modo de exibição. Para Views com cantos arredondados (CardView), adicione ChangeBounds, que anima o raio dos cantos via outline. Para animação de texto (mudança de tamanho da fonte), use ChangeTextTransform. Se um elemento compartilhado desaparece ou aparece em uma das telas, o sistema aplica automaticamente o Fade.

Shared Element Transition no iOS (Swift)

O iOS não tem um equivalente nativo ao sharedElementEnterTransition do Android — as animações compartilhadas são implementadas manualmente via UIViewControllerAnimatedTransitioning usando UIViewPropertyAnimator ou CASpringAnimation. A técnica principal: em animateTransition(using:), obtenha a View da tela de origem, tire um snapshot, adicione-o ao containerView, oculte o original e anime o snapshot para a posição de destino. No final, mostre a View de destino e remova o snapshot.

swift
import UIKit

"> Animador de Shared Element personalizado
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 do elemento compartilhado
        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 — biblioteca para simplificar transições compartilhadas
// heroID corresponde em ambas as telas, similar ao transitionName no Android
import Hero

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

UIViewPropertyAnimator é a maneira preferida para animações compartilhadas no iOS 10+. Ao contrário do UIView.animate, o PropertyAnimator suporta controle interativo (fractionComplete), permitindo integrar a transição compartilhada com o gesto de deslizar para trás. Para a biblioteca Hero (terceiros), a transição compartilhada é configurada via hero.id — um análogo do transitionName do Android, que anima automaticamente posição, tamanho, cornerRadius, zPosition e backgroundColor entre telas.

Shared Element no Jetpack Compose

No Jetpack Compose, o Shared Element Transition é implementado através do modificador graphicsLayer com a animação SharedTransitionScope (Compose 1.7+). Cada elemento compartilhado é marcado com sharedElement() usando uma chave única. O sistema anima automaticamente as mudanças de posição, tamanho, forma, cor e máscara de recorte ao navegar entre funções composable. De acordo com o Android Developers Blog (2026), o Compose Shared Element Transition suporta AnimatedContent, NavHost e contêineres personalizados.

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 — contêiner para elementos compartilhados
SharedTransitionLayout {
    val sharedTransitionState = remember { SharedTransitionState() }

    // Tela 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)
            }
        }
    }

    // Tela B: detalhes (mostrada quando um elemento é selecionado)
    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 — uma nova API disponível em modo experimental. Ela substitui o gerenciamento manual de animações compartilhadas no NavHost. Principais recursos: boundsTransform para curva de animação personalizada, clipToBounds para cantos arredondados, zIndex para ordem de empilhamento. Para animar imagens com contentScale diferente, use Modifier.sharedElement com o wrapper SharedContentKey. Para versões antigas do Compose (< 1.7), use a biblioteca SharedTransitionScope do Google.

Otimização e problemas comuns

Shared Element Transition pode causar problemas de desempenho se as limitações da plataforma não forem consideradas. No Android, o principal problema é o ChangeImageTransform com imagens grandes (acima de 2048 px). No iOS — snapshot com camadas pesadas (CAGradientLayer, CAShapeLayer com muitos nós). Solução: use imagens compactadas para animação compartilhada e tire o snapshot com afterScreenUpdates: false.

ProblemaCausaSolução
Animação com travamentosLayout diferente antes/depois da transição / snapshot pesadoUse Placeholder: mostre uma prévia pequena enquanto carrega
Elemento compartilhado não animatransitionName não corresponde em ambas as telasVerifique a correspondência exata do transitionName no XML/código
FPS < 40 no AndroidElemento compartilhado contém hierarquia de View complexaCompartilhe apenas ImageView, não a CardView inteira
iOS: artefatos no snapshotrenderInContext em camada com máscaras/animaçõesUse drawHierarchy(in:afterScreenUpdates: true)
Compose: elemento não apareceSharedTransitionLayout não envolve ambas as telasSharedTransitionLayout deve ser o pai de ambas
kotlin
// Otimização: Placeholder para imagem em transição compartilhada
import androidx.core.widget.ImageViewCompat
import com.bumptech.glide.Glide

"> Carregamento do preview antes da navegação
Glide.with(context)
    .load(imageUrl)
    .override(300, 300) // Limite de tamanho para transição compartilhada
    .placeholder(R.drawable.placeholder)
    .into(imageView)

> Carregamento tardio de full-resolution após a transição
val detailImage = findViewById<ImageView>(R.id.detailImage)
detailImage.doOnPreDraw {
    Glide.with(context).load(imageUrl).into(detailImage)
}

// iOS: otimização de snapshot com drawHierarchy
// snapshotView(afterScreenUpdates: false) — mais rápido, ignora atualizações pendentes
// Para Views complexas: drawHierarchy(in: view.bounds, afterScreenUpdates: true)

Melhores práticas: não adicione mais de 3 elementos compartilhados por transição — cada elemento adicional aumenta a carga de renderização. Para listas (RecyclerView/UICollectionView), torne o elemento compartilhado no item clicado, não na lista inteira. Para cartões com cantos arredondados, use clipToOutline no Android ou cornerRadius no snapshot no iOS. Se o elemento compartilhado na segunda tela estiver dentro de um ScrollView, aguarde a conclusão do layout (view.doOnPreDraw) antes de iniciar a animação.

Perguntas frequentes

Quantos elementos compartilhados podem ser animados em uma transição?

Recomenda-se não usar mais de 3 elementos compartilhados por transição. Cada elemento adicional aumenta a carga de renderização e pode causar quedas de FPS. Ideal: 1 imagem + 1 texto (título). No Android, cada elemento é adicionado via addSharedElement(view, transitionName). No iOS, cada snapshot é adicionado ao containerView separadamente.

Shared Element Transition não funciona com RecyclerView — o que fazer?

Certifique-se de que transitionName seja definido dinamicamente no onBindViewHolder: holder.imageView.transitionName = "image_${position}". Para FragmentTransaction.addSharedElement, passe a View específica do holder. Para ActivityOptions, use Pair.create(holder.imageView, holder.imageView.transitionName). Atrase a transição compartilhada até a conclusão do layout via postponeEnterTransition() e startPostponedEnterTransition().

Como fazer Shared Element Transition com mudança de cor de fundo?

No Android: adicione ChangeBackground ao TransitionSet ou use uma Transition personalizada substituindo captureStartValues e captureEndValues. No iOS: anime o backgroundColor do snapshot ou use UIViewPropertyAnimator com mudança de cor. No Jetpack Compose: Modifier.sharedElement anima automaticamente o fundo via transição de Color. Para cartões, anime também a elevação — de 2dp na lista para 8dp na tela de detalhes.

O Shared Element funciona no Android com Jetpack Navigation?

Sim, com o Navigation Component, o Shared Element é suportado via NavOptions: val navOptions = NavOptions.Builder().setEnterAnim(R.transition.move).applyNavOptions(fragment). Use FragmentNavigatorExtras para passar elementos compartilhados: FragmentNavigatorExtras(view to transitionName). Para Navigation Compose, use Modifier.sharedElement do SharedTransitionScope (Compose 1.7+).

Como depurar o Shared Element Transition?

No Android, ative o Transition Debugging: adb shell setprop debug.transition 1 — no logcat aparecerão mensagens sobre a captura e animação de elementos compartilhados. Ative "Show layout bounds" nas Developer Options para verificar os limites antes/depois. No iOS, use o Debug View Hierarchy para verificar o snapshot no containerView. Para FPS, use o Profile GPU Rendering (Android) ou o instrumento Core Animation (iOS).

Resumo

  • Shared Element Transition — animação de um elemento compartilhado entre telas preservando o contexto visual.
  • Android: FragmentTransaction.addSharedElement ou ActivityOptions.makeSceneTransitionAnimation.
  • iOS: implementação personalizada via UIViewControllerAnimatedTransitioning + snapshot + PropertyAnimator.
  • Jetpack Compose: Modifier.sharedElement do SharedTransitionScope (Compose 1.7+).
  • Máximo de 3 elementos compartilhados por transição para evitar quedas de FPS.
  • Para ImageView com scaleType diferente, use ChangeImageTransform (Android) ou snapshot (iOS).
  • Biblioteca Hero (iOS) — solução de terceiros com hero.id, similar ao transitionName no Android.

Vamos desenvolver um aplicativo móvel chave na mão

A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.

Discutir o projeto

Leia também