11_动画系统
Flutter 动画系统和动画组件
Flutter 动画分为隐式动画和显式动画,以及页面间共享元素的 Hero 动画。
隐式动画(推荐首选)
只需改变属性值,Widget 自动播放过渡动画,类似 CSS transition:
class AnimatedCardDemo extends StatefulWidget {
@override
State<AnimatedCardDemo> createState() => _AnimatedCardDemoState();
}
class _AnimatedCardDemoState extends State<AnimatedCardDemo> {
bool _expanded = false;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => setState(() => _expanded = !_expanded),
child: AnimatedContainer(
duration: const Duration(milliseconds: 350),
curve: Curves.easeInOutCubic, // 缓动函数(类比 CSS timing-function)
width: _expanded ? 300 : 150,
height: _expanded ? 200 : 100,
decoration: BoxDecoration(
color: _expanded ? Colors.blue : Colors.grey.shade300,
borderRadius: BorderRadius.circular(_expanded ? 24 : 8),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(_expanded ? 0.3 : 0.1),
blurRadius: _expanded ? 20 : 4,
offset: const Offset(0, 4),
),
],
),
child: Center(
child: Text(
_expanded ? '收起' : '展开',
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
),
),
),
);
}
}
常用隐式动画 Widget 速查:
| Widget | 动画效果 | 类比 CSS |
|---|---|---|
AnimatedContainer |
尺寸、颜色、装饰变化 | transition: all |
AnimatedOpacity |
透明度变化 | transition: opacity |
AnimatedPadding |
内边距变化 | transition: padding |
AnimatedAlign |
对齐位置变化 | — |
AnimatedSwitcher |
子 Widget 切换过渡 | — |
AnimatedDefaultTextStyle |
文字样式变化 | transition: font-* |
TweenAnimationBuilder |
任意值的自定义过渡 | @keyframes |
// AnimatedSwitcher:切换不同 Widget 时自动过渡(key 必须不同!)
AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
transitionBuilder: (child, animation) => FadeTransition(
opacity: animation,
child: child,
),
child: _isLoading
? const CircularProgressIndicator(key: ValueKey('loading'))
: Text('已加载', key: ValueKey('content')),
)
// TweenAnimationBuilder:数字增减动画
TweenAnimationBuilder<double>(
tween: Tween(begin: 0, end: _targetValue),
duration: const Duration(milliseconds: 800),
curve: Curves.easeOut,
builder: (context, value, _) {
return Text(
value.toStringAsFixed(0),
style: const TextStyle(fontSize: 48, fontWeight: FontWeight.bold),
);
},
)
显式动画(精细控制)
需要循环、暂停、精确控制进度时使用 AnimationController:
class PulsingButton extends StatefulWidget {
@override
State<PulsingButton> createState() => _PulsingButtonState();
}
class _PulsingButtonState extends State<PulsingButton>
with SingleTickerProviderStateMixin { // ← mixin 提供 vsync
late AnimationController _controller;
late Animation<double> _scaleAnim;
late Animation<double> _opacityAnim;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 1000),
vsync: this, // vsync 同步帧率,节省资源
);
_scaleAnim = Tween<double>(begin: 1.0, end: 1.2).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
);
_opacityAnim = Tween<double>(begin: 1.0, end: 0.6).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
);
_controller.repeat(reverse: true); // 来回循环
}
@override
void dispose() {
_controller.dispose(); // 必须释放!
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder( // 监听动画值重建 UI
animation: _controller,
builder: (context, child) {
return Transform.scale(
scale: _scaleAnim.value,
child: Opacity(
opacity: _opacityAnim.value,
child: child,
),
);
},
child: ElevatedButton( // child 不随动画重建(静态部分优化)
onPressed: () {},
child: const Text('点击我'),
),
);
}
}
// AnimationController 常用方法
_controller.forward(); // 正向播放(0 → 1)
_controller.reverse(); // 反向播放(1 → 0)
_controller.repeat(); // 正向循环
_controller.repeat(reverse: true); // 来回循环
_controller.stop(); // 暂停
_controller.reset(); // 重置到 0
_controller.value = 0.5; // 跳到指定进度
多动画协调(TweenSequence)
// 分段动画:先放大,再恢复
final scaleAnim = TweenSequence<double>([
TweenSequenceItem(tween: Tween(begin: 1.0, end: 1.5), weight: 50),
TweenSequenceItem(tween: Tween(begin: 1.5, end: 1.0), weight: 50),
]).animate(_controller);
// 多个动画各自指定时间区间(Interval)
final fadeAnim = Tween<double>(begin: 0, end: 1).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0.0, 0.5, curve: Curves.easeIn), // 前 50% 时间
),
);
final slideAnim = Tween<Offset>(begin: const Offset(0, 0.5), end: Offset.zero).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0.3, 1.0, curve: Curves.easeOut), // 30%-100% 时间
),
);
Hero 动画(页面间共享元素)
两个页面间的共享元素过渡,tag 相同的 Widget 会自动产生飞翔效果:
// 列表页
GestureDetector(
onTap: () => Navigator.push(context, MaterialPageRoute(
builder: (_) => ProductDetailPage(product: product),
)),
child: Hero(
tag: 'product_${product.id}', // tag 必须唯一,且两页面相同
child: Image.network(product.imageUrl, width: 80, height: 80, fit: BoxFit.cover),
),
)
// 详情页(tag 保持一致)
Hero(
tag: 'product_${product.id}',
child: Image.network(product.imageUrl, width: double.infinity, height: 300, fit: BoxFit.cover),
)
Curves 缓动函数速查
// 对应 CSS timing-function
Curves.linear → linear
Curves.ease → ease
Curves.easeIn → ease-in
Curves.easeOut → ease-out
Curves.easeInOut → ease-in-out
Curves.easeInOutCubic → cubic-bezier(0.645, 0.045, 0.355, 1.0)(常用)
Curves.fastOutSlowIn → Material Design 标准缓动
Curves.bounceOut → 弹跳效果
Curves.elasticIn → 弹性进入
Curves.decelerate → 减速