Skip to Content
Flutter⚡ Nâng cao✨ Animation cơ bản

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

WidgetAnimate
AnimatedContainerSize, color, padding, margin, decoration
AnimatedOpacityOpacity
AnimatedPaddingPadding
AnimatedPositionedPosition trong Stack
AnimatedAlignAlignment
AnimatedDefaultTextStyleText style
AnimatedSwitcherWidget switching
AnimatedCrossFadeFade 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, )
CurveDescription
linearĐều
easeInChậm → nhanh
easeOutNhanh → chậm
easeInOutChậm → nhanh → chậm
bounceOutNả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

TypeKhi nào dùng
Implicit (AnimatedX)Đơn giản, chỉ cần thay đổi value
TweenAnimationBuilderCustom values
HeroTransition giữa screens
WidgetAnimate
AnimatedContainerNhiều properties
AnimatedOpacityFade in/out
AnimatedSwitcherThay đổi widget
AnimatedCrossFadeFade giữa 2 widgets
Last updated on