Bỏ qua để đến nội dung

✨ Animation cơ bản

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!


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

AnimatedOpacity(
duration: Duration(milliseconds: 500),
opacity: _isVisible ? 1.0 : 0.0,
child: Container(
width: 100,
height: 100,
color: Colors.blue,
),
)

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),
),
],
)

Animate khi thay đổi widget:

AnimatedSwitcher(
duration: Duration(milliseconds: 300),
child: Text(
'$_count',
key: ValueKey<int>(_count), // Key quan trọng!
),
)
AnimatedSwitcher(
duration: Duration(milliseconds: 300),
transitionBuilder: (child, animation) {
return ScaleTransition(scale: animation, child: child);
},
child: _currentWidget,
)

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),
)

Đ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

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),
)

Animate widget khi chuyển screen:

GestureDetector(
onTap: () => Navigator.push(context, MaterialPageRoute(
builder: (_) => DetailScreen(),
)),
child: Hero(
tag: 'avatar', // Same tag on both screens
child: CircleAvatar(radius: 30),
),
)
Hero(
tag: 'avatar', // Same tag
child: CircleAvatar(radius: 100),
)

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'),
),
),
);
}
}
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...'),
),
),
],
),
),
);
}
}

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