Flutter动画高级技巧
·
Flutter动画高级技巧
1. 核心概念
1.1 动画基础
- Animation:动画对象,管理动画的状态和值
- AnimationController:控制动画的播放、暂停、反转等
- Tween:定义动画的开始和结束值
- Curve:动画曲线,控制动画的缓动效果
- AnimatedBuilder:用于构建动画UI
1.2 动画类型
- 补间动画:从一个值过渡到另一个值
- 物理动画:模拟物理效果的动画
- 交错动画:多个动画按顺序执行
- 页面转场动画:页面之间的过渡效果
- Hero动画:元素在页面之间的过渡效果
2. 高级技巧
2.1 补间动画
class TweenAnimationExample extends StatefulWidget {
@override
_TweenAnimationExampleState createState() => _TweenAnimationExampleState();
}
class _TweenAnimationExampleState extends State<TweenAnimationExample> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
);
_animation = Tween<double>(begin: 0, end: 1).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
)..addStatusListener((status) {
if (status == AnimationStatus.completed) {
_controller.reverse();
} else if (status == AnimationStatus.dismissed) {
_controller.forward();
}
});
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Transform.scale(
scale: 1 + _animation.value * 0.5,
child: Container(
width: 200,
height: 200,
decoration: BoxDecoration(
color: Colors.blue.withOpacity(0.7 + _animation.value * 0.3),
borderRadius: BorderRadius.circular(20 * _animation.value),
),
),
);
},
);
}
}
2.2 物理动画
class PhysicsAnimationExample extends StatefulWidget {
@override
_PhysicsAnimationExampleState createState() => _PhysicsAnimationExampleState();
}
class _PhysicsAnimationExampleState extends State<PhysicsAnimationExample> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<Offset> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
);
final spring = SpringSimulation(
SpringDescription(
mass: 1,
stiffness: 100,
damping: 10,
),
0, // 初始位置
1, // 目标位置
0, // 初始速度
);
_animation = Tween<Offset>(
begin: Offset(0, 0),
end: Offset(1, 0),
).animate(
_controller.drive(CurveTween(curve: Curves.linear)),
);
_controller.animateWith(spring);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Transform.translate(
offset: _animation.value * 200,
child: Container(
width: 100,
height: 100,
color: Colors.red,
),
);
},
);
}
}
2.3 交错动画
class StaggeredAnimationExample extends StatefulWidget {
@override
_StaggeredAnimationExampleState createState() => _StaggeredAnimationExampleState();
}
class _StaggeredAnimationExampleState extends State<StaggeredAnimationExample> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _opacityAnimation;
late Animation<double> _scaleAnimation;
late Animation<Offset> _translateAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
);
_opacityAnimation = Tween<double>(begin: 0, end: 1).animate(
CurvedAnimation(parent: _controller, curve: Interval(0, 0.5)),
);
_scaleAnimation = Tween<double>(begin: 0.5, end: 1).animate(
CurvedAnimation(parent: _controller, curve: Interval(0.3, 0.7)),
);
_translateAnimation = Tween<Offset>(begin: Offset(0, 50), end: Offset(0, 0)).animate(
CurvedAnimation(parent: _controller, curve: Interval(0.5, 1.0)),
);
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Opacity(
opacity: _opacityAnimation.value,
child: Transform.scale(
scale: _scaleAnimation.value,
child: Transform.translate(
offset: _translateAnimation.value,
child: Container(
width: 200,
height: 200,
color: Colors.green,
),
),
),
);
},
);
}
}
2.4 页面转场动画
class PageTransitionExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('页面转场动画')),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
PageRouteBuilder(
transitionDuration: Duration(milliseconds: 500),
pageBuilder: (context, animation, secondaryAnimation) => SecondPage(),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
const begin = Offset(1.0, 0.0);
const end = Offset.zero;
const curve = Curves.easeInOut;
var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
return SlideTransition(
position: animation.drive(tween),
child: child,
);
},
),
);
},
child: Text('跳转到第二页'),
),
),
);
}
}
class SecondPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('第二页')),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('返回'),
),
),
);
}
}
2.5 Hero动画
class HeroAnimationExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Hero动画')),
body: GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 10,
mainAxisSpacing: 10,
),
itemCount: 6,
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailPage(index),
),
);
},
child: Hero(
tag: 'image_$index',
child: Container(
width: 150,
height: 150,
color: Colors.blue[index % 9 * 100],
child: Center(
child: Text(
'Image $index',
style: TextStyle(color: Colors.white, fontSize: 18),
),
),
),
),
);
},
),
);
}
}
class DetailPage extends StatelessWidget {
final int index;
DetailPage(this.index);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('详情页')),
body: Center(
child: Hero(
tag: 'image_$index',
child: Container(
width: 300,
height: 300,
color: Colors.blue[index % 9 * 100],
child: Center(
child: Text(
'Image $index',
style: TextStyle(color: Colors.white, fontSize: 24),
),
),
),
),
),
);
}
}
2.6 自定义动画曲线
class CustomCurve extends Curve {
@override
double transform(double t) {
// 自定义曲线逻辑
return sin(t * pi) * 0.5 + 0.5;
}
}
class CustomCurveExample extends StatefulWidget {
@override
_CustomCurveExampleState createState() => _CustomCurveExampleState();
}
class _CustomCurveExampleState extends State<CustomCurveExample> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
);
_animation = Tween<double>(begin: 0, end: 1).animate(
CurvedAnimation(parent: _controller, curve: CustomCurve()),
);
_controller.repeat(reverse: true);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Transform.rotate(
angle: _animation.value * 2 * pi,
child: Container(
width: 100,
height: 100,
color: Colors.purple,
),
);
},
);
}
}
3. 最佳实践
3.1 性能优化
- 使用const构造器:减少不必要的重建
- 避免在build方法中创建动画:将动画初始化放在initState
- 使用RepaintBoundary:减少重绘区域
- 合理使用动画控制器:及时dispose避免内存泄漏
- 使用硬件加速:启用Flutter的硬件加速
3.2 动画设计
- 保持动画简洁:避免过度复杂的动画
- 使用合适的曲线:根据场景选择合适的动画曲线
- 控制动画时长:根据动画类型设置合理的时长
- 保持一致性:在应用中保持动画风格一致
3.3 可访问性
- 提供动画开关:允许用户关闭动画
- 考虑动画速度:避免过快的动画影响用户体验
- 确保动画不会导致闪烁:避免频繁的重绘
3.4 测试
- 测试动画性能:确保动画流畅
- 测试不同设备:确保在不同设备上的表现一致
- 测试动画边界情况:确保动画在各种情况下都能正常工作
4. 实际应用
4.1 登录按钮动画
class LoginButton extends StatefulWidget {
final VoidCallback onPressed;
const LoginButton({Key? key, required this.onPressed}) : super(key: key);
@override
_LoginButtonState createState() => _LoginButtonState();
}
class _LoginButtonState extends State<LoginButton> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _scaleAnimation;
late Animation<double> _opacityAnimation;
bool _isLoading = false;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 300),
vsync: this,
);
_scaleAnimation = Tween<double>(begin: 1, end: 0.9).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
);
_opacityAnimation = Tween<double>(begin: 1, end: 0).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _handlePress() {
setState(() {
_isLoading = true;
_controller.forward();
});
// 模拟网络请求
Future.delayed(const Duration(seconds: 2), () {
setState(() {
_isLoading = false;
_controller.reverse();
});
widget.onPressed();
});
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: _isLoading ? null : _handlePress,
child: AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Transform.scale(
scale: _scaleAnimation.value,
child: Container(
width: 200,
height: 50,
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(25),
),
child: Stack(
alignment: Alignment.center,
children: [
Opacity(
opacity: _opacityAnimation.value,
child: const Text(
'登录',
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
if (_isLoading)
const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
strokeWidth: 2,
),
),
],
),
),
);
},
),
);
}
}
4.2 列表项动画
class AnimatedListItem extends StatelessWidget {
final Widget child;
final int index;
const AnimatedListItem({Key? key, required this.child, required this.index}) : super(key: key);
@override
Widget build(BuildContext context) {
return FadeTransition(
opacity: AnimationController(
duration: const Duration(milliseconds: 300),
vsync: context.findAncestorStateOfType<SingleTickerProviderStateMixin>()!,
)..forward(),
child: SlideTransition(
position: Tween<Offset>(
begin: const Offset(0, 0.5),
end: Offset.zero,
).animate(
CurvedAnimation(
parent: AnimationController(
duration: const Duration(milliseconds: 300),
vsync: context.findAncestorStateOfType<SingleTickerProviderStateMixin>()!,
)..forward(),
curve: Interval(0, 1, curve: Curves.easeOut),
),
),
child: child,
),
);
}
}
class AnimatedListExample extends StatelessWidget {
final List<String> items = List.generate(20, (index) => 'Item $index');
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return AnimatedListItem(
index: index,
child: ListTile(
title: Text(items[index]),
leading: CircleAvatar(
child: Text('${index + 1}'),
),
),
);
},
);
}
}
5. 总结
Flutter动画的高级技巧包括:
- 补间动画和物理动画
- 交错动画和页面转场动画
- Hero动画和自定义动画曲线
- 性能优化和动画设计
- 可访问性和测试
通过掌握这些技巧,你可以创建出更加流畅、美观的Flutter应用动画,提升用户体验和应用的视觉效果。
更多推荐
所有评论(0)