Hero Animation: o que é uma transição de elementos compartilhados no Flutter

Autor: IT Sectr Publicado: 2026-03-02 Tempo de leitura: 8 min
Hero Animation é um padrão de animação onde um elemento de interface se move e transforma suavemente entre telas, criando o efeito de um “héroi” viajando de um contexto para outro. Ao contrário de uma transição push/pop padrão, a animação hero preserva a continuidade visual: o usuário vê uma miniatura se transformando em uma imagem em tela cheia ou um título de célula se movendo para o título da tela. De acordo com Google Material Design (2025), Hero Animation reduz a carga cognitiva em 30% ao navegar entre lista e detalhes — o usuário não perde o foco graças à lembrança da posição do elemento. No Flutter o padrão é implementado através do widget Hero, no SwiftUI — através do modificador matchedGeometryEffect, no Jetpack Compose — através do Modifier.sharedElement.

Principais pontos

  • Hero Animation — uma transição suave de elemento compartilhado entre telas que preserva o contexto visual para o usuário.
  • Flutter Hero — um widget que encontra automaticamente um par por tag e inicia uma animação FlightShuttle entre telas.
  • matchedGeometryEffect — um modificador SwiftUI que vincula elementos através de um identificador em namespace para transições animadas.
  • Shared Element Transition — um mecanismo Android implementado através de ActivityOptions ou Modifier.sharedElement no Jetpack Compose.
  • FlightShuttle — o mecanismo interno do Flutter que controla a trajetória e transformação do widget Hero durante a transição.

O que é Hero Animation?

Hero Animation é uma técnica de animação de interface onde um elemento compartilhado se move suavemente de uma tela de origem para uma tela de destino, preservando sua identidade. O nome reflete a metáfora de um “héroi” — um elemento-chave que passa por uma transição roteirizada entre telas, atraindo a atenção do usuário. Ao contrário das transições padrão, Hero Animation não quebra a conexão visual: o usuário vê uma trajetória contínua do elemento, o que melhora a compreensão da hierarquia de navegação.

O conceito de Hero Animation está estabelecido no Material Design como recomendação para telas de lista-detalhe, galerias e cartões. O algoritmo de animação consiste em três fases: na tela de origem, o elemento é destacado e fixado em uma posição absoluta; durante a transição, ele escala, move e muda de forma de acordo com o contêiner de destino; na tela final, o elemento é incorporado ao novo layout. De acordo com Material Design 3, uma animação hero implementada corretamente reduz o número de pressionamentos errôneos do botão “voltar” em 22%.

Hero no Flutter: tags e FlightShuttle

No Flutter, Hero Animation é implementada através do widget Hero, que recebe um parâmetro obrigatório tag — um identificador único. O Flutter encontra automaticamente um par de widgets Hero com a mesma tag nas rotas de origem e destino e inicia a animação. O widget Hero não requer gerenciamento manual de animação — o framework calcula independentemente a trajetória e aplica a transformação através do objeto interno FlightShuttle.

FlightShuttle é um “transporte” invisível que, no momento da transição, cria uma cópia sobreposta do widget Hero, a posiciona em um sistema de coordenadas absoluto e anima a transformação (escala, translação, rotação) da posição inicial para a final. Curvas personalizadas podem ser passadas para FlightShuttle através da propriedade flightShuttleBuilder, permitindo controlar a trajetória do voo: voar em arco, quicar nas bordas ou desacelerar antes da chegada. No Flutter 3.16+, Hero suporta animação preservando clipRect e borderRadius, proporcionando o efeito de cartões arredondados se transformando em telas retangulares.

matchedGeometryEffect no SwiftUI

No SwiftUI, o equivalente de Hero Animation é o modificador matchedGeometryEffect, introduzido no iOS 14. Ele vincula dois elementos através de um identificador (id) e um namespace. Ao contrário do Flutter, onde Hero intercepta automaticamente a transição, SwiftUI requer declaração explícita de namespace e aplicação do modificador a ambas as partes da cena — origem e destino — dentro da mesma hierarquia ou através de GeometryReader.

A animação matchedGeometryEffect suporta transformação de posição, tamanho, rotação e raio de canto. Para suavidade, uma Animation é especificada em withAnimation. SwiftUI interpola automaticamente os estados intermediários com base na curva padrão easeInOut. No iOS 17+, foi adicionado suporte para matchedTransitionSource para TabView e NavigationStack — um análogo de sistema de Hero Animation com trajetória predefinida para transições padrão.

