What are hero animations?

In Flutter, the act of transporting an image from one screen to another is termed a hero animation, although this identical movement is occasionally denoted as a shared element transition.

You may have encountered hero animations frequently. For instance, imagine a scenario where a screen exhibits a collection of thumbnails representing products available for purchase. When you choose an item, it smoothly transitions to a new screen, revealing additional details along with a “Buy” button. In Flutter, this process of seamlessly moving an image or item from one screen to another is known as a hero animation, although it is occasionally described as a shared element transition.

This following guide demonstrates how to build standard hero animations, and hero animations that transform the image from a circular shape to a square shape during flying.

We can create these animations in Flutter with Hero widgets. We all know that hero animates from the source to the destination route, the destination route fades into view. heroes are small parts of the UI, like images, that both routes have in common. From the user’s perspective the hero “flies” between the routes. The below guide shows how to create the hero animations:

Terminology: A Route means a page or screen in a Flutter app.

In this tutorial we will learn two types of flutter hero animations:

  Standard hero animations

  Radial hero animations

 

Standard hero animations

standard hero animation flies the hero between one route to a new route, landing a page or screen at a different location and with a different size.

Radial hero animations:

In radial hero animation, as the hero flies in between the routes its shapes changes from circular to rectangular.

Basic structure of a hero animation

we have to use  two hero widgets in separate routes but with identical tags to implement the animation.

The Navigator manages a stack of the app’s routes.

Whenever Pushing a route on or popping a route from the Navigator’s stack then only it triggers the animation.

The Flutter’s framework calculates a rectangle tween, RectTween that defines the boundary of hero as it flies from the source to the destination route. During hero’s flight, the hero is moved to an application overlay, that’s why it appears on top of both routes.

Hero animations are implemented using the two Hero widgets: one describes the widget in the source route, and another describes about the widget in the destination route. From the user’s point of view, the hero appears to be shared, But only the programmer needs to understand it’s implementation in detail. Hero animation code has the following structure:

Define a starting Hero widget, it refers to as the source hero.

Define an ending Hero widget, that refers as the destination hero.

Then create a route that contains the destination hero. The destination route describes the widget tree that exists at the end of the animation

Now Start Triggering  the animation by pushing the destination route on the Navigator’s stack.

 

Text box item sample content

Text box item sample content

Text box item sample content

At the flight completion time:

  • Flutter moves the hero widget from the overlay to the destination route. At that time overlay is empty.

  • The destination hero displayed in its final position in the destination route.

  • The source hero is restored to its route.

 

Important Classes This guide depends on the following fundamental classes to implement hero animations:

  1. Hero: The widget that transitions from the source to the destination route. Flutter animates pairs of heroes with matching tags.

  2. InkWell: Determines the action when tapping the hero. The onTap() method of InkWell constructs the new route and pushes it to the Navigator’s stack.

  3. Navigator: Manages a stack of routes, and the animation is triggered by pushing or popping a route from the Navigator’s stack.

  4. Route: Represents a screen or page. In more complex apps, there are typically multiple routes beyond the basic structure.

Standard hero animations:

Define a route using MaterialPageRoute, CupertinoPageRoute, or build a custom route using PageRouteBuilder.

Change the size of the image at the end of the transition by wrapping the destination’s image in a SizedBox.

Change the location of the image by placing the destination’s image in a layout widget.

PhotoHero class

The  PhotoHero class maintains the hero, and its size, image, and behavior when tapped. The PhotoHero defines the following widget tree:

following is the code for PhotoHero

class PhotoHero extends StatelessWidget {
const PhotoHero({
super.key,
required this.photo,
this.onTap,
required this.width,
});

final String photo;
final VoidCallback? onTap;
final double width;

@override
Widget build(BuildContext context) {
return SizedBox(
width: width,
child: Hero(
tag: photo,
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: onTap,
child: Image.asset(
photo,
fit: BoxFit.contain,
),
),
),
),
);
}
}

HeroAnimation class
The HeroAnimation class creates the source and destination PhotoHeroes, and also creates the transition.

Below is the code :

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

