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'
});
}
}
Code Examples
Basic Animation Implementation
AnimationController Basic Example
Example animating size, color, and rotation simultaneously
코드 보기
// 커스텀 BoxDecoration Tween
class DecorationTween extends Tween<BoxDecoration> {
constructor({ begin, end }: { begin: BoxDecoration; end: BoxDecoration }) {
super({ begin, end });
}
protected lerp(t: number): BoxDecoration {
return BoxDecoration.lerp(this.begin, this.end, t);
}
}
class AnimationExample extends StatefulWidget {
createState() {
return new AnimationExampleState();
}
}
class AnimationExampleState extends State<AnimationExample> {
controller!: AnimationController;
sizeAnimation!: Animation<number>;
decorationAnimation!: Animation<BoxDecoration>;
rotationAnimation!: Animation<number>;
initState(context: BuildContext) {
super.initState(context);
// AnimationController 생성 (duration은 밀리초 단위)
this.controller = new AnimationController({
duration: 2000 // 2초
});
// 크기 애니메이션
this.sizeAnimation = new Tween({
begin: 100,
end: 200
}).animated(new CurvedAnimation({
parent: this.controller,
curve: Curves.easeInOut
}));
// BoxDecoration 애니메이션 (색상 포함)
this.decorationAnimation = new DecorationTween({
begin: new BoxDecoration({
color: '#3b82f6',
shape: 'rectangle',
borderRadius: BorderRadius.circular(16)
}),
end: new BoxDecoration({
color: '#ef4444',
shape: 'rectangle',
borderRadius: BorderRadius.circular(16)
})
}).animated(new CurvedAnimation({
parent: this.controller,
curve: Curves.easeInOut
}));
// 회전 애니메이션
this.rotationAnimation = new Tween({
begin: 0,
end: Math.PI * 2
}).animated(this.controller);
// 애니메이션 리스너 추가
this.controller.addListener(() => {
this.setState();
});
}
dispose() {
this.controller.dispose();
super.dispose();
}
build(context: BuildContext) {
return GestureDetector({
onClick: () => {
if (this.controller.isCompleted) {
this.controller.reverse();
} else {
this.controller.forward();
}
},
child: Transform.rotate({
angle: this.rotationAnimation.value,
child: Container({
width: this.sizeAnimation.value,
height: this.sizeAnimation.value,
decoration: this.decorationAnimation.value,
child: Center({
child: Text(
this.controller.isCompleted ? "뒤로" : "앞으로",
{
style: new TextStyle({
color: '#ffffff',
fontSize: 18,
fontWeight: 'bold'
})
}
)
})
})
})
});
}
}
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. vsync Configuration
You should use SingleTickerProviderStateMixin or TickerProviderStateMixin:
// Flitter does not use mixins
// Create and manage AnimationController directly
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.
Next Steps
Once you’ve mastered animation basics, learn these topics:
- Advanced Animations - Complex animation patterns and optimization
- Custom Painting - Advanced graphics implementation with CustomPaint
- Widget Reference - Detailed guide for built-in animation widgets