-
Notifications
You must be signed in to change notification settings - Fork 3.5k
docs: Add new 'Animating widgets' how-to guide. #283
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,293 @@ | ||
| --- | ||
| layout: page | ||
| title: Animating Widgets | ||
| sidebar: home_sidebar | ||
| permalink: /animating-widgets/ | ||
| --- | ||
|
|
||
| This guide shows some common ways you can add animation | ||
| effects to your apps using Flutter’s animation system. | ||
|
|
||
| * TOC | ||
| {:toc} | ||
|
|
||
| ## Introduction | ||
|
|
||
| You can use animations to make the user experience on your mobile app feel more | ||
| intuitive, dynamic, and polished. Animations are especially useful when the | ||
| screen changes state, such as when content loads or when new actions become | ||
| available. | ||
|
|
||
| The [`flutter/animation`](https://docs.flutter.io/flutter/animation/animation-library.html) | ||
| package in the Flutter SDK provides APIs to create animation effects in user | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Technically
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. changed to "library". |
||
| interfaces (UIs) that are built on the | ||
| [`StatelessWidget`](https://docs.flutter.io/flutter/widgets/StatelessWidget-class.html) | ||
| and [`StatefulWidget`](https://docs.flutter.io/flutter/widgets/StatefulWidget-class.html) | ||
| classes. | ||
|
|
||
| Animations in Flutter are encapsulated as | ||
| [`Animation`](https://docs.flutter.io/flutter/animation/Animation-class.html) | ||
| objects that contain these key properties: | ||
|
|
||
| + **Value:** The animation object’s typed value represents a data point within | ||
| a range (also called an interpolation). You can define an animation class to | ||
| generate values based on a given functional mapping, such as a linear, curve, | ||
| or step-wise function. | ||
| + **Status:** The animation object’s current status (such as forward, | ||
| backward, completed, and dismissed), as listed by the | ||
| [`AnimationStatus`](https://docs.flutter.io/flutter/animation/AnimationStatus-class.html) | ||
| constants. | ||
|
|
||
| To create an animation effect in your app, you need to write code to do the | ||
| following: | ||
|
|
||
| 1. Attach an animation object to a widget or a listener to detect changes to the | ||
| animation object. | ||
| 2. Specify how the the animation is *interpolated* (that is, how the animation | ||
| objects’s value and status changes over | ||
| time) as it runs. | ||
|
|
||
| Based on the animation object’s properties at a specific frame, Flutter | ||
| dynamically modifies your widget visual appearence, and rebuilds the widget | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. widget's visual appearance
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed. |
||
| tree to produce visual transitions such as sliding, scaling, rotation, height | ||
| or width changes, and fading. | ||
|
|
||
| Flutter's flexible animation model allows you to define interpolations for | ||
| spatial positioning, color gradients, image scaling, and others. You can define | ||
| your own subclass of | ||
| [`Animation`](https://docs.flutter.io/flutter/animation/Animation-class.html) | ||
| or use an animation subclass that the framework provides. | ||
|
|
||
| To learn more about | ||
| using the other key classes in the | ||
| [`flutter/animation`](https://docs.flutter.io/flutter/animation/animation-library.html) | ||
| package, see the [Animations in Flutter](/animations) guide. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. s/package/library/
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed. |
||
|
|
||
| ## Render a basic widget animation | ||
|
|
||
| To render your widget with an animation, set one or more of the widget’s | ||
| members to the animation object, then use the animation’s value to decide how | ||
| to draw your object. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. draw is kind of an unusual case. It's much more common to use the animation's value to decide how to build child widgets. |
||
|
|
||
| If you are animating a stateful widget, you can notify your widget of changes | ||
| to the animation’s values by using the | ||
| [`addListener()`](https://docs.flutter.io/flutter/animation/Animation/addListener.html) | ||
| method. The framework invokes the listener callback whenever the value of the | ||
| animation changes. Typically, you’ll want to call the | ||
| [`setState()`](http://docs.flutter.io/flutter/material/State/setState.html) | ||
| method when the animation value change occurs, to trigger the framework to | ||
| rebuild the widget tree. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rather than doing this, we might want to recommend that people use |
||
|
|
||
| The following code snippet shows how you might create a simple animation | ||
| effect to make an logo image appear to expand. | ||
|
|
||
| ```dart | ||
| import 'package:flutter/material.dart'; | ||
| import 'package:flutter/animation.dart'; | ||
|
|
||
| class LogoApp extends StatefulWidget { | ||
| LogoAppState createState() => new LogoAppState(); | ||
| } | ||
|
|
||
| class LogoAppState extends State<LogoApp> { | ||
| Animation<double> animation; | ||
|
|
||
| initState() { | ||
| AnimationController controller = new AnimationController( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You need the |
||
| duration: const Duration(milliseconds: 5000) | ||
| ); | ||
| animation = new Tween(begin: 0.0, end: 200.0).animate(controller); | ||
| animation.addListener(() { | ||
| setState(() { | ||
| // the state that has changed here is the animation object’s value | ||
| }); | ||
| }); | ||
| controller.forward(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You also need to hold onto the controller in a member variable and call it's |
||
| super.initState(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The typical style is to call |
||
| } | ||
|
|
||
| Widget build(BuildContext context) { | ||
| return new Center( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a good application of Widget build(BuildContext context) {
Animation<double> animation = new Tween(begin: 0.0, end: 200.0).animate(_controller);
return new Container(
padding: const EdgeInsets.symmetric(vertical: 10.0),
child: new AnimatedBuilder(
animation: animation,
builder: (BuildContext context) {
return new Image.asset(
name: 'assets/flutter_logo.png',
width: animation.value,
height: animation.value,
fit: ImageFit.contain
);
}
)
} |
||
| child: new Container( | ||
| margin: new EdgeInsets.symmetric(vertical: 10.0), | ||
| height: animation.value, | ||
| width: animation.value, | ||
| child: new AssetImage( | ||
| name: 'assets/flutter_logo.png', | ||
| fit: ImageFit.contain | ||
| ) | ||
| ) | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| void main() { | ||
| runApp( | ||
| new MaterialApp( | ||
| title: 'My Animation Test', | ||
| routes: <String, WidgetBuilder> { | ||
| '/': (BuildContext context) => new LogoApp() | ||
| } | ||
| ) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can simplify this as: runApp(
new MaterialApp(
title: 'My Animation Test',
home: new LogoApp()
)
);There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why were all of the "new MaterialApp" statements added to the examples? These are not Material Apps and so similar statements were removed in early code reviews of these examples.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, we can skip that and just run |
||
| ); | ||
| } | ||
| ``` | ||
|
|
||
| You can simplify the code snippet above further by using the | ||
| [`AnimatedWidget`](http://docs.flutter.io/flutter/material/AnimatedWidget-class.html) | ||
| class. This class hides the state object and listener you previously had to | ||
| implement to run the animation. | ||
|
|
||
| ```dart | ||
| import 'package:flutter/material.dart'; | ||
| import 'package:flutter/animation.dart'; | ||
|
|
||
| class LogoApp extends AnimatedWidget { | ||
| LogoApp({Key key, Animation<double> animation}) | ||
| : super(key: key, animation: animation); | ||
|
|
||
| Widget build(BuildContext context) { | ||
| return new Center( | ||
| child: new Container( | ||
| margin: new EdgeInsets.symmetric(vertical: 10.0), | ||
| height: animation.value, | ||
| width: animation.value, | ||
| child: new AssetImage( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be |
||
| name: 'assets/flutter_logo.png', | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why were these changed from web-based images to local images? With web-based images, readers can copy and paste the examples and they'll work. With local examples, readers have to create a project with the directory structure exactly right, and get the flutter.yaml file right, none of which is shown here. This was identified during usability testing of the original examples. |
||
| fit: ImageFit.contain | ||
| ) | ||
| ) | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| void main() { | ||
| final AnimationController controller = new AnimationController( | ||
| duration: const Duration(milliseconds: 5000) | ||
| ); | ||
| final animation = new Tween( | ||
| begin: 0.0, | ||
| end: 200.0) | ||
| .animate(controller); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure this is a good example. It's extremely unusual to create an AnimationController inside the I'm not sure it's worth presenting these two examples. Instead, we should just use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I strongly disagree that it's "simple enough". I originally wrote my animation tutorial because of the amount of effort it took to understand how AnimatedBuilder worked. Unfortunately the AnimatedBuilder example was removed from this document, as was all of the discussion about how AnimatedBuilder worked. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The point of this document is to teach people how the animations work, not to provide boilerplate to copy and paste. I sounds like the document should discourage readers from using the low level calls, which is fine, but (IMO) understanding how it works under the covers was key to building up to how AnimatedBuilder works.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That makes sense. However, we should be aware that people are going to copy-and-paste code samples. If we're going to show people code that we don't want them to copy (e.g., missing dispose calls and starting AnimationControllers in main), then we should make it more obvious that these are code snippets and not complete executable samples. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This code was written last January and things have changed since then, so, instead of making them snippets, I think we should do your proposed changes to add dispose() calls and take the AnimationControllers out of main(). Even if we are showing low level calls, we should still show the proper way to use them. |
||
| runApp( | ||
| new MaterialApp( | ||
| title: 'My Animation Test', | ||
| routes: <String, WidgetBuilder> { | ||
| '/': (BuildContext context) => new LogoApp( | ||
| animation: animation | ||
| ) | ||
| } | ||
| ) | ||
| ); | ||
| controller.forward(); | ||
| } | ||
| ``` | ||
|
|
||
| For more complex animations involving additional state, consider using the | ||
| [`AnimatedBuilder`](http://docs.flutter.io/flutter/material/AnimatedBuilder-class.html) | ||
| class. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd skip this sentence. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I disagree with this comment for the intended audience of this document. The sentence is to help direct people with their progression of learning. |
||
|
|
||
| ## Render simultaneous widget animations | ||
|
|
||
| The following example shows how you can apply two simultaneous animation | ||
| effects: one that controls the size of the widget and another that controls | ||
| the opacity to fade the widget in and out. | ||
|
|
||
| You can use the | ||
| [`addStatusListener()`](http://docs.flutter.io/flutter/animation/Animation/addStatusListener.html) | ||
| method to notify your widget when the current status of an animation changes. | ||
| In this example, we use the status listener to switch the animation’s playback | ||
| between going forward or reverse. | ||
|
|
||
| ```dart | ||
| import 'package:flutter/material.dart'; | ||
| import 'package:flutter/animation.dart'; | ||
|
|
||
| class LogoApp extends AnimatedWidget { | ||
|
|
||
| // Declare the Tween objects as static because we don't change them. | ||
| static final _opacityTween = new Tween<double>(begin: 1.0, end: 0.3); | ||
| static final _sizeTween = new Tween<double>(begin: 0.0, end: 300.0); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We would normally declare these at the library scope. |
||
|
|
||
| LogoApp({Key key, Animation<double> animation}) | ||
| : super(key: key, animation: animation); | ||
|
|
||
| Widget build(BuildContext context) { | ||
| return new Center( | ||
| child: new Opacity( | ||
| opacity: _opacityTween.evaluate(animation), | ||
| child: new Container( | ||
| margin: new EdgeInsets.symmetric(vertical: 10.0), | ||
| height: _sizeTween.evaluate(animation), | ||
| width: _sizeTween.evaluate(animation), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Better to evaluate once and re-use the value than to call |
||
| child: new AssetImage( | ||
| name: 'assets/flutter_logo.png', | ||
| fit: ImageFit.contain | ||
| ) | ||
| ) | ||
| ) | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| void main() { | ||
| final AnimationController controller = new AnimationController( | ||
| duration: const Duration(milliseconds: 5000) | ||
| ); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, this pattern of creating an AnimationController is |
||
|
|
||
| final Animation<double> animation = | ||
| new CurvedAnimation(parent: controller, curve: Curves.bounceIn); | ||
|
|
||
| animation.addStatusListener((status) { | ||
| if (status == AnimationStatus.completed) | ||
| controller.reverse(); | ||
| else if (status == AnimationStatus.dismissed) | ||
| controller.forward(); | ||
| }); | ||
|
|
||
| runApp( | ||
| new MaterialApp( | ||
| title: 'My Animation Test', | ||
| routes: <String, WidgetBuilder> { | ||
| '/': (BuildContext context) => new LogoApp( | ||
| animation: animation | ||
| ) | ||
| } | ||
| ) | ||
| ); | ||
| controller.forward(); | ||
| } | ||
| ``` | ||
|
|
||
| ## Dispose your widget animation | ||
|
|
||
| It’s good practice to dispose your animation controller and free up your | ||
| resources when they are no longer needed. To do so, call the | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's more than a good practice. It's required for your code to be correct and you'll now get an assert if you don't do it. |
||
| [`dispose()`](http://docs.flutter.io/flutter/material/AnimationController/dispose.html) | ||
| method on the | ||
| [`AnimationController`](http://docs.flutter.io/flutter/material/AnimationController-class.html) | ||
| object for the animation object you want to free. This stops the animation from | ||
| running. | ||
|
|
||
| The following snippet shows how to call the animation controller’s | ||
| [`dispose()`](http://docs.flutter.io/flutter/material/AnimationController/dispose.html) | ||
| method from within a | ||
| [`State`](http://docs.flutter.io/flutter/material/State-class.html) object’s | ||
| [`dispose()`](http://docs.flutter.io/flutter/material/State/dispose.html) method | ||
| implementation. The framework typically invokes the | ||
| [`State`](http://docs.flutter.io/flutter/material/State-class.html) | ||
| object’s [`dispose()`](http://docs.flutter.io/flutter/material/State/dispose.html) | ||
| method when switching between screens, as directed by the routes property. | ||
|
|
||
| ```dart | ||
| class LogoAppState extends State<LogoApp> { | ||
|
|
||
| // Implementation code omitted for brevity. | ||
|
|
||
| @override | ||
| void dispose() { | ||
| controller.dispose(); | ||
| super.dispose(); | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should be doing this for all the examples throughout. It's not really optional. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 |
||
| } | ||
| ``` | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably the title and URL should match.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The titles in the sidebar (left-nav) are shortened by design, to make them easier to scan. The full title ('Animating Widgets') is shown when users open the link to that page.