主动画过渡
What you’ll learn
- The hero refers to the widget that flies between screens.
- Create a hero animation using Flutter’s Hero widget.
- Fly the hero from one screen to another.
- Animate the transformation of a hero’s shape from circular torectangular while flying it from one screen to another.
- The Hero widget in Flutter implements a style of animationcommonly known as shared element transitions orshared element animations.
You’ve probably seen hero animations many times. For example,a screen displays a list of thumbnails representing itemsfor sale. Selecting an item flies it to a new screen,containing more details and a “Buy” button. Flying an imagefrom one screen to another is called a hero animation_in Flutter, though the same motion is sometimes referred to asa _shared element transition.
This guide demonstrates how to build standardhero animations, and hero animations that transform theimage from a circular shape to a square shape during flight.
Examples
This guide provides examples of each hero animation styleat these links:
New to Flutter?This page assumes you know how to create a layout using Flutter’swidgets. For more information, see Building Layouts inFlutter.
Terminology:A Route describes a page or screenin a Flutter app.
You can create this animation in Flutter with Hero widgets.As the hero animates from the source to the destination route,the destination route (minus the hero) fades into view.Typically, heroes are small parts of the UI, likeimages, that both routes have in common. From the user’sperspective the hero “flies” between the routes.This guide shows how to create the following hero animations:
Standard hero animations
A standard hero animation flies the hero from one route to a newroute, usually landing at a different location and with adifferent size.
The following video (recorded at slow speed) shows a typical example.Tapping the flippers in the center of the route flies them to theupper left corner of a new, blue route, at a smaller size.Tapping the flippers in the blue route(or using the device’s back-to-previous-route gesture)flies the flippers back to the original route.
Radial hero animations
In radial hero animation, as the hero flies between routesits shape appears to change from circular to rectangular.
The following video (recorded at slow speed),shows an example of a radial hero animation. At the start, arow of three circular images appears at the bottom of the route.Tapping any of the circular images flies that image to a new routethat displays it with a square shape.Tapping the square image flies the hero back tothe original route, displayed with a circular shape.
Before moving to the sections specific tostandardor radial hero animations,read basic structure of a hero animationto learn how to structure hero animation code,and behind the scenes to understandhow Flutter performs a hero animation.
Basic structure of a hero animation
- Use two hero widgets in different routes but with matching tags toimplement the animation.
- The Navigator manages a stack containing the app’s routes.
- Pushing a route on or popping a route from the Navigator’s stacktriggers the animation.
- The Flutter framework calculates a rectangletweenthat defines the hero’s boundary as it flies from the source tothe destination route. During its flight, the hero is moved toan application overlay, so that it appears on top of both routes.
Terminology:If the concept of tweens or tweening is new to you, please see theAnimations in Flutter tutorial.
Hero animations are implemented using twoHerowidgets: one describing the widget in the source route,and another describing the widget in the destination route.From the user’s point of view, the hero appears to be shared, andonly the programmer needs to understand this implementation detail.
Note about dialogs:Heroes fly from one PageRoute to another. Dialogs(displayed with showDialog()
, for example), use PopupRoutes,which are not PageRoutes. At least for now,you can’t animate a hero to a Dialog.For further developments (and a possible workaround), watch thisissue.
Hero animation code has the following structure:
- Define a starting Hero widget, referred to as the sourcehero. The hero specifies its graphical representation(typically an image), and an identifying tag, and is inthe currently displayed widget tree as defined by the source route.
- Define an ending Hero widget, referred to as the destination hero.This hero also specifies its graphical representation,and the same tag as the source hero.It’s essential that both hero widgets are created withthe same tag, typically an object that represents theunderlying data. For best results, the heroes should havevirtually identical widget trees.
- Create a route that contains the destination hero.The destination route defines the widget tree that existsat the end of the animation.
- Trigger the animation by pushing the destination route on theNavigator’s stack. The Navigator push and pop operations triggera hero animation for each pair of heroes with matching tags inthe source and destination routes.Flutter calculates the tween that animates the Hero’s bounds fromthe starting point to the endpoint (interpolating size and position),and performs the animation in an overlay.
The next section describes Flutter’s process in greater detail.
Behind the scenes
The following describes how Flutter performs the transition fromone route to another.
Before transition, the source hero waits in the source route’s widgettree. The destination route does not yet exist, and the overlayis empty.
Pushing a route to the Navigator triggers the animation.At t=0.0, Flutter does the following:
Calculates the destination hero’s path, offscreen, using thecurved motion as described in the Material motion spec.Flutter now knows where the hero ends up.
Places the destination hero in the overlay, at thesame location and size as the source hero.Adding a hero to the overlay changes its Z-order so thatit appears on top of all routes.
Moves the source hero offscreen.
As the hero flies, its rectangular bounds are animated usingTween<Rect>,specified in Hero’screateRectTween
property.By default, Flutter uses an instance ofMaterialRectArcTween,which animates the rectangle’s opposing corners along a curved path.(See Radial hero animationsfor an example that uses a different Tween animation.)
When the flight completes:
Flutter moves the hero widget from the overlay to thedestination route. The overlay is now empty.
The destination hero appears in its final position inthe destination route.
The source hero is restored to its route.
Popping the route performs the same process, animating thehero back to its size and location in the source route.
Essential classes
The examples in this guide use the following classes toimplement hero animations:
- Hero
- The widget that flies from the source to the destination route.Define one Hero for the source route and another for thedestination route, and assign each the same tag.Flutter animates pairs of heroes with matching tags.
- Inkwell
- Specifies what happens when tapping the hero.The InkWell’s
onTap()
method builds the new route and pushes itto the Navigator’s stack. - Navigator
- The Navigator manages a stack of routes. Pushing a route on orpopping a route from the Navigator’s stack triggers the animation.
- Route
- Specifies a screen or page. Most apps, beyond the most basic,have multiple routes.
Standard hero animations
- Specify a route using MaterialPageRoute, CupertinoPageRoute, orbuild a custom route usingPageRouteBuilder. The examples in this section use MaterialPageRoute.
- Change the size of the image at the end of the transition bywrapping the destination’s image in a SizedBox.
- Change the location of the image by placing the destination’simage in a layout widget. These examples use Container.
Standard hero animation code
Each of the following examples demonstrates flying an image from oneroute to another. This guide describes the first example.
- hero_animation
- Encapsulates the hero code in a custom PhotoHero widget.Animates the hero’s motion along a curved path,as described in the Material motion spec.
- basic_hero_animation
- Uses the hero widget directly.This more basic example, provided for your reference, isn’tdescribed in this guide.
What’s going on?
Flying an image from one route to another is easy to implementusing Flutter’s hero widget. When using MaterialPageRouteto specify the new route, the image flies along a curved path,as described by the Material Design motionspec.
Create a new Flutter example andupdate it using the files from theGitHub directory.
To run the example:
- Tap on the home route’s photo to fly the image to a new routeshowing the same photo at a different location and scale.
- Return to the previous route by tapping the image, or by using thedevice’s back-to-the-previous-route gesture.
- You can slow the transition further using the
timeDilation
property.
PhotoHero class
The custom PhotoHero class maintains the hero, and its size, image,and behavior when tapped. The PhotoHero builds the followingwidget tree:
Here’s the code:
- class PhotoHero extends StatelessWidget {
- const PhotoHero({ Key key, this.photo, this.onTap, this.width }) : super(key: key);
- final String photo;
- final VoidCallback onTap;
- final double width;
- 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,
- ),
- ),
- ),
- ),
- );
- }
- }
Key information:
- The starting route is implicitly pushed by MaterialApp whenHeroAnimation is provided as the app’s home property.
- An InkWell wraps the image, making it trivial to add a tapgesture to the both the source and destination heroes.
- Defining the Material widget with a transparent colorenables the image to “pop out” of the background as itflies to its destination.
- The SizedBox specifies the hero’s size at the start andend of the animation.
- Setting the Image’s
fit
property toBoxFit.contain
,ensures that the image is as large as possible during thetransition without changing its aspect ratio.
HeroAnimation class
The HeroAnimation class creates the source and destinationPhotoHeroes, and sets up the transition.
Here’s the code:
- class HeroAnimation extends StatelessWidget {
- 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<void>(
- builder: (BuildContext context) {
- return Scaffold(
- appBar: AppBar(
- title: const Text('Flippers Page'),
- ),
- body: Container(
- // The blue background emphasizes that it's a new route.
- color: Colors.lightBlueAccent,
- padding: const EdgeInsets.all(16.0),
- alignment: Alignment.topLeft,
- child: PhotoHero(
- photo: 'images/flippers-alpha.png',
- width: 100.0,
- onTap: () {
- Navigator.of(context).pop();
- },
- ),
- ),
- );
- }
- ));
- },
- ),
- ),
- );
- }
- }
Key information:
- When the user taps the InkWell containing the source hero,the code creates the destination route using MaterialPageRoute.Pushing the destination route to the Navigator’s stacktriggers the animation.
- The Container positions the PhotoHero in the destination route’stop-left corner, below the AppBar.
- The
onTap()
method for the destination PhotoHero pops theNavigator’s stack, triggering the animation that fliesthe Hero back to the original route. - Use the
timeDilation
property to slow the transitionwhile debugging.
Radial hero animations
- A radial transformation animates a circular shape into a squareshape.
- A radial hero animation performs a radial transformation whileflying the hero from the source route to the destination route.
- MaterialRectCenterArcTween defines the tween animation.
- Build the destination route using PageRouteBuilder.
Flying a hero from one route to another as it transforms from acircular shape to a rectanglar shape is a slick effect that youcan implement using Hero widgets. To accomplish this,the code animates the intersection of two clip shapes: acircle and a square.Throughout the animation, the circle clip (and the image) scales fromminRadius
to maxRadius
, while the square clip maintains constantsize. At the same time, the image fliesfrom its position in the source route to its position in thedestination route. For visual examples of this transition, seeRadialtransformation in the Material motion spec.
This animation might seem complex (and it is), but you cancustomize the provided example to your needs.The heavy lifting is done for you.
Radial hero animation code
Each of the following examples demonstrates a radial hero animation.This guide describes the first example.
- radial_hero_animation
- A radial hero animation as described in the Material motion spec.
- basic_radial_hero_animation
- The simplest example of a radial hero animation. The destinationroute has no Scaffold, Card, Column, or Text.This basic example, provided for your reference, isn’tdescribed in this guide.
- radial_hero_animation_animate_rectclip
- Extends radial_hero_animaton by also animating the size of therectangular clip. This more advanced example,provided for your reference, isn’t described in this guide.
Pro tip:The radial hero animation involves intersecting a round shape witha square shape. This can be hard to see, even when slowingthe animation with timeDilation
, so you might consider enablingFlutter’s visual debugging modeduring development.
What’s going on?
The following diagram shows the clipped image at the beginning(t = 0.0
), and the end (t = 1.0
) of the animation.
The blue gradient (representing the image), indicates where the clipshapes intersect. At the beginning of the transition,the result of the intersection is a circular clip(ClipOval).During the transformation,the ClipOval scales from minRadius
to maxRadius
while theClipRectmaintains a constant size.At the end of the transition the intersection of the circular andrectangular clips yield a rectangle that’s the same size as the herowidget. In other words, at the end of the transition the image is nolonger clipped.
Create a new Flutter example andupdate it using the files from theGitHub directory.
To run the example:
- Tap on one of the three circular thumbnails to animate the imageto a larger square positioned in the middle of a new route thatobscures the original route.
- Return to the previous route by tapping the image, or by using thedevice’s back-to-the-previous-route gesture.
- You can slow the transition further using the
timeDilation
property.
Photo class
The Photo class builds the widget tree that holds the image:
- class Photo extends StatelessWidget {
- Photo({ Key key, this.photo, this.color, this.onTap }) : super(key: key);
- 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,
- )
- ),
- );
- }
- }
Key information:
The Inkwell captures the tap gesture.The calling function passes the
onTap()
function to thePhoto’s constructor.During flight, the InkWell draws its splash on its first Materialancestor.
The Material widget has a slightly opaque color, so thetransparent portions of the image are rendered with color.This ensures that the circle-to-square transition is easy to see,even for images with transparency.
The Photo class does not include the Hero in its widget tree.For the animation to work, the herowraps the RadialExpansion widget, which wraps the hero.
RadialExpansion class
The RadialExpansion widget, the core of the demo, builds thewidget tree that clips the image during the transition.The clipped shape results from the intersection of a circular clip(that grows during flight),with a rectangular clip (that remains a constant size throughout).
To do this, it builds the following widget tree:
Here’s the code:
- class RadialExpansion extends StatelessWidget {
- RadialExpansion({
- Key key,
- this.maxRadius,
- this.child,
- }) : clipRectSize = 2.0 * (maxRadius / math.sqrt2),
- super(key: key);
- final double maxRadius;
- final clipRectSize;
- final Widget child;
- @override
- Widget build(BuildContext context) {</span>
- return ClipOval(
- child: Center(
- child: SizedBox(
- width: clipRectSize,
- height: clipRectSize,
- child: ClipRect(
- child: child, // Photo
- ),
- ),
- ),
- );
- }
- }
Key information:
The hero wraps the RadialExpansion widget.
As the hero flies, its size changes and,because it constrains its child’s size, the RadialExpansionwidget changes size to match.
The RadialExpansion animation is created by twooverlapping clips.
The example defines the tweening interpolation usingMaterialRectCenterArcTween.The default flight path for a hero animation interpolates the tweensusing the corners of the heroes. This approach affects thehero’s aspect ratio during the radial transformation,so the new flight path uses MaterialRectCenterArcTween tointerpolate the tweens using the center point of each hero.
Here’s the code:
- static RectTween _createRectTween(Rect begin, Rect end) {
- return MaterialRectCenterArcTween(begin: begin, end: end);
- }
The hero’s flight path still follows an arc,but the image’s aspect ratio remains constant.
Resources
The following resources might help when writing animations:
- Animations landing page
- Lists the available documentation for Flutter animations.If tweens are new to you, check out theAnimations tutorial.
- Flutter API documentation
- Reference documentation for all of the Flutter libraries.In particular, see the animationlibrarydocumentation.
- Flutter Gallery
- Demo app showcasing many Material Design widgets and other Flutterfeatures. The Shrinedemoimplements a hero animation.
- Material motion spec
- Describes motion for Material design apps.