Animation cơ bản trong Flutter
1. Implicit Animations
Flutter cung cấp các Animated widgets dễ sử dụng:
AnimatedContainer(
duration: Duration(milliseconds: 300),
width: _isExpanded ? 200 : 100,
height: _isExpanded ? 200 : 100,
color: _isExpanded ? Colors.blue : Colors.red,
child: Text('Animated'),
)Chỉ cần thay đổi giá trị và widget tự animate!
2. Các Animated Widgets phổ biến
| Widget | Animate |
|---|---|
AnimatedContainer | Size, color, padding, margin, decoration |
AnimatedOpacity | Opacity |
AnimatedPadding | Padding |
AnimatedPositioned | Position trong Stack |
AnimatedAlign | Alignment |
AnimatedDefaultTextStyle | Text style |
AnimatedSwitcher | Widget switching |
AnimatedCrossFade | Fade giữa 2 widgets |
3. AnimatedOpacity
AnimatedOpacity(
duration: Duration(milliseconds: 500),
opacity: _isVisible ? 1.0 : 0.0,
child: Container(
width: 100,
height: 100,
color: Colors.blue,
),
)4. AnimatedPositioned
Trong Stack:
Stack(
children: [
AnimatedPositioned(
duration: Duration(milliseconds: 300),
left: _isLeft ? 0 : 200,
top: _isTop ? 0 : 200,
child: Container(width: 50, height: 50, color: Colors.red),
),
],
)5. AnimatedSwitcher
Animate khi thay đổi widget:
AnimatedSwitcher(
duration: Duration(milliseconds: 300),
child: Text(
'$_count',
key: ValueKey<int>(_count), // Key quan trọng!
),
)Custom transition
AnimatedSwitcher(
duration: Duration(milliseconds: 300),
transitionBuilder: (child, animation) {
return ScaleTransition(scale: animation, child: child);
},
child: _currentWidget,
)6. AnimatedCrossFade
Fade giữa 2 widgets:
AnimatedCrossFade(
duration: Duration(milliseconds: 300),
crossFadeState: _showFirst
? CrossFadeState.showFirst
: CrossFadeState.showSecond,
firstChild: Icon(Icons.play_arrow, size: 50),
secondChild: Icon(Icons.pause, size: 50),
)7. Curves
Điều chỉnh acceleration của animation:
AnimatedContainer(
duration: Duration(milliseconds: 500),
curve: Curves.easeInOut, // Smooth start and end
// curve: Curves.bounceOut, // Bounce effect
// curve: Curves.elasticOut, // Elastic effect
width: _isExpanded ? 200 : 100,
)| Curve | Description |
|---|---|
linear | Đều |
easeIn | Chậm → nhanh |
easeOut | Nhanh → chậm |
easeInOut | Chậm → nhanh → chậm |
bounceOut | Nảy ở cuối |
elasticOut | Đàn hồi |
8. TweenAnimationBuilder
Animation tùy chỉnh với giá trị bất kỳ:
TweenAnimationBuilder<double>(
tween: Tween<double>(begin: 0, end: _targetValue),
duration: Duration(milliseconds: 500),
builder: (context, value, child) {
return Transform.rotate(
angle: value * 2 * 3.14159, // Rotate based on value
child: child,
);
},
child: Icon(Icons.refresh),
)9. Hero Animation
Animate widget khi chuyển screen:
Screen 1
GestureDetector(
onTap: () => Navigator.push(context, MaterialPageRoute(
builder: (_) => DetailScreen(),
)),
child: Hero(
tag: 'avatar', // Same tag on both screens
child: CircleAvatar(radius: 30),
),
)Screen 2
Hero(
tag: 'avatar', // Same tag
child: CircleAvatar(radius: 100),
)10. Ví dụ thực tế
Loading Button
class LoadingButton extends StatefulWidget {
@override
State<LoadingButton> createState() => _LoadingButtonState();
}
class _LoadingButtonState extends State<LoadingButton> {
bool _isLoading = false;
Future<void> _handlePress() async {
setState(() => _isLoading = true);
await Future.delayed(Duration(seconds: 2));
setState(() => _isLoading = false);
}
@override
Widget build(BuildContext context) {
return AnimatedContainer(
duration: Duration(milliseconds: 300),
width: _isLoading ? 50 : 150,
height: 50,
child: ElevatedButton(
onPressed: _isLoading ? null : _handlePress,
child: AnimatedSwitcher(
duration: Duration(milliseconds: 200),
child: _isLoading
? SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
)
: Text('Submit'),
),
),
);
}
}Expandable Card
class ExpandableCard extends StatefulWidget {
@override
State<ExpandableCard> createState() => _ExpandableCardState();
}
class _ExpandableCardState extends State<ExpandableCard> {
bool _isExpanded = false;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => setState(() => _isExpanded = !_isExpanded),
child: AnimatedContainer(
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
width: double.infinity,
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(_isExpanded ? 0.2 : 0.1),
blurRadius: _isExpanded ? 20 : 10,
),
],
),
child: Column(
children: [
Text('Card Title'),
AnimatedCrossFade(
duration: Duration(milliseconds: 300),
crossFadeState: _isExpanded
? CrossFadeState.showSecond
: CrossFadeState.showFirst,
firstChild: SizedBox.shrink(),
secondChild: Padding(
padding: EdgeInsets.only(top: 16),
child: Text('Expanded content here...'),
),
),
],
),
),
);
}
}📝 Tóm tắt
| Type | Khi nào dùng |
|---|---|
Implicit (AnimatedX) | Đơn giản, chỉ cần thay đổi value |
| TweenAnimationBuilder | Custom values |
| Hero | Transition giữa screens |
| Widget | Animate |
|---|---|
AnimatedContainer | Nhiều properties |
AnimatedOpacity | Fade in/out |
AnimatedSwitcher | Thay đổi widget |
AnimatedCrossFade | Fade giữa 2 widgets |
Last updated on