Flutter : Shared Element Transitions — Hero — Heroes

As Android developers, most of us have seen the Shared Element Transition, if we have not seen it, I will explain what it is.

Shared Element transition are the animations that are shown for certain elements that go from one screen to another, the clearest example is when we have a list of elements with thumbnail images, then we go into the detail of that element, and we see how it is shown an animation from the initial screen, towards the destination screen, moving the image while the transition continues
Shared Element Transition example

In Android perform such animations can be a bit annoying depending on the views we try to communicate, since they can be from an Activity -> Activity, Fragment -> Activity, Activity -> Fragment or Fragment -> Fragment.

In Flutter this animation has the name of ‘Hero’, and now I’m going to show you how easy is it to do it.

To achieve a simple transition animation, we need two Heroes, Hero origin and Hero destination, the 2 must share the same tag name to make it work.
So if we want our image to have that animation, we have to wrap our widget with a Hero widget.

I created a custom widget that we will use to move from the origin to destination

class CustomLogo extends StatelessWidget {
final double size;
CustomLogo({this.size = 200.0});
@override
Widget build(BuildContext context) {
return Container(
color: Colors.lightBlueAccent,
width: size,
height: size,
child: Center(
child: Image.asset(
"images/mario_logo.png",
width: size,
height: size,
),
),
);
}
}

This is the image if you want to use

mario_logo.png

Don’t forget to add it into the pubspec.yaml file

assets:
- images/mario_logo.png

After we have our child widget, we have to put it inside our Hero widget with a defined tag name, in this case I will use hero1 as tag name.

                Hero(
tag: "hero1",
child: ClipOval(
child: CustomLogo(
size: 60.0,
),
),
),

As you can see in the code above, our CustomLogo is wrapped by ClipOval (To cut out our widget in a circular way) and also wrapped by the Hero.

Now that we have our origin Hero , let’s go to call our destination page that contains the destination Hero:

Navigator.push(
context,
MaterialPageRoute(
fullscreenDialog: true, builder: (BuildContext context) => Page1()));

Basically, after we press some button, we’ll call to the Navigator.push to go to the next page.

And this is our Page1 (destination page) widget.

As you can see in the code above, we are using a our CustomLogo with a custom size , wrapped by a Hero with the same tag name as the origin “hero1”

Result

It is also possible to have animation for more than one element, that is, we can have more than one Hero per page, in the same way Hero origin and destination must have the same tag names.

In this case we will show a Hero image and a Hero text as they move towards the destination screen.

Column(
children: <Widget>[
Hero(
tag: "hero1",
child: ClipOval(
child: CustomLogo(
size: 60.0,
),
),
),
Hero(
tag: "hero2",
child: Material(
color: Colors.transparent,
child: Text(
"Sample Hero",
style: TextStyle(fontSize: 14.0, color: Colors.black),
),
))
],
)

Calling our Page2

Navigator.push(
context,
MaterialPageRoute(
fullscreenDialog: true, builder: (BuildContext context) => Page2()));

Result

If you see this animation you notice a glitch in the Text widget transition, I think it’s a Flutter bug, but it can easily fixed by wrapping our Text widget origin and destination with the Material widget.

Column(
children: <Widget>[
Hero(
tag: "hero1",
child: ClipOval(
child: CustomLogo(
size: 60.0,
),
),
),
Hero(
tag: "hero2",
child: Material(
color: Colors.transparent,
child: Text(
"Sample Hero",
style: TextStyle(fontSize: 14.0, color: Colors.black),
),
))
],
),

Destination

Result

Works better, right?

Something that many ask is if we can use Heroes using AlertDialog.

This is possible, but to achieve this we have to create a custom PageRoute.

Origin

Column(
children: <Widget>[
Hero(
tag: "hero1",
child: ClipOval(
child: CustomLogo(
size: 60.0,
),
),
),
Hero(
tag: "hero2",
child: Material(
color: Colors.transparent,
child: Text(
"Sample Hero",
style: TextStyle(fontSize: 14.0, color: Colors.black),
),
))
],
)

Destination Popup (after we press click in some button)

Navigator.push(
context, HeroDialogRoute(builder: (BuildContext context) => return Center(
child: AlertDialog(
title: Hero(
tag: "hero2", child: Material(child: Text('You are my hero.'))),
content: Container(
child: Hero(
tag: 'hero1',
child: CustomLogo(
size: 300.0,
)),
),
actions: <Widget>[
OutlineButton(
onPressed: () => Navigator.of(context).pop(),
child: Icon(Icons.close),
),
],
),
)));

You can see an error here, the HeroDialogRoute doesn’t exist , so we’ll have to create that custom route.

This is the code

Result

Finally, we can also create personalized transitions, with the duration and animation that we like the most.

As we can see in the first example, the transition of the background screen is shown a little forced and the animation of the Heroes are shown very fast.

Now I going to create a Hero animation smoother and also with more duration.

Origin

Column(
children: <Widget>[
Hero(
tag: "hero1",
child: ClipOval(
child: CustomLogo(
size: 60.0,
),
),
),
Hero(
tag: "hero2",
child: Material(
color: Colors.transparent,
child: Text(
"Sample Hero",
style: TextStyle(fontSize: 14.0, color: Colors.black),
),
))
],
)

Destination

We’ll use the same Page1 widget as destination page.

Achieving a custom animation is related to the transaction of the PageRoute, we will show an example.

We use a PageRouteBuilder with an AnimatedBuilder as pageBuilder attribute, and inside the animatedBuilder we use the Opacity widget to display the page smoother, also we modified the transitionDuration to 600 milliseconds.

Final Result

Conclusion

Flutter provides us with many tools and APIs for handling transitions and animations, later I will write some related posts about animations.

You can check the source code in my flutter-samples repo https://github.com/diegoveloper/flutter-samples

References

https://flutter.io/animations/hero-animations/