image

27 Dec 2025

9K

35K

Hero Animations with Multiple Widgets in Flutter

Flutter's Hero widgets provide a powerful way to create visually stunning shared element transitions between routes. When a user navigates from one screen to another, a Hero animation can smoothly animate a widget from its position and size on the source screen to its new position and size on the destination screen, creating a sense of visual continuity. While basic Hero animations are straightforward for single widgets, the challenge often arises when you need to animate a complex UI component comprising multiple widgets.

Introduction to Hero Animations

At its core, a Hero animation is about making a widget appear to "fly" from one screen to another. This is achieved by wrapping the same widget on both the source and destination screens with a Hero widget and assigning them identical tag properties. Flutter then takes care of the interpolation, scaling, and positioning during the route transition.

The Hero widget identifies a widget that should fly between routes. It takes two main properties:

  • tag: A unique identifier for the Hero. The Hero on the source route and the Hero on the destination route must have the same tag.
  • child: The widget that will be animated.

The Basic Hero Animation

Let's start with a simple example of a single image animating between two screens.


import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Basic Hero Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: SourceScreen(),
    );
  }
}

class SourceScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Source Screen')),
      body: Center(
        child: GestureDetector(
          onTap: () {
            Navigator.push(
              context,
              MaterialPageRoute(builder: (_) => DestinationScreen()),
            );
          },
          child: Hero(
            tag: 'my-image-hero',
            child: Image.network(
              'https://picsum.photos/id/100/100/75',
              width: 100,
              height: 75,
              fit: BoxFit.cover,
            ),
          ),
        ),
      ),
    );
  }
}

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

In this example, when you tap the small image on SourceScreen, it seamlessly grows and moves to the center of DestinationScreen, demonstrating a basic Hero animation.

The Challenge: Animating Multiple Widgets

The challenge arises when your "shared element" is not a single widget but a composition of several widgets, like a product card containing an image, a title, a price, and a description. You might want this entire card to animate, or perhaps specific elements within it (like the image and title) to animate independently to different positions on the next screen.

Directly wrapping a Column or Row of distinct visual elements in a single Hero tag will make the entire group animate as one cohesive unit. While this is often desired, it limits the flexibility of animating individual sub-components to different final states or positions.

Approach 1: Grouping Widgets as a Single Hero

The simplest method to animate multiple widgets is to wrap their common parent widget (e.g., a Card, Container, Column, or Row) within a single Hero widget. This makes the entire group of widgets animate as a single visual entity. Flutter will interpolate the size, position, and any other properties of the entire parent widget.

Pros:

  • Easy to implement.
  • Maintains the layout and relative positioning of child widgets during the transition.

Cons:

  • Lacks individual control over how child widgets animate. The group scales and moves uniformly.
  • If the internal structure or arrangement of widgets changes drastically between screens, the animation might look less natural as the entire block interpolates.

Example: Animating a Card with Image and Text

Here, a Card containing an Image, Text for title, and another Text for description is wrapped in a single Hero.


// main.dart (assuming MyApp structure as above)

class SourceScreenGrouped extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Source Screen (Grouped Hero)')),
      body: Center(
        child: GestureDetector(
          onTap: () {
            Navigator.push(
              context,
              MaterialPageRoute(builder: (_) => DestinationScreenGrouped()),
            );
          },
          child: Hero(
            tag: 'my-combined-card-hero',
            child: Card(
              elevation: 5,
              child: Padding(
                padding: const EdgeInsets.all(8.0),
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    Image.network(
                      'https://picsum.photos/id/237/200/150',
                      width: 200,
                      height: 150,
                      fit: BoxFit.cover,
                    ),
                    SizedBox(height: 8),
                    Text(
                      'Cute Doggo',
                      style: Theme.of(context).textTheme.headline6,
                    ),
                    Text(
                      'A very cute puppy image!',
                      style: Theme.of(context).textTheme.subtitle1,
                    ),
                  ],
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

class DestinationScreenGrouped extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Destination Screen (Grouped Hero)')),
      body: Center(
        child: Hero(
          tag: 'my-combined-card-hero',
          child: Card( // The destination widget must also be a Card with similar structure
            elevation: 5,
            child: Padding(
              padding: const EdgeInsets.all(16.0), // Padding can be different, Hero interpolates
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  Image.network(
                    'https://picsum.photos/id/237/400/300', // Different size for interpolation
                    width: 400,
                    height: 300,
                    fit: BoxFit.cover,
                  ),
                  SizedBox(height: 16), // Different spacing
                  Text(
                    'Cute Doggo (Expanded View)',
                    style: Theme.of(context).textTheme.headline4, // Different style
                  ),
                  Text(
                    'This is an expanded view of the very cute puppy image! Enjoy its cuteness and details.',
                    style: Theme.of(context).textTheme.bodyText1,
                    textAlign: TextAlign.center,
                  ),
                ],
              ),
            ),
          ),
        ),
      ),
    );
  }
}