Widget build(BuildContext context) {
timeDilation = 5.0; // 1.0 means normal animation speed.

return Scaffold(
appBar: AppBar(
title: const Text('Basic Hero Animation'),
),
body: Center(
child: PhotoHero(
photo: 'images/flippers-alpha.png',
width: 300.0,
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flippers Page'),
),
body: Container(
// Set background to blue to emphasize that it's a new route.
color: Colors.lightBlueAccent,
padding: const EdgeInsets.all(16),
alignment: Alignment.topLeft,
child: PhotoHero(
photo: 'images/flippers-alpha.png',
width: 100.0,
onTap: () {
Navigator.of(context).pop();
},
),
),
);
}
));
},
),
),
);
}
}

Radial hero animations:

A radial transformation animates from a circular shape into a square shape.

A radial hero animation performs a radial transformation during flying the hero from the source to the destination route.

MaterialRectCenter­Arc­Tween defines the tween animation.

Create the destination route using PageRouteBuilder.

 

Flying a hero between one route to another as it transforms  a circular shape to a rectangular shape is a slick effect that we can implement using Hero widgets. The code animates the intersection of two clip shapes: a circle and a square. During the animation, the circle clip (and the image)  changes scales from minRadius to maxRadius, but the square clip remains fixed size. At that time, the image flies from its position in the source route to the destination route.

 

The radial hero animation consists of the intersection between a circular shape and a square shape. But it is hard to see, even when slowing the animation with timeDilation, so you might enabling the debugPaintSizeEnabled flag during development.

 

The following diagram shows the clipped image  at  the two time intervals beginning (t = 0.0), and the end (t = 1.0) of the animation.

The blue gradient, indicates where the clip shapes intersect. At the beginning of the transition, the result of the intersection is a circular clip (ClipOval). During the transformation, the ClipOval changes scales from minRadius to maxRadius while the ClipRect remains a same size. At the end of the transition the intersection of the circular and rectangular clips became a rectangle that’s the same size as the hero widget. In other words, at the end of the transition the image is no longer clipped.

Photo class:

The Photo class builds the widget tree that contains the image as shown below:

 

class Photo extends StatelessWidget {
const Photo({super.key, required this.photo, this.color, this.onTap});

final String photo;
final Color? color;
final VoidCallback onTap;

Widget build(BuildContext context) {
return Material(
// Slightly opaque color appears where the image has transparency.
color: Theme.of(context).primaryColor.withOpacity(0.25),
child: InkWell(
onTap: onTap,
child: Image.asset(
photo,
fit: BoxFit.contain,
),
),
);
}
}

The InkWell captures the tap gesture. The calling function passes the onTap() function.

The Photo class does not have the Hero in its widget tree. For a animation to work, the hero wraps the RadialExpansion widget. 

 

RadialExpansion class:

The RadialExpansion widget, the core of the demo, builds the widget tree that clips the image during the transition. The clipped shape results from the intersection of a circular clip, with a rectangular clip. To do this, it builds the following widget tree as shown below:

 

RadialExpansion class: 

   Following is the code for RadialExpansion class

class RadialExpansion extends StatelessWidget {
const RadialExpansion({
super.key,
required this.maxRadius,
this.child,
}) : clipRectSize = 2.0 * (maxRadius / math.sqrt2);

final double maxRadius;
final clipRectSize;
final Widget child;

@override
Widget build(BuildContext context) {
return ClipOval(
child: Center(
child: SizedBox(
width: clipRectSize,
height: clipRectSize,
child: ClipRect(
child: child, // Photo
),
),
),
);
}
}

The RadialExpansion animation is created by two overlapping clips.

The hero wraps the RadialExpansion widget. 

In thebelow example, the tweening interpolation is established through MaterialRectCenterArcTween. By default, the hero animation’s flight path interpolates tweens using the corners of the heroes. However, this method impacts the hero’s aspect ratio during the radial transformation. To address this, the updated flight path will be employs MaterialRectCenterArcTween, which interpolates tweens based on the center point of each hero.

static RectTween _createRectTween(Rect? begin, Rect? end) {
return MaterialRectCenterArcTween(begin: begin, end: end);
}

 The image’s aspect ratio remains constant  but the hero’s flight path still follows an arc as we all notice it

Leave a Reply

Categories