Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion _data/sidebars/home_sidebar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ entries:
- title: Flutter Examples
external_url: https://github.com/flutter/flutter/tree/master/examples
output: web

- title: New to Flutter
output: web
folderitems:
Expand Down Expand Up @@ -98,6 +98,10 @@ entries:
url: /text-input/
output: web

- title: Animate Widgets
url: /animating-widgets/

Copy link
Copy Markdown
Contributor

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.

Copy link
Copy Markdown
Contributor Author

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.

output: web

- title: Helpful Resources
output: web
folderitems:
Expand Down
293 changes: 293 additions & 0 deletions animating-widgets.md
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically animation is a library rather than a package.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

widget's visual appearance

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/package/library/

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than doing this, we might want to recommend that people use AnimatedWidget or AnimatedBuilder.


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(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need the vsync parameter now.

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();

@abarth abarth Sep 30, 2016

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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 dispose method in the State's dispose method. If you don't do this, the animation will continue to run if your widget is removed from the tree, which is a waste of resources and will trigger an asset.

super.initState();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The typical style is to call super.initState first.

}

Widget build(BuildContext context) {
return new Center(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good application of AnimatedBuilder. That will let you skip the animation member variable. You can use a build function like:

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()
}
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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()
  )
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, we can skip that and just run LogoApp directly.

);
}
```

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(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be Image.asset. Also, the width and height parameter should move to the image (it's more efficient). Finally, width should be listed before height.

name: 'assets/flutter_logo.png',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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 main function. Also, now with the vsync parameter, you'll have trouble figuring out which vsync you want to attach your animation to.

I'm not sure it's worth presenting these two examples. Instead, we should just use AnimatedBuilder in the first example. That's the code you'd normally write and it's simple enough to explain in one go.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd skip this sentence. AnimatedBuilder is good for simple animations too.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to evaluate once and re-use the value than to call evaluate twice.

child: new AssetImage(
name: 'assets/flutter_logo.png',
fit: ImageFit.contain
)
)
)
);
}
}

void main() {
final AnimationController controller = new AnimationController(
duration: const Duration(milliseconds: 5000)
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, this pattern of creating an AnimationController is main is extremely unusual and isn't a practice we should recommend. Instead, we should host the AnimationController in a State object so that it can get the proper vsync signal and participate in the rest of the lifecycle.


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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

}
```
Loading