Flutter 进阶 - 动画(Animation)
一、隐式动画
1. AnimatedContainer
定义:
最常用的隐式动画组件,自动处理动画细节,只需改变属性值。
基础用法:
class AnimatedContainerDemo extends StatefulWidget {
_AnimatedContainerDemoState createState() => _AnimatedContainerDemoState();
}
class _AnimatedContainerDemoState extends State<AnimatedContainerDemo> {
bool _isExpanded = false;
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: GestureDetector(
onTap: () {
setState(() {
_isExpanded = !_isExpanded;
});
},
child: AnimatedContainer(
duration: Duration(milliseconds: 500), // 动画时长
curve: Curves.easeInOut, // 动画曲线
width: _isExpanded ? 200 : 100,
height: _isExpanded ? 200 : 100,
color: _isExpanded ? Colors.blue : Colors.red,
alignment: _isExpanded ? Alignment.center : Alignment.topLeft,
child: Text('点击我', style: TextStyle(color: Colors.white)),
),
),
),
);
}
}
实际应用 - 动画卡片:
class AnimatedCard extends StatefulWidget {
_AnimatedCardState createState() => _AnimatedCardState();
}
class _AnimatedCardState extends State<AnimatedCard> {
bool _isExpanded = false;
Widget build(BuildContext context) {
return AnimatedContainer(
duration: Duration(milliseconds: 300),
width: double.infinity,
height: _isExpanded ? 300 : 100,
margin: EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.3),
blurRadius: 10,
offset: Offset(0, 5),
),
],
),
child: Column(
children: [
ListTile(
title: Text('可展开卡片'),
trailing: IconButton(
icon: Icon(_isExpanded ? Icons.expand_less : Icons.expand_more),
onPressed: () {
setState(() {
_isExpanded = !_isExpanded;
});
},
),
),
if (_isExpanded)
Expanded(
child: Padding(
padding: EdgeInsets.all(16),
child: Text('这是展开后的内容...'),
),
),
],
),
);
}
}
2. AnimatedOpacity(透明度动画)
用法:
class OpacityDemo extends StatefulWidget {
_OpacityDemoState createState() => _OpacityDemoState();
}
class _OpacityDemoState extends State<OpacityDemo> {
bool _visible = true;
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
AnimatedOpacity(
opacity: _visible ? 1.0 : 0.0,
duration: Duration(milliseconds: 500),
child: Container(
width: 200,
height: 200,
color: Colors.blue,
child: Center(child: Text('渐变效果')),
),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
setState(() {
_visible = !_visible;
});
},
child: Text(_visible ? '隐藏' : '显示'),
),
],
),
),
);
}
}
3. AnimatedAlign(对齐动画)
用法:
class AlignDemo extends StatefulWidget {
_AlignDemoState createState() => _AlignDemoState();
}
class _AlignDemoState extends State<AlignDemo> {
bool _isLeft = true;
Widget build(BuildContext context) {
return Scaffold(
body: Container(
width: double.infinity,
height: 200,
color: Colors.grey[200],
child: AnimatedAlign(
alignment: _isLeft ? Alignment.centerLeft : Alignment.centerRight,
duration: Duration(milliseconds: 500),
curve: Curves.easeInOut,
child: GestureDetector(
onTap: () {
setState(() {
_isLeft = !_isLeft;
});
},
child: Container(
width: 100,
height: 100,
color: Colors.blue,
child: Center(child: Text('点击移动')),
),
),
),
),
);
}
}
4. AnimatedDefaultTextStyle(文本样式动画)
用法:
class TextStyleDemo extends StatefulWidget {
_TextStyleDemoState createState() => _TextStyleDemoState();
}
class _TextStyleDemoState extends State<TextStyleDemo> {
bool _isBig = false;
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
AnimatedDefaultTextStyle(
duration: Duration(milliseconds: 500),
style: TextStyle(
fontSize: _isBig ? 50 : 20,
color: _isBig ? Colors.blue : Colors.red,
fontWeight: _isBig ? FontWeight.bold : FontWeight.normal,
),
child: Text('动画文本'),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
setState(() {
_isBig = !_isBig;
});
},
child: Text('切换样式'),
),
],
),
),
);
}
}
二、显式动画
5. AnimationController
定义:
手动控制动画过程,更加灵活和强大。
基础示例:
class ExplicitAnimationDemo extends StatefulWidget {
_ExplicitAnimationDemoState createState() => _ExplicitAnimationDemoState();
}
class _ExplicitAnimationDemoState extends State<ExplicitAnimationDemo>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
void initState() {
super.initState();
// 创建动画控制器
_controller = AnimationController(
duration: Duration(seconds: 2),
vsync: this,
);
// 创建动画(从 0 到 300)
_animation = Tween<double>(begin: 0, end: 300).animate(_controller);
// 监听动画状态
_controller.addStatusListener((status) {
if (status == AnimationStatus.completed) {
_controller.reverse(); // 反向播放
} else if (status == AnimationStatus.dismissed) {
_controller.forward(); // 正向播放
}
});
// 开始动画
_controller.forward();
}
void dispose() {
_controller.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Container(
width: _animation.value,
height: _animation.value,
color: Colors.blue,
child: child,
);
},
child: Center(
child: Text('动画', style: TextStyle(color: Colors.white)),
),
),
),
);
}
}
6. 旋转动画
class RotationDemo extends StatefulWidget {
_RotationDemoState createState() => _RotationDemoState();
}
class _RotationDemoState extends State<RotationDemo>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(seconds: 2),
vsync: this,
)..repeat(); // 重复播放
}
void dispose() {
_controller.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RotationTransition(
turns: _controller,
child: Icon(Icons.refresh, size: 100, color: Colors.blue),
),
),
);
}
}
7. 缩放动画
class ScaleDemo extends StatefulWidget {
_ScaleDemoState createState() => _ScaleDemoState();
}
class _ScaleDemoState extends State<ScaleDemo>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(milliseconds: 500),
vsync: this,
);
_animation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _controller, curve: Curves.elasticOut),
);
_controller.forward();
}
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ScaleTransition(
scale: _animation,
child: Container(
width: 200,
height: 200,
color: Colors.blue,
child: Center(child: Text('弹性缩放')),
),
),
),
);
}
void dispose() {
_controller.dispose();
super.dispose();
}
}
三、Hero 动画
8. Hero 基础
定义:
实现页面跳转时的共享元素动画效果。
基础用法:
// 第一个页面
class FirstPage extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('第一页')),
body: Center(
child: GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondPage()),
);
},
child: Hero(
tag: 'hero-image', // 唯一标识
child: Image.network(
'https://picsum.photos/200',
width: 100,
height: 100,
),
),
),
),
);
}
}
// 第二个页面
class SecondPage extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('第二页')),
body: Center(
child: Hero(
tag: 'hero-image', // 相同的标识
child: Image.network(
'https://picsum.photos/200',
width: 300,
height: 300,
),
),
),
);
}
}
9. Hero 实际应用 - 图片详情
// 列表页
class ImageListPage extends StatelessWidget {
final List<String> images = List.generate(
20,
(index) => 'https://picsum.photos/200?random=$index',
);
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('图片列表')),
body: GridView.builder(
padding: EdgeInsets.all(8),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: 8,
mainAxisSpacing: 8,
),
itemCount: images.length,
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ImageDetailPage(
imageUrl: images[index],
tag: 'image-$index',
),
),
);
},
child: Hero(
tag: 'image-$index',
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Image.network(images[index], fit: BoxFit.cover),
),
),
);
},
),
);
}
}
// 详情页
class ImageDetailPage extends StatelessWidget {
final String imageUrl;
final String tag;
const ImageDetailPage({
required this.imageUrl,
required this.tag,
});
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
),
body: Center(
child: Hero(
tag: tag,
child: Image.network(imageUrl),
),
),
);
}
}
四、动画曲线
10. 常用 Curves
// 不同的动画曲线效果
class CurvesDemo extends StatefulWidget {
_CurvesDemoState createState() => _CurvesDemoState();
}
class _CurvesDemoState extends State<CurvesDemo> {
bool _moved = false;
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('动画曲线')),
body: Column(
children: [
_buildAnimatedBox('linear', Curves.linear),
_buildAnimatedBox('easeIn', Curves.easeIn),
_buildAnimatedBox('easeOut', Curves.easeOut),
_buildAnimatedBox('easeInOut', Curves.easeInOut),
_buildAnimatedBox('bounceIn', Curves.bounceIn),
_buildAnimatedBox('elasticOut', Curves.elasticOut),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
setState(() {
_moved = !_moved;
});
},
child: Text('开始动画'),
),
],
),
);
}
Widget _buildAnimatedBox(String label, Curve curve) {
return Container(
height: 50,
margin: EdgeInsets.symmetric(vertical: 4),
child: Row(
children: [
SizedBox(
width: 100,
child: Text(label, style: TextStyle(fontSize: 12)),
),
Expanded(
child: Stack(
children: [
AnimatedAlign(
alignment: _moved ? Alignment.centerRight : Alignment.centerLeft,
duration: Duration(seconds: 1),
curve: curve,
child: Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(20),
),
),
),
],
),
),
],
),
);
}
}
常用曲线:
Curves.linear- 线性Curves.easeIn- 缓慢开始Curves.easeOut- 缓慢结束Curves.easeInOut- 缓慢开始和结束Curves.bounceIn- 弹跳进入Curves.bounceOut- 弹跳退出Curves.elasticIn- 弹性进入Curves.elasticOut- 弹性退出
总结
隐式动画 vs 显式动画
| 特性 | 隐式动画 | 显式动画 |
|---|---|---|
| 使用难度 | 简单 | 较复杂 |
| 灵活性 | 低 | 高 |
| 控制力 | 自动 | 手动 |
| 适用场景 | 简单属性变化 | 复杂动画逻辑 |
| 性能 | 良好 | 需要优化 |
选择建议
- 简单动画:使用隐式动画(AnimatedContainer、AnimatedOpacity 等)
- 复杂动画:使用显式动画(AnimationController)
- 页面过渡:使用 Hero 动画
- 组合动画:结合显式动画和自定义曲线
1846

被折叠的 条评论
为什么被折叠?



