Hero animation in Flutter is one of the most popular visual designs which can be easily implemented using Flutter's hero widget. When we discuss hero animation, it refers to a style of animation where a widget(or an element) "flies" between two screens. This pattern of animation is commonly known as shared element transitions or shared element animations.
Hero animations are commonly used in mobile applications. You might have noticed such a transition while using apps. One typical use case scenario would be in a news app, where one would select a news item featuring a thumbnail image of the story in a list. Once selected, the image 'flies' to the top of the detail page, where we find more information about the story.
As we proceed further into these techniques, you will learn how to fly widgets from one page to another, animate transformations of widgets' shapes, and understand the process behind these animations.
Having already introduced the basics of hero animation in Flutter, let's proceed to explore the hero widget more profoundly.
One of the defining features of the hero widget in Flutter is its ability to create visually delightful animations between two pages in an application. What technically happens under the hood is this: two hero widgets — one on each page, carrying the same tag — animate between screens when the user navigates from one page to the next. This transition makes it appear as if the same image or widget is 'flying' from the source route to the destination route, providing a smooth and continuous user experience.
Here is a sample Flutter code snippet for creating hero widgets on two screens:
1 class MyApp extends StatelessWidget { 2 @override 3 Widget build(BuildContext context) { 4 return MaterialApp( 5 home: FirstScreen(), 6 ); 7 } 8 } 9 10 class FirstScreen extends StatelessWidget { 11 @override 12 Widget build(BuildContext context) { 13 return Scaffold( 14 appBar: AppBar( 15 title: Text('First Screen'), 16 ), 17 body: GestureDetector( 18 onTap: () { 19 Navigator.push(context, MaterialPageRoute(builder: (_) { 20 return SecondScreen(); 21 })); 22 }, 23 child: Hero( 24 tag: 'imageHero', 25 child: Image.network( 26 'https://example.com/image.jpg', 27 ), 28 ), 29 ), 30 ); 31 } 32 } 33 34 class SecondScreen extends StatelessWidget { 35 @override 36 Widget build(BuildContext context) { 37 return Scaffold( 38 appBar: AppBar( 39 title: Text('Second Screen'), 40 ), 41 body: GestureDetector( 42 onTap: () { Navigator.pop(context); }, 43 child: Center( 44 child: Hero( 45 tag: 'imageHero', 46 child: Image.network( 47 'https://example.com/image.jpg', 48 ), 49 ), 50 ), 51 ), 52 ); 53 } 54 } 55 56 void main() { 57 runApp(MyApp()); 58 } 59
In this Flutter code snippet, we can create a simple MyApp with two pages that allow us to navigate back and forth. The hero widgets, carrying the same tag 'imageHero' and the same image, animate nicely between the two pages.
The hero widget in Flutter not only offers simple animations but also lets you transform the widget's shape along its journey from the source to the destination. This can be achieved with custom-built tweens and rect changes. Whether you need to transition a widget from circular to rectangular or vice versa, hero animation offers endless possibilities to the developers.
Now we've seen the basic usage of the hero widget, let's better understand the principles and structure behind it.
The structure of a hero animation in Flutter revolves around two hero widgets with the same tag, one each on the source and destination routes.
In the animation process, the hero widget 'flies' from the source route to the destination route. Simultaneously, the destination route (minus the hero) gradually fades in. This makes it seem as if the hero is 'travelling' between screens, creating a visually pleasing experience for the user.
It's essential to note that both hero widgets have the same tag — this is crucial for Flutter to recognize which widgets to animate between screens. The tags are typically objects that represent the underlying data. For instance, you can use the unique ID of an item as the hero tag.
Flutter uses an intermediate step to perform the hero transition animation. When pushing a route onto the Navigator's stack triggers the animation, Flutter calculates the destination hero's bounds change, during which the hero flies in an application overlay. This overlay ensures that the hero appears on top of both routes during the transition.
Flutter also implements a rectangle tween, known as RectTween, to define the hero's boundary as it flies from the source to the destination route. By default, Flutter uses the MaterialRectArcTween instance, which animates the rectangle's opposing corners along a curved path.
In a project that uses hero animations in Flutter, understanding how to structure and trigger them effectively is crucial. Let's delve deeper into implementing hero animations.
The first step to the implementation of hero animations is defining the source and destination hero widgets. Both heroes are required to specify their graphical representation (typically an image) and an identical tag. These heroes' tags are usually objects that represent the underlying data. Remember, the two hero widgets must share the same tag for the hero transition animation to take place successfully.
1 class SourceHero extends StatelessWidget { 2 @override 3 Widget build(BuildContext context) { 4 return Hero( 5 tag: 'imageHero', 6 child: Image.network( 7 'https://example.com/image.jpg', 8 ), 9 ); 10 } 11 } 12 13 class DestinationHero extends StatelessWidget { 14 @override 15 Widget build(BuildContext context) { 16 return Hero( 17 tag: 'imageHero', 18 child: Image.network( 19 'https://example.com/image.jpg', 20 ), 21 ); 22 } 23 } 24
In this Flutter code snippet, notice that both SourceHero and DestinationHero have the same tag and display the same image. They represent the source and destination heroes respectively for the hero animation.
Once the heroes are in place, you need to define a route that contains the destination hero. The destination route essentially defines the widget tree that exists at the end of the animation, including the destination hero.
This is when the Navigator comes into play — pushing the destination route onto the Navigator's stack triggers the animation. The Navigator recognizes the heroes with the same tag and initiates an overlay transition from the source hero to the destination hero.
With the necessary widgets and routes in place, it's time to delve into how Flutter animates the transformation of the hero widget from the source route to the destination route.
In Flutter, the primary triggers for hero animation are the pushing and popping of routes from the Navigator's stack. The Navigator, being a stack of routes in Flutter, initiates the hero animation for every pair of heroes with matching tags in the source and destination routes when you push or pop a route.
Pushing a route onto the stack, such as when a user taps a hero widget, initiates the hero's flight from the source route to the destination route. Popping a route, such as when the user uses a back swipe gesture or presses the back button, triggers the hero's return journey.
This interaction is made possible by the 'InkWell' widget surrounding the hero widget in both routes. In Flutter, InkWell is a material design concept that implements a rectangular area of Material that responds to touch. For example:
1 class MyHero extends StatelessWidget { 2 @override 3 Widget build(BuildContext context) { 4 return InkWell( 5 onTap: () { 6 Navigator.push(context, MaterialPageRoute(builder: (_) { 7 return SecondScreen(); 8 })); 9 }, 10 child: Hero( 11 tag: 'imageHero', 12 child: Image.network( 13 'https://example.com/image.jpg', 14 ), 15 ), 16 ); 17 } 18 } 19
In this code sample, we have an InkWell widget that wraps the Hero widget. When a user taps on the hero, the Navigator pushes a new route onto the stack, triggering the hero animation.
As we explored earlier, during the transition, the hero flies in an application overlay, appearing on top of both routes. This is made possible by placing the destination hero in the overlay at the same location and size as the source hero. As the hero's flight progresses, the overlay adjusts the rectangular bounds of the hero using a Tween of type Rect (RectTween).
Having learned about the triggers for the hero animation and the role of the overlay, let's delve into how Flutter performs the transition from one route to another using the hero animation.
Before the transition begins, the source hero waits in the source route’s widget tree. At this point, the overlay is empty, and the destination route does not exist yet.
Once the Navigator triggers the animation, several things occur.
Initially, Flutter calculates the final location and size of the hero on the destination route offscreen using a curved path as described in the Material motion specification. Then, it places the destination hero on the overlay at the same location and size as the source hero.
A point worthy of note here is that adding a hero to the overlay changes its Z-order so that it appears on top of all routes. After this, the source hero moves offscreen.
Throughout the hero's flight in the overlay to its final position, its rectangular bounds, as specified in Hero’s createRectTween property, get animated using Tween<Rect>
. By default, Flutter uses an instance of MaterialRectArcTween for this, which animates the rectangle's opposing corners along a curved path.
Once the flight completes, Flutter moves the hero widget from the overlay to the destination route, leaving the overlay empty. At this point, the destination hero appears in its final position in the destination route.
Popping the route triggers the same series of actions as before but in reverse order, animating the hero back to its original size and location in the source route.
Flutter offers several easy-to-use classes to implement Hero Animations. These classes form the backbone of a hero animation, serving different purposes in the hero's flight.
The Hero class is the fundamental widget that transitions across screens. It is defined by a tag that uniquely identifies it. Notice that it is this tag that allows Flutter to recognize the widget that must be animated when we move from one screen to another.
1 class MyHero extends StatelessWidget { 2 @override 3 Widget build(BuildContext context) { 4 return Hero( 5 tag: 'hero1', 6 child: CircleAvatar( 7 backgroundColor: Colors.amber, 8 ), 9 ); 10 } 11 } 12
In the code snippet above, we are defining a Hero widget with the tag 'hero1'.
We surround the hero with an InkWell widget, which comes with an onTap() method. We can hence make use of InkWell to define what happens when the user taps on our hero. When the user taps on the hero, we can use Navigator to push a new route to the Navigator's stack and begin the hero's flight.
The Navigator holds and manages a stack of routes. It is an essential class for creating multi-screen applications. Pushing and popping routes using the Navigator triggers the animation for pairs of heroes with matching tags in the source and destination routes.
1 InkWell( 2 onTap: () { 3 Navigator.push(context, MaterialPageRoute(builder: (_) { 4 return SecondScreen(); 5 })); 6 }, 7 child: Hero( 8 tag: 'hero1', 9 child: CircleAvatar( 10 backgroundColor: Colors.amber, 11 ), 12 ), 13 ) 14
In the provided code snippet, we are pushing a new route onto the Navigator's stack when the user taps the hero. This trigger initiates the hero's flight from the source route to the destination route.
A Route describes a page or screen in a Flutter app. We can define our destination route, which may contain key elements like Scaffold and AppBar along with the destination hero.
Understanding and implementing standard hero animations are fundamental for any developer intending to use Flutter's hero widget. Let's dive into it.
Creating a standard hero animation is straightforward. A standard hero animation flies the hero from one route to a new route, usually landing at a different location and with a different size.
An example of the code necessary to create such an animation is below:
1 // Define a starting Hero widget, referred to as the source hero. 2 class SourceHero extends StatelessWidget { 3 @override 4 Widget build(BuildContext context) { 5 return Hero( 6 tag: 'imageHero', 7 child: Image.network( 8 'https://example.com/image.jpg', 9 ), 10 ); 11 } 12 } 13 14 // Define an ending Hero widget, referred to as the destination hero. 15 class DestinationHero extends StatelessWidget { 16 @override 17 Widget build(BuildContext context) { 18 return Hero( 19 tag: 'imageHero', 20 child: Image.network( 21 'https://example.com/image.jpg', 22 ), 23 ); 24 } 25 } 26 27 // Trigger the animation by pushing the destination route on the Navigator’s stack. 28 InkWell( 29 onTap: () { 30 Navigator.push(context, MaterialPageRoute(builder: (_) { 31 return SecondScreen(); 32 })); 33 }, 34 child: Hero( 35 tag: 'hero1', 36 child: CircleAvatar( 37 backgroundColor: Colors.amber, 38 ), 39 ), 40 ) 41
In this example, we defined a SourceHero and a DestinationHero. Each has the Hero widget representing an image, and both carries the same tag 'imageHero'. We've also encapsulated the hero code within an InkWell. On tapping the widget, we pushed a new route to the Navigator’s stack, triggering the animation.
The HeroAnimation class is a key component for utilizing and managing Hero animations. This class facilitates defining heroes and setting up the transition.
In the HeroAnimation class, you can set the speed of the animation, design the transition, and define the destination route for the hero’s journey. It's in this class you also manage the user gestures, such as onTap to push a new screen onto the Navigator’s stack, initiating the Hero Transition.
Beyond standard hero animation, Flutter also offers the option to create more intricate and engaging animations such as the radial hero animation.
In a radial hero animation, as the hero flies between routes, its shape appears to change from circular to rectangular. This transformation is visually impressive and can add a unique aesthetic to your application.
In addition to the visual appeal, the radial hero animation also provides a clear guide for the user, smoothly transitioning them from one screen to another.
Implementing a radial hero animation involves calculating and animating the intersection of two clip shapes: a circle and a square. Throughout the animation, the circular clip (and the image) scales from a minimum radius to a maximum radius, while the square clip maintains a constant size. At the same time, the image flies from its position on the source route to its position on the destination route.
To simplify this process, Flutter provides a MaterialRectCenterArcTween, which defines the tween that animates the hero's rectangle from the starting point to the endpoint.
Now that we understand what radial hero animation is and its visual appeal let's look at some ways to code a radial hero animation.
Creating a radial hero animation may seem complex, given that it involves manipulating two different shapes (a circle and a square). However, with the use of Flutter's predefined classes and functions, the steps required become more approachable. Here's a simplified code snippet for radial hero animation:
1 class RadialExpansionDemo extends StatelessWidget { 2 @override 3 Widget build(BuildContext context) { 4 return Center( 5 child: PhotoHero( 6 photo: 'https://example.com/image.jpg', 7 onTap: () { 8 Navigator.of(context).push( 9 MaterialPageRoute<void>( 10 builder: (BuildContext context) { 11 return Scaffold( 12 appBar: AppBar( 13 title: const Text('Radial Transition Demo'), 14 ), 15 body: Container( 16 color: Colors.lightBlueAccent, 17 padding: const EdgeInsets.all(16.0), 18 alignment: FractionalOffset.topLeft, 19 child: PhotoHero( 20 photo: 'https://example.com/image.jpg', 21 onTap: () { 22 Navigator.of(context).pop(); 23 }, 24 ), 25 ), 26 ); 27 }, 28 ), 29 ); 30 }, 31 ), 32 ); 33 } 34 } 35
In this example, we start with an image. When you tap this image (thanks to the InkWell widget), a new route (detail page) is pushed onto the stack via the Navigator’s push method. This causes our hero (the image) to "fly" across the screen, morphing from a circular to rectangular shape.
An essential part of the radial hero animation is dealing with the change in shape of the widget. This is where the RadialExpansion Widget comes into play, which builds a widget tree that includes two clip elements: ClipOval and ClipRect. As the widget flies from the source route to the destination route, ClipRect remains a constant size while ClipOval scales from the minimum radius to the maximum radius, changing the perceived shape of our hero image.
Implementing a RadialExpansion widget in your code would look something like this:
1 class RadialExpansion extends StatelessWidget { 2 RadialExpansion({ 3 Key key, 4 this.maxRadius, 5 this.child, 6 }) : clipRectSize = 2.0 * maxRadius, 7 super(key: key); 8 9 final double maxRadius; 10 final clipRectSize; 11 final Widget child; 12 13 @override 14 Widget build(BuildContext context) { 15 return ClipOval( 16 child: Center( 17 child: SizedBox( 18 width: clipRectSize, 19 height: clipRectSize, 20 child: ClipRect( 21 child: child 22 ), 23 ), 24 ), 25 ); 26 } 27 } 28
In this example, we added clipRectSize so that we can systematically control the size of the ClipRect inside our widget. We also allow child widgets through the child parameter, offering more flexibility with what widget to animate.
After diving into creating standard and radial hero animations, let's now turn our attention to tips and techniques to ensure these animations run smoothly and look fantastic.
While working with hero animations in Flutter, there are a few key considerations you should bear in mind:
Some handy tools can enhance your experience while working with hero animations. Flutter offers properties like flightShuttleBuilder and transitionOnUserGestures in the Hero class.
The flightShuttleBuilder property allows customization of the hero widget that flies between routes, and transitionOnUserGestures enables users to control the animation when swiping back to the previous route.
So, you've got your hero animations working, and they look pretty good. But there are a few things you can do to take your animations to the next level and make them look even better.
The type of page route used to navigate to the next page can have a significant effect on how the Hero's animations are handled. MaterialPageRoute and CupertinoPageRoute are two options that come built-in with Flutter and provide some pre-defined animations. However, if you want full control, you can use PageRouteBuilder to build your custom routes and define precisely how the animations should run.
To easily adjust the ending size of your Hero widget, consider wrapping the Destination Hero in a SizedBox. This allows you to explicitly set the width and height that the Hero widget should animate towards.
1 SizedBox( 2 width: 300, 3 height: 300, 4 child: Hero( 5 tag: 'imageHero', 6 child: Image.network( 7 'https://example.com/image.jpg', 8 ), 9 ), 10 ); 11
In this code snippet, the destination hero is wrapped in a SizedBox, which specifies the hero size once the animation is finished.
Just as you can control the ending size of your Hero, you also have full control over its final location. By placing the Hero within different layout widgets, such as Container, Padding, or Align, you can specify where on the screen the Hero should animate towards.
One common problem developers run into while implementing Hero animations is the 'Hero animations in parallel navigation history' exception. This happens if you push two routes to the Navigator's stack and both routes contain a Hero with the same tag. Flutter won't be able to manage the simultaneous animations of two heroes with the same tag. To avoid this, make sure every hero has a unique tag unless they connect the same two routes.
One fundamental class is HeroController. This class creates a new Hero controller that can be used to manually manage hero animations. By setting the createRectTween property, we get full reign over how the target and destination rectangles interpolate during a hero's flight.
To create a smooth transition, consider wrapping the widgets before and after hero animations are the same in terms of widget hierarchy.
Also, it might be tempting to use high-resolution images as your heroes, but remember that the larger the image you're trying to animate, the more memory it uses and the longer it may take to load, which can cause a noticeable delay in the animation.
Flutter also comes with a handy debugging feature for hero animations. By setting the timeDilation property to a number larger than one, we can slow down the animations, making it easier to observe and debug them.
Hero animation is unarguably one of the most potent tools in the world of Flutter app development. Not only does it provide a smooth and visually appealing transition between widgets, but it also plays a crucial role in enhancing the users' experience within an app.
WiseGPT is a game-changing plugin that brings a revolutionary code generation experience to your Flutter projects. Specifically designed for Flutter, this powerful IDE plugin allows you to effortlessly create complex animation code without any output size restrictions, significantly enhancing your productivity.
WiseGPT
One of the standout features of WiseGPT is its ability to mirror your coding style seamlessly. No more worrying about disjointed code snippets or inconsistent formatting. The code generated by WiseGPT effortlessly blends with your existing codebase, maintaining consistency and reducing the need for tedious adjustments.
This means more time spent building exceptional animations and less time grappling with code structure.
Gone are the days of dealing with prompts or spending precious minutes tweaking the generated code. WiseGPT intuitively understands your requirements without any prompts, ensuring a smooth and efficient code generation process. It effortlessly grasps the essence of your animation needs and generates the corresponding code, saving you valuable time and effort.
WiseGPT takes automation to a new level by automatically creating files and functions for your animations. No more manual code management or setting up boilerplate code. With WiseGPT, you can focus on defining the core aspects of your animations while the tool handles the rest.
This automation streamlines your development workflow and allows you to build more robust and scalable Flutter applications in a fraction of the time.
Incorporating WiseGPT into your Flutter development toolkit empowers you to take your animation projects to new heights. This plugin streamlines the code generation process, saving you time and effort while maintaining the quality of your animations. Say goodbye to mundane coding tasks and embrace the seamless integration and automation that WiseGPT brings to your Flutter projects.
By combining the power of Flutter Animations with WiseGPT, you unlock a whole new realm of possibilities for your app development. Embrace the potential of this innovative tool and witness a significant transformation in your Flutter projects.
Whether you're a skilled developer or just starting with Flutter, WiseGPT is a must-try addition to your toolkit. Streamline your animation workflow and unleash your creativity with WiseGPT for Flutter!
Tired of manually designing screens, coding on weekends, and technical debt? Let DhiWise handle it for you!
You can build an e-commerce store, healthcare app, portfolio, blogging website, social media or admin panel right away. Use our library of 40+ pre-built free templates to create your first application using DhiWise.