flutter
/

Flutter Hero Animation – Smooth Shared Element Transitions

Last Sync: Today

On this page

12
0%
5 min read
Remaining
5 minleft

Click any section to jump — progress syncs automatically

flutter

Flutter Hero Animation – Smooth Shared Element Transitions

What is a Hero Animation?

A Hero animation is a shared element transition where a widget appears to 'fly' from one screen to another. It's often used for images, avatars, or any prominent element that the user taps to see more details. Flutter's built‑in Hero widget makes it easy to create these smooth, delightful transitions with minimal code.

How Hero Works

The Hero widget requires a unique tag (usually a string) that identifies the widget. When a route push/pop occurs, Flutter looks for a Hero with the same tag on both the source and destination screens. It then creates an overlay that animates the widget from the source position and size to the destination position and size, while fading the original widgets in/out.

Basic Hero Animation

To create a hero transition, wrap the widget you want to animate with a Hero widget on both screens, using the same tag. Usually, the source screen's hero is inside an InkWell or GestureDetector to handle tap navigation.

DARTRead-only
1
// Source screen
Hero(
  tag: 'avatar',
  child: CircleAvatar(
    radius: 40,
    backgroundImage: NetworkImage('https://example.com/avatar.jpg'),
  ),
)

// Destination screen
Hero(
  tag: 'avatar',
  child: CircleAvatar(
    radius: 100,
    backgroundImage: NetworkImage('https://example.com/avatar.jpg'),
  ),
)

When you navigate from the source screen to the destination screen, the avatar will smoothly animate from the small size to the large size, creating a seamless effect.

Hero Tags

The tag can be any object, but it's typically a string. For lists, you might use a unique identifier like an index or an item's ID. If you use the same tag on multiple heroes in the same route, Flutter will throw an error. Keep tags unique per route.

DARTRead-only
1
// In a list, use the item's unique ID as tag
ListView.builder(
  itemCount: items.length,
  itemBuilder: (context, index) {
    final item = items[index];
    return GestureDetector(
      onTap: () => Navigator.push(context, MaterialPageRoute(
        builder: (_) => DetailPage(item: item),
      )),
      child: Hero(
        tag: 'image_${item.id}',
        child: Image.network(item.imageUrl),
      ),
    );
  },
)

Customizing the Hero Transition

You can customize the transition using the flightShuttleBuilder, placeholderBuilder, and createRectTween properties.

Flight Shuttle Builder

flightShuttleBuilder lets you replace the flying widget with a custom widget during the transition. It gives you access to the context, the hero's size and position, and the animation progress.

DARTRead-only
1
Hero(
  tag: 'image',
  flightShuttleBuilder: (flightContext, animation, flightDirection, fromHeroContext, toHeroContext) {
    final fromWidget = fromHeroContext.widget;
    final toWidget = toHeroContext.widget;
    return AnimatedBuilder(
      animation: animation,
      builder: (context, child) {
        return Transform.scale(
          scale: 0.5 + animation.value * 0.5,
          child: child,
        );
      },
      child: fromWidget,
    );
  },
  child: Image.network('...'),
)

Placeholder Builder

placeholderBuilder allows you to show a placeholder while the hero is flying. This is useful when you want to keep the original widget visible (or fade it) during the transition.

DARTRead-only
1
Hero(
  tag: 'image',
  placeholderBuilder: (context, size, child) => Opacity(
    opacity: 0.5,
    child: child,
  ),
  child: Image.network('...'),
)

Custom Rect Tween

createRectTween lets you control how the hero's bounds animate (e.g., using a curved motion). You can supply your own RectTween subclass.

DARTRead-only
1
Hero(
  tag: 'image',
  createRectTween: (begin, end) {
    return MaterialRectCenterArcTween(begin: begin, end: end); // arc motion
  },
  child: Image.network('...'),
)

Heroes with Different Widget Types

Heroes can animate between different widget types (e.g., a CircleAvatar to a ClipRRect image). As long as the Hero widget has the same tag, Flutter will interpolate the size, position, and other visual properties. You may need to provide a flightShuttleBuilder if the default transition doesn't look good.