When tapping the card on the source screen, the entire card (image and text together) will animate and expand to the center of the destination screen. The internal padding, spacing, and text styles will smoothly interpolate between the two states.

Approach 2: Compound Hero Animations with Multiple Hero Widgets

For greater control, or when different parts of your UI need to transition to distinct final states or positions on the destination screen, you can employ multiple Hero widgets. Each distinct part of your UI (e.g., an image, a title text, a rating star) will be wrapped in its own Hero widget with a unique tag.

On the destination screen, you will then have corresponding Hero widgets with matching tags. These can be arranged using `Column`, `Row`, `Stack`, or any other layout widget to achieve the desired final composition, allowing them to animate to independent positions.

Pros:

  • Granular control over each animating element.
  • Allows for complex and visually rich transitions where elements seem to "disassemble" from the source and "reassemble" at different positions/styles on the destination.
  • Ideal when the destination layout for sub-elements is significantly different from the source layout.

Cons:

  • More complex to implement.
  • Requires careful tag management to ensure each source Hero has a unique matching destination Hero.
  • Layout on the destination screen needs to be precisely managed to ensure elements land correctly after their independent animations.

Example: Animating Image and Text Separately

In this scenario, the image and the text will have their own Hero tags and will animate independently. Notice how the destination screen places them differently.


// main.dart (assuming MyApp structure as above)

class SourceScreenMultiple extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Source Screen (Multiple Hero)')),
      body: Center(
        child: GestureDetector(
          onTap: () {
            Navigator.push(
              context,
              MaterialPageRoute(builder: (_) => DestinationScreenMultiple()),
            );
          },
          child: Column( // A simple Column for the source
            mainAxisSize: MainAxisSize.min,
            children: [
              Hero(
                tag: 'hero_image_tag',
                child: Image.network(
                  'https://picsum.photos/id/101/100/75', // Different image ID for uniqueness
                  width: 100,
                  height: 75,
                  fit: BoxFit.cover,
                ),
              ),
              SizedBox(height: 8),
              Hero(
                tag: 'hero_title_tag',
                child: Text(
                  'Forest Path Title',
                  style: Theme.of(context).textTheme.subtitle1,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class DestinationScreenMultiple extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Destination Screen (Multiple Hero)')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column( // Arranging elements differently on destination
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Hero(
              tag: 'hero_image_tag',
              child: Image.network(
                'https://picsum.photos/id/101/400/300',
                width: 400,
                height: 300,
                fit: BoxFit.cover,
              ),
            ),
            SizedBox(height: 16),
            Hero(
              tag: 'hero_title_tag',
              child: Text(
                'Grand Forest Path (Expanded View)',
                style: Theme.of(context).textTheme.headline4,
              ),
            ),
            SizedBox(height: 8),
            Text(
              'A beautiful description of the scenic forest path, inviting viewers to explore its serene beauty and tranquility.',
              style: Theme.of(context).textTheme.bodyText1,
            ),
          ],
        ),
      ),
    );
  }
}

In this example, when the user taps the combined `Column` on SourceScreenMultiple, the image will fly to its larger position at the top of DestinationScreenMultiple, and the title text will simultaneously fly to its new, larger position below the image. They animate as distinct entities, providing a more dynamic and intricate transition.

Conclusion

Flutter's Hero animations are a powerful tool for creating engaging user experiences. Whether you need to animate a single widget, a group of widgets as one unit, or individual components of a complex UI to distinct destinations, the Hero widget provides the flexibility. By understanding the two main approaches—grouping widgets under a single Hero tag or using multiple Hero widgets for compound animations—you can craft sophisticated and visually appealing transitions in your Flutter applications.

Related Articles

May 14, 2026

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter Countdown timers are essential in many applic

May 11, 2026

Unleashing Dynamic UIs: Flutter's Animation Prowess

Unleashing Dynamic UIs: Flutter's Animation Prowess for Slide & Scale Effects Flutter's declarative UI framework, combined with its powerful animation capabilit

May 11, 2026

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy A well-designed Product Detail Page (PDP) is