Animation Basics
Overview
Flitter provides the same powerful animation system as Flutter. You can easily implement smooth and natural animations by combining AnimationController, Tween, and Curve.
Why are animations important?
Well-designed animations greatly improve user experience:
- Visual Feedback: Provide immediate response to user actions
- State Transitions: Smoothly express changes in screens or elements
- Attention Guidance: Draw user focus to important information or changes
- Brand Identity: Express app personality through unique animations
- Cognitive Continuity: Naturally show element movement or transformation
Core Concepts
1. AnimationController
The core class that controls animation playback, stopping, repeating, etc.:
class MyWidgetState extends State<MyWidget> {
controller!: AnimationController;
initState(context: BuildContext) {
super.initState(context);
// duration is specified in milliseconds
this.controller = new AnimationController({
duration: 1000 // 1 second
});
}
dispose() {
this.controller.dispose();
super.dispose();
}
}2. Tween
Defines interpolation between start and end values:
// Number Tween
const sizeTween = new Tween({
begin: 50,
end: 200
});
// Color Tween
const colorTween = new ColorTween({
begin: '#3b82f6',
end: '#ef4444'
});
// Offset Tween (position movement)
const positionTween = new Tween({
begin: new Offset(0, 0),
end: new Offset(100, 100)
});3. Animation
Connects Tween and AnimationController to generate actual animation values:
const animation = sizeTween.animated(this.controller);
// Apply easing effects with CurvedAnimation
const curvedAnimation = new CurvedAnimation({
parent: this.controller,
curve: Curves.easeInOut
});
const animation = sizeTween.animated(curvedAnimation);4. Animation Listeners
Add listeners to rebuild widgets whenever animation values change:
class MyWidgetState extends State<MyWidget> {
controller!: AnimationController;
animation!: Animation<number>;
initState(context: BuildContext) {
super.initState(context);
this.controller = new AnimationController({ duration: 1000 });
this.animation = new Tween({ begin: 0, end: 100 }).animated(this.controller);
// Add listener - call setState whenever animation value changes
this.controller.addListener(() => {
this.setState();
});
}
build(context: BuildContext): Widget {
return Container({
width: this.animation.value,
height: this.animation.value,
color: '#3b82f6'
});
}
}Animation Control
// Start animation
this.controller.forward();
// Reverse animation
this.controller.reverse();
// Repeat animation
this.controller.repeat();
// Animate to specific value
this.controller.animateTo(0.5);
// Stop animation
this.controller.stop();
// Reset animation
this.controller.reset();Practical Examples
1. Pulse Animation
class PulseAnimation extends StatefulWidget {
createState() {
return new PulseAnimationState();
}
}
class PulseAnimationState extends State<PulseAnimation> {
controller!: AnimationController;
animation!: Animation<number>;
initState(context: BuildContext) {
super.initState(context);
this.controller = new AnimationController({
duration: 1000 // 1 second
});
this.animation = new Tween({
begin: 1.0,
end: 1.2
}).animated(new CurvedAnimation({
parent: this.controller,
curve: Curves.easeInOut
}));
this.controller.repeat({ reverse: true });
}
dispose() {
this.controller.dispose();
super.dispose();
}
build(context: BuildContext): Widget {
return Transform.scale({
scale: this.animation.value,
child: Container({
width: 100,
height: 100,
decoration: new BoxDecoration({
color: '#3b82f6',
shape: 'circle'
})
})
});
}
}2. Sequential Animation
class SequentialAnimation extends StatefulWidget {
createState() {
return new SequentialAnimationState();
}
}
class SequentialAnimationState extends State<SequentialAnimation> {
controller!: AnimationController;
slideAnimation!: Animation<Offset>;
fadeAnimation!: Animation<number>;
initState(context: BuildContext) {
super.initState(context);
this.controller = new AnimationController({
duration: 2000 // 2 seconds
});
// Slide animation
this.slideAnimation = new Tween({
begin: new Offset(-1, 0),
end: new Offset(0, 0)
}).animated(new CurvedAnimation({
parent: this.controller,
curve: Curves.easeOut
}));
// Fade animation
this.fadeAnimation = new Tween({
begin: 0.0,
end: 1.0
}).animated(new CurvedAnimation({
parent: this.controller,
curve: Curves.easeIn
}));
// Add animation listener
this.controller.addListener(() => {
this.setState();
});
this.controller.forward();
}
dispose() {
this.controller.dispose();
super.dispose();
}
build(context: BuildContext): Widget {
return Transform.translate({
offset: new Offset(
this.slideAnimation.value.dx * 200, // x-axis movement
this.slideAnimation.value.dy * 0 // y-axis movement
),
child: Opacity({
opacity: this.fadeAnimation.value,
child: Container({
width: 200,
height: 100,
color: '#10b981',
child: Center({
child: Text("Sequential Animation", {
style: new TextStyle({
color: '#ffffff',
fontSize: 18
})
})
})
})
})
});
}
}3. Custom Curve Animation
class CustomCurveAnimation extends StatefulWidget {
createState() {
return new CustomCurveAnimationState();
}
}
class CustomCurveAnimationState extends State<CustomCurveAnimation> {
controller!: AnimationController;
bounceAnimation!: Animation<number>;
initState(context: BuildContext) {
super.initState(context);
this.controller = new AnimationController({
duration: 1500 // 1.5 seconds
});
// Custom curve for bounce effect
this.bounceAnimation = new Tween({
begin: 0,
end: 300
}).animated(new CurvedAnimation({
parent: this.controller,
curve: Curves.bounceOut
}));
// Add animation listener
this.controller.addListener(() => {
this.setState();
});
this.controller.forward();
}
dispose() {
this.controller.dispose();
super.dispose();
}
build(context: BuildContext): Widget {
return Transform.translate({
offset: new Offset(this.bounceAnimation.value, 0),
child: Container({
width: 50,
height: 50,
decoration: new BoxDecoration({
color: '#10b981',
shape: 'circle'
})
})
});
}
}Important Notes
1. Performance Optimization
Animations can impact performance, so optimization is important:
// Bad example: Rebuilding entire widget tree
class MyWidgetState extends State<MyWidget> {
build(context: BuildContext): Widget {
return Column({
children: [
ComplexWidget({}), // Recreated every time
Container({
width: this.animation.value,
height: 100
})
]
});
}
}
// Good example: Separate only parts affected by animation
class MyWidgetState extends State<MyWidget> {
complexWidget = ComplexWidget({}); // Created only once
build(context: BuildContext): Widget {
return Column({
children: [
this.complexWidget, // Reused
Container({
width: this.animation.value,
height: 100
})
]
});
}
}2. Memory Management
AnimationController must be disposed:
dispose() {
this.controller.dispose(); // Required!
super.dispose();
}3. AnimationController Setup
Create and manage AnimationController directly in your State class:
class MyState extends State<MyWidget> {
controller!: AnimationController;
initState(context: BuildContext) {
super.initState(context);
this.controller = new AnimationController({
duration: 1000
});
}
dispose() {
this.controller.dispose();
super.dispose();
}
}Built-in Animation Widgets
Flitter provides built-in widgets for commonly used animations:
- AnimatedContainer: Automatic animation when properties change
- AnimatedOpacity: Opacity animation
- AnimatedPositioned: Position animation within Stack
- AnimatedScale: Size animation
- AnimatedRotation: Rotation animation
- AnimatedSlide: Slide animation
These widgets enable simple animations without AnimationController.