Common Mistakes

    • Duplicate tags in the same route: Each Hero tag must be unique per route.
    • Forgetting to wrap both source and destination: Both screens must have a Hero with the same tag.
    • Using a non‑unique tag in lists: Use unique identifiers (e.g., item ID) to avoid collisions.
    • Not disposing controllers: If you create custom animations, remember to dispose them.
    • Over‑complicating with custom builders: Start simple; only add customisation if needed.

Best Practices

    • Use unique tags: For lists, combine a prefix with the item's ID (e.g., 'image_${item.id}').
    • Test with different screen sizes: The animation should look good on all devices.
    • Keep the hero widget focused: The hero should be the only widget animating; use child for the content.
    • Use flightShuttleBuilder sparingly: Only when you need special effects; the default transition is often sufficient.
    • Consider performance: Heroes are efficient, but avoid heavy widgets inside the hero if possible.

Key Takeaways

    • Hero animations create seamless shared element transitions.
    • Use the same tag on both the source and destination Hero widgets.
    • Tags must be unique per route.
    • Customize transitions with flightShuttleBuilder, placeholderBuilder, and createRectTween.
    • Heroes work between different widget types (e.g., CircleAvatar to Image).
    • Always test with real data to ensure smooth animations.

Try it yourself

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Hero Demo',
      home: const HomeScreen(),
    );
  }
}

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Hero Example')),
      body: Center(
        child: GestureDetector(
          onTap: () {
            Navigator.push(
              context,
              MaterialPageRoute(builder: (_) => const DetailScreen()),
            );
          },
          child: Hero(
            tag: 'hero',
            child: Container(
              width: 100,
              height: 100,
              color: Colors.blue,
              child: const Icon(
                Icons.star,
                size: 50,
                color: Colors.white,
              ),
            ),
          ),
        ),
      ),
    );
  }
}

class DetailScreen extends StatelessWidget {
  const DetailScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Detail')),
      body: Center(
        child: Hero(
          tag: 'hero',
          child: Container(
            width: 200,
            height: 200,
            color: Colors.blue,
            child: const Icon(
              Icons.star,
              size: 100,
              color: Colors.white,
            ),
          ),
        ),
      ),
    );
  }
}

Test Your Knowledge

Q1
of 4

What is the purpose of the `tag` property in a Hero widget?

A
To identify the hero for animations
B
To label the hero in the widget tree
C
To set the hero's priority
D
To group heroes together
Q2
of 4

What happens if two Heroes on the same screen have the same tag?

A
They both animate
B
The first one is ignored
C
Flutter throws an assertion error
D
They share the same animation
Q3
of 4

Which property allows you to replace the flying widget during the transition?

A
flightShuttleBuilder
B
placeholderBuilder
C
createRectTween
D
child
Q4
of 4

Can Hero animate between a `CircleAvatar` and a `ClipRRect`?

A
Yes
B
No
C
Only if both are images
D
Only with a custom flightShuttleBuilder

Frequently Asked Questions

Can I use Hero with different widget types?

Yes, as long as the widget is a Hero with the same tag on both screens, Flutter will animate the size and position. For complex widgets, you may need a custom flightShuttleBuilder to ensure a smooth visual transition.

What happens if two Heroes have the same tag on the same screen?

Flutter will throw an assertion error because the tag must be unique per route. Use a unique identifier, especially in lists.

How do I animate a hero that is part of a list?

Generate a unique tag based on the item's identifier. For example: Hero(tag: 'item_${item.id}', child: ...). When navigating, pass that identifier to the detail screen and use the same tag there.

Can I combine Hero with HeroControllerScope?

You don't normally need to. HeroControllerScope is used when you have custom navigators. For standard MaterialPageRoute, the default hero controller works fine.

How do I disable hero animation for a specific navigation?

You can wrap the hero in a Hero with enabled: false, or use a Navigator without the default HeroController (not recommended). Usually, you keep the hero for smooth transitions.

Previous

flutter animations

Next

flutter custom widget

Related Content

Need help?

Explore our comprehensive docs or start a chat with our tech experts.