Animations
Animations ក្នុង Flutter
Animation ធ្វើឱ្យ UI រស់រវើក និងធ្វើឱ្យ user experience កាន់តែល្អ។ Flutter មាន animation APIs ដែលមានភាពទូលំទូលាយ និងមានប្រសិទ្ធភាពខ្ពស់។
📚 Animation Theory
Animation Pipeline:
Flutter Animation Flow
1️⃣ TICKER (Animation Timer)
- Fires every frame (~60 FPS)
- Provides elapsed time
↓
2️⃣ ANIMATION CONTROLLER
- Controls animation (start, stop, reverse)
- Manages duration & state
↓
3️⃣ TWEEN (Interpolation)
- Defines start & end values
- Calculates intermediate values
↓
4️⃣ ANIMATED BUILDER/WIDGET
- Rebuilds UI with new values
- Displays smooth transition
↓
5️⃣ RENDERED FRAME
- User sees smooth animation
Animation Types in Flutter:
Type | Complexity | Use Case | Example |
---|---|---|---|
Implicit Animations | ⭐ Easy | Simple property changes | AnimatedContainer, AnimatedOpacity |
Explicit Animations | ⭐⭐ Medium | Custom control needed | AnimationController, Tween |
Hero Animations | ⭐ Easy | Shared element transitions | Hero widget |
Physics Animations | ⭐⭐⭐ Advanced | Natural motion | SpringSimulation |
Common Animation Curves:
Curve | Behavior | Best For |
---|---|---|
linear | Constant speed | Loading indicators |
easeIn | Slow start, fast end | Elements leaving screen |
easeOut | Fast start, slow end | Elements entering screen |
easeInOut | Slow start & end | Most UI animations |
bounceIn/Out | Bouncing effect | Playful interactions |
elasticIn/Out | Elastic spring effect | Attention-grabbing |
🎬 Implicit Animations (ងាយបំផុត)
Implicit Animations គឺ automatic animations ដែល Flutter គ្រប់គ្រងឱ្យ។ គ្រាន់តែប្តូរ property, Flutter នឹង animate ដោយស្វ័យប្រវត្តិ។
1. AnimatedContainer
class AnimatedBox extends StatefulWidget {
@override
_AnimatedBoxState createState() => _AnimatedBoxState();
}
class _AnimatedBoxState extends State<AnimatedBox> {
bool isExpanded = false;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
setState(() {
isExpanded = !isExpanded;
});
},
child: AnimatedContainer(
duration: Duration(seconds: 1),
width: isExpanded ? 200 : 100,
height: isExpanded ? 200 : 100,
color: isExpanded ? Colors.blue : Colors.red,
curve: Curves.easeInOut,
),
);
}
}
2. Other Implicit Animations
// AnimatedOpacity - Fade in/out
AnimatedOpacity(
opacity: isVisible ? 1.0 : 0.0,
duration: Duration(milliseconds: 500),
child: Text('Hello'),
)
// AnimatedPositioned - Move widget
AnimatedPositioned(
duration: Duration(seconds: 1),
top: isTop ? 50 : 200,
left: isLeft ? 50 : 200,
child: Container(width: 100, height: 100, color: Colors.blue),
)
// AnimatedDefaultTextStyle - Text style
AnimatedDefaultTextStyle(
duration: Duration(milliseconds: 300),
style: TextStyle(
fontSize: isLarge ? 40 : 20,
color: isRed ? Colors.red : Colors.blue,
),
child: Text('Animated Text'),
)
// AnimatedCrossFade - Switch between two widgets
AnimatedCrossFade(
duration: Duration(milliseconds: 300),
firstChild: Icon(Icons.favorite_border, size: 100),
secondChild: Icon(Icons.favorite, size: 100, color: Colors.red),
crossFadeState: isFavorite
? CrossFadeState.showSecond
: CrossFadeState.showFirst,
)
⚡ Explicit Animations (Full Control)
Explicit Animations ប្រើ AnimationController ដើម្បី control animation manually។
class ExplicitAnimationExample extends StatefulWidget {
@override
_ExplicitAnimationExampleState createState() => _ExplicitAnimationExampleState();
}
class _ExplicitAnimationExampleState extends State<ExplicitAnimationExample>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
// Create controller
_controller = AnimationController(
duration: Duration(seconds: 2),
vsync: this, // Prevents offscreen animations
);
// Create tween animation
_animation = Tween<double>(begin: 0, end: 300).animate(
CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
),
);
// Start animation
_controller.forward();
}
@override
void dispose() {
_controller.dispose(); // Important: clean up!
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Container(
width: _animation.value,
height: _animation.value,
color: Colors.blue,
);
},
);
}
}
Animation Controller Methods:
// Control methods
_controller.forward(); // Play animation forward
_controller.reverse(); // Play animation backward
_controller.repeat(); // Loop animation
_controller.reset(); // Reset to start
_controller.stop(); // Stop animation
// Animation with callbacks
_controller.forward().then((_) {
print('Animation completed!');
});
// Listen to animation
_controller.addListener(() {
print('Value: ' + _controller.value.toString());
});
_controller.addStatusListener((status) {
if (status == AnimationStatus.completed) {
_controller.reverse(); // Reverse when completed
}
});
🎯 Multiple Animations (Staggered)
class StaggeredAnimation extends StatefulWidget {
@override
_StaggeredAnimationState createState() => _StaggeredAnimationState();
}
class _StaggeredAnimationState extends State<StaggeredAnimation>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _opacity;
late Animation<double> _width;
late Animation<double> _height;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(seconds: 2),
vsync: this,
);
// Stagger animations with intervals
_opacity = Tween<double>(begin: 0, end: 1).animate(
CurvedAnimation(
parent: _controller,
curve: Interval(0.0, 0.5, curve: Curves.easeIn),
),
);
_width = Tween<double>(begin: 50, end: 200).animate(
CurvedAnimation(
parent: _controller,
curve: Interval(0.25, 0.75, curve: Curves.easeOut),
),
);
_height = Tween<double>(begin: 50, end: 200).animate(
CurvedAnimation(
parent: _controller,
curve: Interval(0.5, 1.0, curve: Curves.easeInOut),
),
);
_controller.forward();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Opacity(
opacity: _opacity.value,
child: Container(
width: _width.value,
height: _height.value,
color: Colors.purple,
),
);
},
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
🦸 Hero Animations (Shared Element Transitions)
Hero animations ធ្វើឱ្យ widget "ហោះ" ពីមួយ screen ទៅ screen ផ្សេង។
// First Screen
class ListScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (_) => DetailScreen()),
);
},
child: Hero(
tag: 'imageHero', // Must match!
child: Image.network('https://example.com/image.jpg'),
),
),
);
}
}
// Second Screen
class DetailScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Hero(
tag: 'imageHero', // Same tag!
child: Image.network('https://example.com/image.jpg'),
),
),
);
}
}
💫 Page Transitions
// Custom page transition
class FadePageRoute<T> extends PageRoute<T> {
final Widget child;
FadePageRoute({required this.child});
@override
Color? get barrierColor => null;
@override
String? get barrierLabel => null;
@override
Duration get transitionDuration => Duration(milliseconds: 500);
@override
bool get maintainState => true;
@override
Widget buildPage(
BuildContext context,
Animation animation,
Animation secondaryAnimation,
) {
return FadeTransition(
opacity: animation,
child: child,
);
}
}
// Usage
Navigator.push(
context,
FadePageRoute(child: NextPage()),
);
🎨 Transform & Matrix Animations
// Rotation animation
Transform.rotate(
angle: _animation.value * 2 * 3.14159, // Radians
child: Icon(Icons.refresh, size: 100),
)
// Scale animation
Transform.scale(
scale: _animation.value,
child: Container(width: 100, height: 100, color: Colors.green),
)
// Translation animation
Transform.translate(
offset: Offset(_animation.value, 0),
child: Text('Moving Text'),
)
⚡ Performance Tips
// ❌ BAD: Rebuilds entire widget tree
AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return ExpensiveWidget( // Rebuilds every frame!
value: _animation.value,
child: VeryExpensiveChild(),
);
},
)
// ✅ GOOD: Use child parameter
AnimatedBuilder(
animation: _animation,
child: VeryExpensiveChild(), // Built once!
builder: (context, child) {
return ExpensiveWidget(
value: _animation.value,
child: child, // Reuses same child
);
},
)
💡 Best Practices:
- ✅ Use implicit animations for simple cases (easier)
- ✅ Use explicit animations when you need full control
- ✅ Always dispose() AnimationControllers to prevent memory leaks
- ✅ Use const widgets inside AnimatedBuilder child parameter
- ✅ Keep animations subtle (200-500ms usually best)
- ✅ Test animations on real devices (not just emulator)
- ✅ Use Transform instead of changing layout (better performance)
⚠️ Performance Warnings:
- ❌ Too many simultaneous animations (causes jank)
- ❌ Animating expensive widgets without optimization
- ❌ Forgetting to dispose controllers (memory leak)
- ❌ Using animations in ListView items (performance issue)
- ⚠️ Aim for 60 FPS (16ms per frame)
💡 ជំនួយ: AnimatedContainer ងាយប្រើសម្រាប់ animation ធម្មតា។ AnimationController សម្រាប់ animation ស្មុគស្មាញ។ Hero widget សម្រាប់ shared element transitions។