Hero Animation: qué es una transición de elementos compartidos en Flutter

Autor: IT Sectr Publicado: 2026-03-02 Tiempo de lectura: 8 min
Hero Animation es un patrón de animación en el que un elemento de la interfaz se mueve y transforma suavemente entre pantallas, creando el efecto de un “héroe” que viaja de un contexto a otro. A diferencia de una transición push/pop estándar, la animación hero preserva la continuidad visual: el usuario ve una miniatura convertirse en una imagen a pantalla completa o un título de celda moverse al título de la pantalla. Según Google Material Design (2025), Hero Animation reduce la carga cognitiva en un 30% al navegar entre una lista y los detalles — el usuario no pierde el enfoque gracias a recordar la posición del elemento. En Flutter el patrón se implementa mediante el widget Hero, en SwiftUI — mediante el modificador matchedGeometryEffect, en Jetpack Compose — mediante Modifier.sharedElement.

Puntos clave

  • Hero Animation — una transición suave de un elemento compartido entre pantallas que preserva el contexto visual para el usuario.
  • Flutter Hero — un widget que encuentra automáticamente un par por etiqueta y lanza una animación FlightShuttle entre pantallas.
  • matchedGeometryEffect — un modificador de SwiftUI que vincula elementos mediante un identificador en un namespace para transiciones animadas.
  • Shared Element Transition — un mecanismo de Android implementado mediante ActivityOptions o Modifier.sharedElement en Jetpack Compose.
  • FlightShuttle — el mecanismo interno de Flutter que controla la trayectoria y transformación del widget Hero durante la transición.

¿Qué es Hero Animation?

Hero Animation es una técnica de animación de interfaz en la que un elemento compartido se mueve suavemente desde una pantalla de origen a una pantalla de destino, preservando su identidad. El nombre refleja la metáfora de un “héroe” — un elemento clave que atraviesa una transición guionizada entre pantallas, atrayendo la atención del usuario. A diferencia de las transiciones estándar, Hero Animation no rompe la conexión visual: el usuario ve una trayectoria continua del elemento, lo que mejora la comprensión de la jerarquía de navegación.

El concepto de Hero Animation está establecido en Material Design como recomendación para pantallas de lista-detalle, galerías y tarjetas. El algoritmo de animación consta de tres fases: en la pantalla de origen, el elemento se resalta y se fija en una posición absoluta; durante la transición, se escala, se mueve y cambia de forma según el contenedor de destino; en la pantalla final, el elemento se integra en el nuevo diseño. Según Material Design 3, una animación hero correctamente implementada reduce en un 22% el número de pulsaciones erróneas del botón “atrás”.

Hero en Flutter: etiquetas y FlightShuttle

En Flutter, Hero Animation se implementa mediante el widget Hero, que recibe un parámetro obligatorio tag — un identificador único. Flutter encuentra automáticamente un par de widgets Hero con la misma etiqueta en las rutas de origen y destino e inicia la animación. El widget Hero no requiere gestión manual de la animación — el framework calcula independientemente la trayectoria y aplica la transformación a través del objeto interno FlightShuttle.

FlightShuttle es un “transbordador” invisible que, en el momento de la transición, crea una copia superpuesta del widget Hero, la posiciona en un sistema de coordenadas absoluto y anima la transformación (escala, traslación, rotación) desde la posición inicial hasta la final. Se pueden pasar curvas personalizadas a FlightShuttle mediante la propiedad flightShuttleBuilder, lo que permite controlar la trayectoria del vuelo: volar en arco, rebotar en los bordes o disminuir la velocidad antes de llegar. En Flutter 3.16+, Hero admite animación preservando clipRect y borderRadius, lo que proporciona el efecto de tarjetas redondeadas que se transforman en pantallas rectangulares.

matchedGeometryEffect en SwiftUI

En SwiftUI, el equivalente de Hero Animation es el modificador matchedGeometryEffect, introducido en iOS 14. Vincula dos elementos mediante un identificador (id) y un espacio de nombres (namespace). A diferencia de Flutter, donde Hero intercepta automáticamente la transición, SwiftUI requiere una declaración explícita de namespace y la aplicación del modificador a ambas partes de la escena — origen y destino — dentro de la misma jerarquía o mediante GeometryReader.

La animación de matchedGeometryEffect admite la transformación de posición, tamaño, rotación y radio de esquina. Para lograr suavidad, se especifica una Animation en withAnimation. SwiftUI interpola automáticamente los estados intermedios basándose en la curva predeterminada easeInOut. En iOS 17+, se agregó soporte para matchedTransitionSource en TabView y NavigationStack — un análogo de sistema de Hero Animation con una trayectoria predefinida para transiciones estándar.

Shared Element Transition en Android

En Android, Hero Animation se llama Shared Element Transition y se implementa mediante ActivityOptions.makeSceneTransitionAnimation o FragmentTransaction.addSharedElement. Cada elemento compartido se marca con un atributo transitionName en XML o mediante código, y Android maneja la animación entre actividades. A partir de Android 5.0 (API 21), las transiciones pasaron a formar parte del framework; antes de eso, había que escribir animaciones personalizadas mediante ValueAnimator.