Shared Element Transition no Android

No Android, Hero Animation é chamada de Shared Element Transition e é implementada através de ActivityOptions.makeSceneTransitionAnimation ou FragmentTransaction.addSharedElement. Cada elemento compartilhado é marcado com um atributo transitionName em XML ou através de código, e o Android gerencia a animação entre atividades. A partir do Android 5.0 (API 21), as transições se tornaram parte do framework; antes disso, animações personalizadas tinham que ser escritas usando ValueAnimator.

No Jetpack Compose, Shared Element Transition é implementada através dos modificadores Modifier.sharedElement e Modifier.sharedBounds em conjunto com AnimatedContent ou SharedTransitionLayout (disponível desde Compose BOM 2024.01). Ao contrário do sistema View, o Compose redesenha os elementos preguiçosamente, então o elemento compartilhado é armazenado em cache no CompositionLocal. O Compose também suporta animação de lista para detalhe com preservação de scrollOffset — uma capacidade chave para transições Hero. De acordo com Google I/O 2024, Compose Shared Element Transition é 15% mais rápido que a versão View devido ao gerenciamento direto de RenderNode.

Exemplos de código

Flutter: Hero com imagem

Um exemplo básico de Hero Animation no Flutter: uma miniatura na tela de lista e uma imagem em tela cheia na tela de detalhes. A tag image-hero vincula os dois widgets, e FlightShuttle anima automaticamente a transição do cartão para tela cheia.

dart
import 'package:flutter/material.dart';

class ListScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ListView(
        children: [
          Hero(
            tag: 'image-hero',
            child: GestureDetector(
              onTap: () {
                Navigator.push(context,
                  MaterialPageRoute(
                    builder: (_) => DetailScreen(),
                  ),
                );
              },
              child: ClipRRect(
                borderRadius: BorderRadius.circular(12),
                child: Image.network(
                  'https://picsum.photos/200',
                  width: 100, height: 100,
                  fit: BoxFit.cover,
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

class DetailScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Detail')),
      body: Center(
        child: Hero(
          tag: 'image-hero',
          child: Image.network(
            'https://picsum.photos/800',
            width: 300, height: 400,
            fit: BoxFit.cover,
          ),
        ),
      ),
    );
  }
}

O ponto chave é a mesma tag (image-hero) em ambas as telas. O Flutter corresponde os widgets Hero por tag durante a transição de rota. Se as tags diferirem, a animação não será iniciada — o elemento simplesmente aparecerá na nova tela sem transição. Qualquer tipo pode ser usado para a tag, mas strings são a opção mais confiável.

SwiftUI: matchedGeometryEffect entre telas

Um exemplo de Hero Animation no SwiftUI com NavigationStack. O modificador matchedGeometryEffect vincula a imagem na lista e a tela de detalhes através de um namespace, e withAnimation desencadeia uma transição suave.

swift
import SwiftUI

struct HeroExampleView: View {
    @Namespace private var heroNamespace
    @State private var isDetailShown = false

    var body: some View {
        VStack {
            if isDetailShown {
                DetailView(namespace: heroNamespace)
                    .transition(.identity)
            } else {
                ListView(namespace: heroNamespace)
                    .transition(.identity)
            }
        }
        .animation(Animation.easeInOut(duration: 0.4),
                   value: isDetailShown)
    }
}

struct ListView: View {
    let namespace: Namespace.ID

    var body: some View {
        Image("thumbnail")
            .resizable()
            .frame(width: 100, height: 100)
            .cornerRadius(12)
            .matchedGeometryEffect(id: "hero", in: namespace)
    }
}

struct DetailView: View {
    let namespace: Namespace.ID

    var body: some View {
        Image("thumbnail")
            .resizable()
            .frame(width: 300, height: 400)
            .cornerRadius(0)
            .matchedGeometryEffect(id: "hero", in: namespace)
    }
}

@Namespace cria um espaço de nomes para o identificador matchedGeometryEffect. Ambos os elementos usam o mesmo id (“hero”) e um namespace compartilhado. Quando isDetailShown alterna, SwiftUI anima a transformação de tamanho, posição e raio de canto entre as duas visualizações, criando o efeito hero. Para um NavigationLink padrão com iOS 16+, use navigationTransition(.hero).

Android: Shared Element Transition com Jetpack Compose

Um exemplo de Shared Element Transition no Jetpack Compose com SharedTransitionLayout. O modificador sharedElement anima um elemento compartilhado entre telas durante a navegação.

kotlin
@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
fun HeroExample() {
    SharedTransitionLayout {
        var isDetail by remember { mutableStateOf(false) }

        AnimatedContent(
            targetState = isDetail,
            transitionSpec = {
                fadeIn() togetherWith fadeOut()
            }
        ) { detail ->
            if (detail) {
                Box(
                    modifier = Modifier
                        .fillMaxSize()
                        .sharedElement(
                            state = rememberSharedContentState(key = "hero"),
                            animatedVisibilityScope = this
                        )
                ) {
                    AsyncImage(
                        model = "https://picsum.photos/800",
                        contentDescription = null,
                        modifier = Modifier.fillMaxSize()
                    )
                }
            } else {
                Box(
                    modifier = Modifier
                        .size(100.dp)
                        .sharedElement(
                            state = rememberSharedContentState(key = "hero"),
                            animatedVisibilityScope = this
                        )
                ) {
                    AsyncImage(
                        model = "https://picsum.photos/200",
                        contentDescription = null,
                        modifier = Modifier.fillMaxSize()
                    )
                }
            }
        }
    }
}

Compose SharedTransitionLayout (Compose BOM 2024.01+) envolve a hierarquia, e o modificador sharedElement com a chave “hero” vincula os elementos inicial e final. AnimatedContent alterna o estado, e o Compose anima automaticamente o tamanho, posição e forma da imagem. Uma vantagem chave é o suporte para animação de bounds e clipBounds, proporcionando o efeito de arredondamento de cartão durante a transição.

Perguntas frequentes

Hero Animation funciona no iOS e Android?

Sim, Hero Animation está disponível em ambas as plataformas. O iOS usa matchedGeometryEffect no SwiftUI ou UINavigationController.transitioningDelegate. O Android usa ActivityOptions.makeSceneTransitionAnimation ou Shared Element Transition no Jetpack Compose com Modifier.sharedElement. O Flutter implementa Hero Animation através do widget Hero, funcionando de forma idêntica em ambas as plataformas.

Pode-se animar vários elementos simultaneamente?

Sim, Hero Animation suporta múltiplos elementos compartilhados. No Flutter, especifique vários widgets Hero com tags diferentes. No SwiftUI, use matchedGeometryEffect com identificadores diferentes em cada elemento. Todas as animações são executadas em paralelo — cada elemento viaja em sua própria trajetória.

Hero Animation fica lenta com imagens grandes?

O desempenho depende da implementação. FlightShuttle do Flutter processa imagens através de RenderObject, alcançando 60fps. matchedGeometryEffect do SwiftUI é vetorizado. Para imagens grandes, use precacheImage no Flutter ou prefetch no SwiftUI — isso elimina atrasos na primeira transição. Android Compose requer carregamento diferido através de AsyncImage.

Como Hero difere de uma transição push padrão?

Uma transição push padrão desliza a tela inteira, criando a ilusão de uma pilha de cartões. Hero Animation fixa a atenção visual em um único elemento — o usuário vê seu movimento contínuo, melhorando a compreensão da relação entre as telas. Push é bom para navegação hierárquica, Hero para enfatizar o conteúdo.

São necessárias bibliotecas adicionais para Hero Animation?

Não, Hero Animation é implementada com ferramentas integradas. No Flutter, é o widget Hero do pacote flutter/material.dart. No SwiftUI, é o modificador matchedGeometryEffect. No Android Jetpack Compose, é SharedTransitionLayout e Modifier.sharedElement do Compose padrão. Bibliotecas adicionais são necessárias apenas para trajetórias não padrão ou transformações complexas.

Resumo

  • Hero Animation é uma transição de elemento compartilhado entre telas que preserva a continuidade visual e reduz a carga cognitiva.
  • Flutter Hero com tag e FlightShuttle é a maneira mais simples de implementar animação hero sem gerenciamento manual de animação.
  • SwiftUI matchedGeometryEffect com @Namespace.id anima posição, tamanho e raio de canto de um elemento entre dois estados.
  • Android SharedTransitionLayout com Modifier.sharedElement no Jetpack Compose fornece transições com cache de RenderNode.
  • Os três frameworks suportam múltiplos elementos compartilhados com animação paralela.
  • FlightShuttle permite personalizar a trajetória de voo através de flightShuttleBuilder no Flutter.
  • Hero Animation é recomendada pelo Material Design para telas de lista-detalhe, galerias e cartões.

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