- Install
- Set up an editor
- Test drive
- Write your first Flutter app, part 1
- Learn more
- Flutter for Android developers
- Flutter for iOS developers
- Flutter for React Native developers
- Flutter for web developers
- Flutter for Xamarin.Forms developers
- Introduction to declarative UI
- Cookbook
- Codelabs
- Tutorials
- User interface
- Introduction to widgets
- Layouts in Flutter
- Layout tutorial
- Dealing with box constraints
- Adding interactivity to your Flutter app
- Adding assets and images
- Navigation & routing
- Introduction to animations
- Animations overview
- Animations tutorial
- Hero Animations
- Staggered Animations
- Advanced UI
- Slivers
- Taps, drags, and other gestures
- Widget catalog
- Data & backend
- State management
- State management
- Start thinking declaratively
- Differentiate between ephemeral state and app state
- Simple app state management
- List of state management approaches
- JSON and serialization
- Firebase
- Accessibility & internationalization
- Accessibility
- Internationalizing Flutter apps
- Platform integration
- Writing custom platform-specific code
- Packages & plugins
- Using packages
- Developing packages & plugins
- Background processes
- Tools & techniques
- Android Studio / IntelliJ
- Visual Studio Code
- Upgrading Flutter
- Hot reload
- Code formatting
- Debugging Flutter apps
- Using OEM debuggers
- Flutter's build modes
- Testing Flutter apps
- Performance best practices
- Flutter performance profiling
- Creating flavors for Flutter
- Preparing an Android App for Release
- Preparing an iOS App for Release
- Continuous Delivery using fastlane with Flutter
- Bootstrap into Dart
- Inside Flutter
- Platform specific behaviors and adaptations
- Technical Overview
- Technical videos
- FAQ
- Flutter widget index
- Install
- Windows install
- MacOS install
- Linux install
- Set up an editor
- Write your first Flutter app, part 1
- Learn more
- Cupertino (iOS-style) widgets
- Layout widgets
- Animation and motion widgets
- Retrieve the value of a text field
- Basic widgets
- Material Components widgets
- Animate the properties of a Container
- Fade a Widget in and out
- Add a Drawer to a screen
- Displaying SnackBars
- Exporting fonts from a package
- Updating the UI based on orientation
- Using Themes to share colors and font styles
- Using custom fonts
- Working with Tabs
- Building a form with validation
- Create and style a text field
- Focus on a Text Field
- Handling changes to a text field
- Retrieve the value of a text field
- Adding Material Touch Ripples
- Handling Taps
- Implement Swipe to Dismiss
- Display images from the internet
- Fade in images with a placeholder
- Working with cached images
- Basic List
- Create a horizontal list
- Creating a Grid List
- Creating lists with different types of items
- Place a floating app bar above a list
- Working with long lists
- Report errors to a service
- Animating a Widget across screens
- Navigate to a new screen and back
- Navigate with named routes
- Pass arguments to a named route
- Return data from a screen
- Send data to a new screen
- Fetch data from the internet
- Making authenticated requests
- Parsing JSON in the background
- Working with WebSockets
- Persist data with SQLite
- Reading and Writing Files
- Storing key-value data on disk
- Play and pause a video
- Take a picture using the Camera
- An introduction to integration testing
- Performance profiling
- Scrolling
- An introduction to unit testing
- Mock dependencies using Mockito
- An introduction to widget testing
- Finding widgets
- Tapping, dragging and entering text
- Development
- Introduction to widgets
- Layout tutorial
- Dealing with box constraints
- Adding interactivity to your Flutter app
- Adding assets and images
- Navigation & routing
- Navigate to a new screen and back
- Send data to a new screen
- Return data from a screen
- Navigate with named routes
- Animating a Widget across screens
- AnimatedList
- Sample App Catalog
- Animations overview
- Animations tutorial
- Staggered Animations
- Slivers
- Taps, drags, and other gestures
- Accessibility widgets
- Assets, images, and icon widgets
- Async widgets
- Input widgets
- Interaction model widgets
- Painting and effect widgets
- Scrolling widgets
- Styling widgets
- Text widgets
- State management
- Start thinking declaratively
- Differentiate between ephemeral state and app state
- Simple app state management
- List of state management approaches
- JSON and serialization
- Accessibility
- Internationalizing Flutter apps
- Writing custom platform-specific code
- Using packages
- Fetch data from the internet
- Developing packages & plugins
- Background processes
- Android Studio / IntelliJ
- Set up an editor
- Flutter inspector
- Creating Useful Bug Reports
- Visual Studio Code
- Set up an editor
- Upgrading Flutter
- Hot reload
- Code formatting
Hero Animations
You’ve probably seen hero animations many times. For example, a screen displays a list of thumbnails representing items for sale. Selecting an item flies it to a new screen, containing more details and a “Buy” button. Flying an image from one screen to another is called a hero animation in Flutter, though the same motion is sometimes referred to as a shared element transition.
You may want to watch this one-minute video introducing the Hero widget:
This guide demonstrates how to build standard hero animations, and hero animations that transform the image from a circular shape to a square shape during flight.
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, like images, that both routes have in common. From the user’s perspective 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 new route, usually landing at a different location and with a different size.
The following video (recorded at slow speed) shows a typical example. Tapping the flippers in the center of the route flies them to the upper 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 routes its 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, a row of three circular images appears at the bottom of the route. Tapping any of the circular images flies that image to a new route that displays it with a square shape. Tapping the square image flies the hero back to the original route, displayed with a circular shape.
Before moving to the sections specific to standard or radial hero animations, read basic structure of a hero animation to learn how to structure hero animation code, and behind the scenes to understand how Flutter performs a hero animation.
Basic structure of a hero animation
Hero animations are implemented using two Hero widgets: 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, and only the programmer needs to understand this implementation detail.
Hero animation code has the following structure:
- Define a starting Hero widget, referred to as the source hero. The hero specifies its graphical representation (typically an image), and an identifying tag, and is in the 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 with the same tag, typically an object that represents the underlying data. For best results, the heroes should have virtually identical widget trees.
- Create a route that contains the destination hero. The destination route defines the widget tree that exists at the end of the animation.
- Trigger the animation by pushing the destination route on the Navigator’s stack. The Navigator push and pop operations trigger a hero animation for each pair of heroes with matching tags in the source and destination routes.
Flutter calculates the tween that animates the Hero’s bounds from the 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 from one route to another.
Before transition, the source hero waits in the source route’s widget tree. The destination route does not yet exist, and the overlay is 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 the curved motion as described in the Material motion spec. Flutter now knows where the hero ends up.
Places the destination hero in the overlay, at the same location and size as the source hero. Adding a hero to the overlay changes its Z-order so that it appears on top of all routes.
Moves the source hero offscreen.
As the hero flies, its rectangular bounds are animated using Tween<Rect>, specified in Hero’s createRectTween
property. By default, Flutter uses an instance of MaterialRectArcTween, which animates the rectangle’s opposing corners along a curved path. (See Radial hero animations for an example that uses a different Tween animation.)
When the flight completes:
Flutter moves the hero widget from the overlay to the destination route. The overlay is now empty.
The destination hero appears in its final position in the destination route.
The source hero is restored to its route.
Popping the route performs the same process, animating the hero back to its size and location in the source route.
Essential classes
The examples in this guide use the following classes to implement hero animations:
- Hero
- The widget that flies from the source to the destination route. Define one Hero for the source route and another for the destination 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 it to the Navigator’s stack. - Navigator
- The Navigator manages a stack of routes. Pushing a route on or popping 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
What’s going on?
Flying an image from one route to another is easy to implement using Flutter’s hero widget. When using MaterialPageRoute to specify the new route, the image flies along a curved path, as described by the Material Design motion spec.
Create a new Flutter example and update it using the files from the GitHub directory.
To run the example:
- Tap on the home route’s photo to fly the image to a new route showing the same photo at a different location and scale.
- Return to the previous route by tapping the image, or by using the device’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 following widget 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 tap gesture to the both the source and destination heroes. - Defining the Material widget with a transparent color enables the image to “pop out” of the background as it flies to its destination.
- The
SizedBox
specifies the hero’s size at the start and end of the animation. - Setting the Image’s
fit
property toBoxFit.contain
, ensures that the image is as large as possible during the transition without changing its aspect ratio.
HeroAnimation class
The HeroAnimation class creates the source and destination PhotoHeroes, 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 usingMaterialPageRoute
. Pushing the destination route to theNavigator
’s stack triggers the animation. - The
Container
positions thePhotoHero
in the destination route’s top-left corner, below the AppBar. - The
onTap()
method for the destinationPhotoHero
pops theNavigator
’s stack, triggering the animation that flies the Hero back to the original route. - Use the
timeDilation
property to slow the transition while debugging.
Radial hero animations
Flying a hero from one route to another as it transforms from a circular shape to a rectanglar shape is a slick effect that you can implement using Hero widgets. To accomplish this, the code animates the intersection of two clip shapes: a circle and a square. Throughout the animation, the circle clip (and the image) scales from minRadius
to maxRadius
, while the square clip maintains constant size. At the same time, the image flies from its position in the source route to its position in the destination route. For visual examples of this transition, see Radial transformation in the Material motion spec.
This animation might seem complex (and it is), but you can customize the provided example to your needs. The heavy lifting is done for you.
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 clip shapes 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 the ClipRect maintains a constant size. At the end of the transition the intersection of the circular and rectangular clips yield 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.
Create a new Flutter example and update it using the files from the GitHub directory.
To run the example:
- Tap on one of the three circular thumbnails to animate the image to a larger square positioned in the middle of a new route that obscures the original route.
- Return to the previous route by tapping the image, or by using the device’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 theonTap()
function to the Photo’s constructor. - During flight, the InkWell draws its splash on its first Material ancestor.
- The Material widget has a slightly opaque color, so the transparent 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 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 (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) 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
RadialExpansion
widget changes size to match. - The
RadialExpansion
animation is created by two overlapping clips. The example defines the tweening interpolation using MaterialRectCenterArcTween. The default flight path for a hero animation interpolates the tweens using the corners of the heroes. This approach affects the hero’s aspect ratio during the radial transformation, so the new flight path uses
MaterialRectCenterArcTween
to interpolate 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 the Animations tutorial.
- Flutter API documentation
- Reference documentation for all of the Flutter libraries. In particular, see the animation library documentation.
- Flutter Gallery
- Demo app showcasing many Material Design widgets and other Flutter features. The Shrine demo implements a hero animation.
- Material motion spec
- Describes motion for Material design apps.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论