This project is part of the Simple Animations Framework
Stateless Animation enables developers to craft custom animations with simple widgets.
- Create beautiful animations within seconds
- No struggeling with stateful widgets and AnimationControllers
🛈 The following code snippets use supercharged for syntactic sugar.
Add Simple Animations to your project by following the instructions on the install page.
To learn how to use Stateless Animation:
- Continue reading this page
- Study complete examples at example page
- Discover the API documentation
Create your animation by adding the PlayAnimation
widget to your app. It takes two mandatory parameters tween
and builder
.
The tween
is the description of your animation. Mostly it will change a value from A to B. Tweens describe what will happen but not how fast it will happen.
// Animate a color from red to blue
Animatable<Color> myTween = Colors.red.tweenTo(Colors.blue);
The builder
is a function that is called for each new rendered frame of your animation. It takes three parameters: context
, child
and value
.
-
context
is your FlutterBuildContext
, which should be familiar to you. -
child
is a placeholder for any widget that you can additionally pass in aPlayAnimation
widget. It's usage is described further below. -
value
is "current value" of any animated variable. If your tween describes to interpolate from0
to100
, thevariable
is a value somewhere between0
and100
.
How often your builder
function is called, depends on the animation duration and the framerate of the device used.
The PlayAnimation<?>
widget can be typed with the type of the animated variable. This enables us the code type-safe.
PlayAnimation<Color>( // <-- specify type of animated variable
tween: Colors.red.tweenTo(Colors.blue), // <-- define tween of colors
builder: (context, child, value) { // <-- builder function
return Container(
color: value, // <-- use animated value
width: 100,
height: 100
);
});
This snippet creates animation of a red square. It's color will fade to blue within one second.
By default the duration of the animation is one second. You set the optional parameter duration
to refine that.
PlayAnimation<Color>(
tween: Colors.red.tweenTo(Colors.blue),
builder: (context, child, value) {
return Container(color: value, width: 100, height: 100);
},
duration: 5.seconds, // <-- specify duration
);
Now the red square will fade it's color for 5 seconds.
By default animations will play automatically. You can set the delay
parameter to make PlayAnimation
wait for a given amount of time.
PlayAnimation<Color>(
tween: Colors.red.tweenTo(Colors.blue),
builder: (context, child, value) {
return Container(color: value, width: 100, height: 100);
},
duration: 5.seconds,
delay: 2.seconds, // <-- add delay
);
The red square will wait for 2 seconds before it starts fading it's color.
You can make your animation more interesting by applying a non-linear timing curve to it. By default the tween is animated constantly or linear.
Scenarios where the animation is faster at beginning and slower at the ending are called non-linear animations.
You can enrich your animation with non-linear behavior by supplying a Curve
to the curve
parameter. Flutter comes with a set of predefined curves inside the Curves
class.
PlayAnimation<Color>(
tween: Colors.red.tweenTo(Colors.blue),
curve: Curves.easeInOut, // <-- specify curve
builder: (context, child, value) {
return Container(color: value, width: 100, height: 100);
},
);
Animations are highly demanding because parts of your apps are recomputed many times per second. It's important to keep these computions as low as possible.
Image the following scenario: There is a Container
with a colored background. Inside the Container
is a Text
. Now we want to animate the background color. There is no need to recompute the Text
because the animation only effects the Container
color.
In that scenario we have static Text
widget. Only the Container
need to be update on each frame. We can set the static widget as a child
parameter. In our builder
function we receive that child widget and can use it inside our animated scene. This way the child widget is only computed once.
PlayAnimation<Color>(
tween: Colors.red.tweenTo(Colors.blue),
child: Text("Hello World"), // <-- set child widget
builder: (context, child, value) { // <-- get child passed into builder function
return Container(
child: child, // <-- use child
color: value,
width: 100,
height: 100,
);
},
);
Flutter tends to recycle used widgets. If your app swaps out a PlayAnimation
with another different PlayAnimation
in the same second, it may recycle the first one. This may lead to a strange behavior.
All widgets mentioned here support keys to avoid such strange behavior. If you are not familiar with keys then watch this video.
Beside PlayAnimation
there are two similar widgets LoopAnimation
and MirrorAnimation
.
It's configuration is pretty the same as the PlayAnimation
.
A LoopAnimation
repeatly plays the specified tween
from the start to the end.
LoopAnimation<Color>(
tween: Colors.red.tweenTo(Colors.blue), // <-- mandatory
builder: (context, child, value) { // <-- mandatory
return Container(child: child, color: value, width: 100, height: 100);
},
duration: 5.seconds, // <-- optional
curve: Curves.easeInOut, // <-- optional
child: Text("Hello World"), // <-- optional
);
A MirrorAnimation
repeatly plays the specified tween
from the start to the end, then reverse to the start, then again forward and so on.
MirrorAnimation<Color>(
tween: Colors.red.tweenTo(Colors.blue), // <-- mandatory
builder: (context, child, value) { // <-- mandatory
return Container(child: child, color: value, width: 100, height: 100);
},
duration: 5.seconds, // <-- optional
curve: Curves.easeInOut, // <-- optional
child: Text("Hello World"), // <-- optional
);
Use CustomAnimation
if the animation widgets discussed above aren't sufficient for you use case. Beside all parameters mentioned for PlayAnimation
it allows you actively control the animation.
The control
parameter can be set to the following values:
CustomAnimationControl.VALUE | Description |
---|---|
STOP | Stops the animation at the current position. |
PLAY | Plays the animation from the current position reverse to the start. |
PLAY_REVERSE | Plays the animation from the current position reverse to the start. |
PLAY_FROM_START | Reset the position of the animation to 0.0 and starts playing to the end. |
PLAY_REVERSE_FROM_END | Reset the position of the animation to 1.0 and starts playing reverse to the start. |
LOOP | Endlessly plays the animation from the start to the end. |
MIRROR | Endlessly plays the animation from the start to the end, then it plays reverse to the start, then forward again and so on. |
You can bind the control
value to state variable and change it during the animation. The CustomAnimation
will adapt to that.
class _PageState extends State<Page> {
CustomAnimationControl control = CustomAnimationControl.PLAY; // <-- state variable
@override
Widget build(BuildContext context) {
return CustomAnimation<double>(
control: control, // <-- bind state variable to parameter
tween: (-100.0).tweenTo(100.0),
builder: (context, child, value) {
return Transform.translate( // <-- animation that moves childs from left to right
offset: Offset(value, 0),
child: child,
);
},
child: MaterialButton( // <-- there is a button
color: Colors.yellow,
child: Text("Swap"),
onPressed: toggleDirection, // <-- clicking button changes animation direction
),
);
}
void toggleDirection() {
setState(() { // toggle between control instructions
control = (control == CustomAnimationControl.PLAY)
? CustomAnimationControl.PLAY_REVERSE
: CustomAnimationControl.PLAY;
});
}
}
Each animation has an internal abstract position. This is a value ranging form 0.0
(start) to 1.0
end.
You can modify the initial position by setting the startPosition
parameter.
CustomAnimation<Color>(
control: CustomAnimationControl.PLAY, // <-- play forward
startPosition: 0.5, // <-- set start position at 50%
duration: 10.seconds, // <-- full duration is 10 seconds
tween: Colors.red.tweenTo(Colors.blue),
builder: (context, child, value) {
return Container(color: value, width: 100, height: 100);
});
This animation will start playing right in the middle of the specified animation and only will animate for 5 seconds.
Behind the scenes there is an AnimationController
processing the animation. CustomAnimation
exposes it's AnimationStatusListener
to enable you to react to finished animations.
You can specify your own listener at the animationStatusListener
parameter.
CustomAnimation<Color>(
tween: Colors.red.tweenTo(Colors.blue),
builder: (context, child, value) {
return Container(color: value, width: 100, height: 100);
},
animationStatusListener: (AnimationStatus status) {
if (status == AnimationStatus.completed) {
print("Animation completed!");
}
},
);