image

23 Jan 2026

9K

35K

Flutter Custom Hero Animations with Curves

Flutter's Hero widget provides a beautiful "shared element" transition between routes, giving your app a polished, native feel. By default, Hero animations use a standard interpolation curve, which is often sufficient. However, for unique UI designs or to emphasize specific interactions, you might want to customize this animation. This article will guide you through creating custom Hero animations, specifically focusing on applying a custom curve to the shared element transition.

Understanding Hero Animations

A Hero animation occurs when two Hero widgets, one on the source route and one on the destination route, share the same tag. Flutter automatically animates the widget from its position and size on the source route to its position and size on the destination route. This creates a visually appealing motion that helps users understand the flow of information between screens.

The Default Hero Animation

Let's start with a basic Hero animation example to illustrate the default behavior. Ensure you have the following file structure for your project:

  • lib/main.dart
  • lib/first_screen.dart
  • lib/second_screen.dart

First, define your main application structure in lib/main.dart:


import 'package:flutter/material.dart';
import 'package:flutter_app/first_screen.dart'; // Assuming your project name is flutter_app

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Hero Animation Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const FirstScreen(),
    );
  }
}

Next, create the FirstScreen in lib/first_screen.dart:


import 'package:flutter/material.dart';
import 'package:flutter_app/second_screen.dart'; // Assuming your project name is flutter_app

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('First Screen')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Hero(
              tag: 'imageHero',
              child: GestureDetector(
                onTap: () {
                  Navigator.push(context, MaterialPageRoute(builder: (_) {
                    return const SecondScreen();
                  }));
                },
                child: Image.network(
                  'https://picsum.photos/100', // Small image
                  width: 100.0,
                  height: 100.0,
                ),
              ),
            ),
            const SizedBox(height: 20),
            const Text('Tap the image to see Hero animation'),
          ],
        ),
      ),
    );
  }
}

And finally, the SecondScreen in lib/second_screen.dart:


import 'package:flutter/material.dart';

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Second Screen')),
      body: Center(
        child: Hero(
          tag: 'imageHero',
          child: Image.network(
            'https://picsum.photos/400', // Larger image
            width: 300.0,
            height: 300.0,
          ),
        ),
      ),
    );
  }
}

Run this code, and you'll observe the image animating smoothly between screens using the default curve, which is typically Curves.fastOutSlowIn.

Customizing Hero Animations with Curves

To introduce a custom curve to our Hero animation, we need to intercept the route transition. Flutter provides the PageRouteBuilder class, which allows us to define custom transitions. While the createRectTween method within PageRouteBuilder is used to define the actual path (e.g., straight or arc) the Hero takes, the speed or timing along that path is controlled by the route's primary animation curve.

We'll create a custom route that applies a specific curve to the route's animation, and since the Hero animation is driven by this route's animation, it will inherit the custom curve. Let's use Curves.elasticOut for a more dramatic, bouncing effect.

Implementing a Custom Hero Route with a Curve

Create a new file lib/custom_hero_route.dart:


import 'package:flutter/material.dart';

class CustomHeroRoute extends PageRouteBuilder {
  final Widget page;
  final Curve curve;

  CustomHeroRoute({required this.page, this.curve = Curves.fastOutSlowIn})
      : super(
          pageBuilder: (
            BuildContext context,
            Animation animation,
            Animation secondaryAnimation,
          ) =>
              page,
          // Define a transition for the route itself. This can be anything.
          transitionsBuilder: (
            BuildContext context,
            Animation animation,
            Animation secondaryAnimation,
            Widget child,
          ) =>
              FadeTransition( // Example: a simple fade transition for the entire route
            opacity: animation,
            child: child,
          ),
          transitionDuration: const Duration(milliseconds: 700), // Adjust duration as needed
        );

  @override
  Animation createAnimation() {
    // This method is key: It allows us to apply a custom curve to the route's
    // primary animation, which in turn drives the Hero transition.
    return CurvedAnimation(
      parent: super.createAnimation(),
      curve: curve,
    );
  }

  // Optionally, you can override createRectTween for a custom Hero *path*.
  // For simply applying a curve, the default MaterialRectCenterArcTween path is usually fine.
  @override
  RectTween createRectTween(Rect? begin, Rect? end) {
    return MaterialRectCenterArcTween(begin: begin, end: end);
  }
}

Now, modify your FirstScreen (lib/first_screen.dart) to use CustomHeroRoute when navigating to SecondScreen:


import 'package:flutter/material.dart';
import 'package:flutter_app/custom_hero_route.dart'; // Import your custom route
import 'package:flutter_app/second_screen.dart'; // Assuming your project name is flutter_app

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('First Screen')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Hero(
              tag: 'imageHero',
              child: GestureDetector(
                onTap: () {
                  Navigator.push(
                    context,
                    CustomHeroRoute(
                      page: const SecondScreen(),
                      curve: Curves.elasticOut, // Apply your desired curve here!
                    ),
                  );
                },
                child: Image.network(
                  'https://picsum.photos/100',
                  width: 100.0,
                  height: 100.0,
                ),
              ),
            ),
            const SizedBox(height: 20),
            const Text('Tap the image to see Custom Hero animation'),
          ],
        ),
      ),
    );
  }
}

By simply replacing MaterialPageRoute with CustomHeroRoute and specifying curve: Curves.elasticOut, your Hero animation will now exhibit an elastic, bouncing movement during its transition, thanks to the custom curve applied to the route's primary animation controller.

Conclusion

Flutter's Hero animations are powerful out of the box, but their customizability allows for even richer user experiences. By leveraging PageRouteBuilder and specifically overriding the animation controller's curve via createAnimation, you can dictate the timing and feel of your shared element transitions. This technique enables you to integrate unique motion designs that align perfectly with your application's brand and interaction patterns, making your Flutter apps truly stand out.

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