En Jetpack Compose, Shared Element Transition se implementa mediante los modificadores Modifier.sharedElement y Modifier.sharedBounds junto con AnimatedContent o SharedTransitionLayout (disponible desde Compose BOM 2024.01). A diferencia del sistema View, Compose redibuja los elementos de manera perezosa, por lo que el elemento compartido se almacena en caché en CompositionLocal. Compose también admite la animación de lista a detalle con preservación de scrollOffset — una capacidad clave para las transiciones Hero. Según Google I/O 2024, Compose Shared Element Transition es un 15% más rápido que la versión View debido a la gestión directa de RenderNode.

Ejemplos de código

Flutter: Hero con imagen

Un ejemplo básico de Hero Animation en Flutter: una miniatura en la pantalla de lista y una imagen a pantalla completa en la pantalla de detalles. La etiqueta image-hero vincula los dos widgets, y FlightShuttle anima automáticamente la transición de la tarjeta a pantalla completa.

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,
          ),
        ),
      ),
    );
  }
}

El punto clave es la misma etiqueta (image-hero) en ambas pantallas. Flutter empareja los widgets Hero por etiqueta durante la transición de ruta. Si las etiquetas difieren, la animación no se iniciará — el elemento simplemente aparecerá en la nueva pantalla sin transición. Se puede usar cualquier tipo para la etiqueta, pero las cadenas son la opción más fiable.

SwiftUI: matchedGeometryEffect entre pantallas

Un ejemplo de Hero Animation en SwiftUI con NavigationStack. El modificador matchedGeometryEffect vincula la imagen en la lista y la pantalla de detalles mediante un namespace, y withAnimation desencadena una transición 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 crea un espacio de nombres para el identificador de matchedGeometryEffect. Ambos elementos usan el mismo id (“hero”) y un namespace compartido. Al alternar isDetailShown, SwiftUI anima la transformación de tamaño, posición y radio de esquina entre las dos vistas, creando el efecto hero. Para un NavigationLink estándar con iOS 16+, use navigationTransition(.hero).

Android: Shared Element Transition con Jetpack Compose

Un ejemplo de Shared Element Transition en Jetpack Compose con SharedTransitionLayout. El modificador sharedElement anima un elemento compartido entre pantallas durante la navegación.

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+) envuelve la jerarquía, y el modificador sharedElement con la clave “hero” vincula los elementos inicial y final. AnimatedContent alterna el estado, y Compose anima automáticamente el tamaño, la posición y la forma de la imagen. Una ventaja clave es el soporte para la animación de bounds y clipBounds, lo que proporciona el efecto de redondeo de tarjeta durante la transición.

Preguntas frecuentes

¿Hero Animation funciona en iOS y Android?

Sí, Hero Animation está disponible en ambas plataformas. iOS usa matchedGeometryEffect en SwiftUI o UINavigationController.transitioningDelegate. Android usa ActivityOptions.makeSceneTransitionAnimation o Shared Element Transition en Jetpack Compose con Modifier.sharedElement. Flutter implementa Hero Animation mediante el widget Hero, que funciona de forma idéntica en ambas plataformas.

¿Se pueden animar varios elementos simultáneamente?

Sí, Hero Animation admite múltiples elementos compartidos. En Flutter, especifique varios widgets Hero con diferentes etiquetas. En SwiftUI, use matchedGeometryEffect con diferentes identificadores en cada elemento. Todas las animaciones se ejecutan en paralelo — cada elemento viaja por su propia trayectoria.

¿Hero Animation se ralentiza con imágenes grandes?

El rendimiento depende de la implementación. FlightShuttle de Flutter procesa imágenes a través de RenderObject, logrando 60fps. matchedGeometryEffect de SwiftUI está vectorizado. Para imágenes grandes, use precacheImage en Flutter o prefetch en SwiftUI — esto elimina los retrasos en la primera transición. Android Compose requiere carga diferida mediante AsyncImage.

¿En qué se diferencia Hero de una transición push estándar?

Una transición push estándar desplaza toda la pantalla, creando la ilusión de una pila de tarjetas. Hero Animation fija la atención visual en un único elemento — el usuario ve su movimiento continuo, lo que mejora la comprensión de la relación entre las pantallas. Push es bueno para la navegación jerárquica, Hero para enfatizar el contenido.

¿Se necesitan bibliotecas adicionales para Hero Animation?

No, Hero Animation se implementa con herramientas integradas. En Flutter, es el widget Hero del paquete flutter/material.dart. En SwiftUI, es el modificador matchedGeometryEffect. En Android Jetpack Compose, es SharedTransitionLayout y Modifier.sharedElement de Compose estándar. Solo se requieren bibliotecas adicionales para trayectorias no estándar o transformaciones complejas.

Resumen

  • Hero Animation es una transición de elemento compartido entre pantallas que preserva la continuidad visual y reduce la carga cognitiva.
  • Flutter Hero con etiqueta y FlightShuttle es la forma más sencilla de implementar la animación hero sin gestión manual de la animación.
  • SwiftUI matchedGeometryEffect con @Namespace.id anima la posición, el tamaño y el radio de esquina de un elemento entre dos estados.
  • Android SharedTransitionLayout con Modifier.sharedElement en Jetpack Compose proporciona transiciones con almacenamiento en caché de RenderNode.
  • Los tres frameworks admiten múltiples elementos compartidos con animación paralela.
  • FlightShuttle permite personalizar la trayectoria de vuelo mediante flightShuttleBuilder en Flutter.
  • Hero Animation es recomendada por Material Design para pantallas de lista-detalle, galerías y tarjetas.

Desarrollaremos una aplicación móvil llave en mano

IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.

Discutir el proyecto

Lea también