Flutter 陈航 22-动画 Animation Hero
目录
22 | 如何构造炫酷的动画效果?
Flutter 完全接管了渲染层,除了静态的页面布局之外,对组件动画的支持自然也不在话下。
Animation
- Animation:根据预定规则,在单位时间内持续输出动画的当前状态
- AnimationController:用于管理 Animation,可用来设置动画的时长、启动/暂停、反转动画等
- Listener:是 Animation 的回调函数,用来监听动画的进度变化,进而根据当前值重新渲染组件
Animation 仅提供动画的数据,而不负责动画的渲染,因此我们还需要监听动画执行进度,并在回调中使用 setState 强制刷新界面才能看到动画效果。
案例
class HomePageState extends State<HomePage> with SingleTickerProviderStateMixin {
double _width = 50;
late AnimationController controller;
@override
void initState() {
Duration duration = const Duration(milliseconds: 2000); // 动画周期为 2000 毫秒
controller = AnimationController(vsync: this, duration: duration); // vsync 用于防止出现不可见动画
Animation<double> animation = Tween(begin: 50.0, end: 300.0).animate(controller); // 从 50 到 300 线性变化的动画
animation.addListener(() {
//flog("进度回调 value = ${animation.value}");
setState(() => _width = animation.value);
});
animation.addStatusListener((status) => flog("status = ${status.name}"));
super.initState();
}
void _onPressed() {
flog("duration = ${controller.duration?.inMilliseconds}, status = ${controller.status}, "
"velocity = ${controller.velocity.toStringAsExponential(1)}, animationBehavior = ${controller.animationBehavior}, "
"animating = ${controller.isAnimating}, complete = ${controller.isCompleted}, dismiss = ${controller.isDismissed}");
controller.reset(); // 重置到默认状态
controller.forward().then((value) => flog("动画执行完毕")); //启动动画
}
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: Text(widget.title)),
body: Image.asset("assets/images/monkey.jpg", width: _width),
floatingActionButton: FloatingActionButton(onPressed: _onPressed));
@override
void dispose() {
controller.dispose(); // 释放资源
super.dispose();
}
}
上面在创建 AnimationController 的时候,设置了一个 vsync
属性,设置后会把动画绑定到一个 Widget,当 Widget 不显示时,动画将会暂停,当 Widget 再次显示时,动画会重新恢复执行,这样就可以避免动画组件不可见时消耗资源。
非线性曲线动画
Tween 默认是线性变化的,通过创建 CurvedAnimation 可以实现非线性曲线动画。Curves 提供了很多常用的曲线,比如震荡曲线 elasticOut
:
CurvedAnimation curve = CurvedAnimation(parent: controller, curve: Curves.elasticOut); // 震荡曲线
Animation<double> animation = Tween(begin: 50.0, end: 300.0).animate(curve);
心跳效果
如果想让动画像心跳一样执行,有两个办法:
- 使用
controller.repeat(reverse: true)
启动动画,让动画来回重复执行 - 监听动画状态,在动画结束时,反向执行;在动画反向执行完毕时,重新启动执行
animation.addStatusListener((status) {
if (status == AnimationStatus.completed) {
controller.reverse(); // 动画结束时反向执行
} else if (status == AnimationStatus.dismissed) {
controller.forward(); // 动画反向执行完毕时,重新执行
}
});
controller.forward();
简化动画代码
Flutter 提供了两个类来简化动画代码,即 AnimatedWidget 与 AnimatedBuilder。
AnimatedWidget
AnimatedWidget 会将 Animation 的状态与其子 Widget 的视觉样式绑定,因此,我们只需把 Animation 对象传入 AnimatedWidget 即可,而不用再通过监听动画的执行进度刷新 UI 了。
class AnimatedImage extends AnimatedWidget {
final Animation<double> animation;
const AnimatedImage(this.animation, {super.key}) : super(listenable: animation);
@override
Widget build(BuildContext context) => Image.asset("assets/images/monkey.jpg", width: animation.value);
}
AnimatedBuilder
与 AnimatedWidget 类似,AnimatedBuilder 也会自动监听 Animation 对象的变化,并根据需要将该控件树标记为 dirty 以自动刷新 UI。
AnimatedBuilder 也是继承自 AnimatedWidget 的
AnimatedBuilder(
animation: animation, // 自动监听 Animation 的变化,并通过 builder 重新构建 Widget
builder: (context, child) => Image.asset("assets/images/monkey.jpg", width: animation.value),
)
Hero 动画
共享元素变换 Shared Element Transition:在两个页面的共享元素之间,做出流畅的页面切换效果
Android 原生提供了对这种动画效果的支持,通过几行代码,就可以实现在两个 Activity 共享的组件之间做出流畅的转场动画。Flutter 也有类似的概念,即 Hero 控件。
为了实现共享元素变换,我们需要将这两个组件分别用 Hero 包裹,并同时为它们设置相同的 tag
。
class HomePageState extends State<HomePage> with SingleTickerProviderStateMixin {
void _onPressed() {
Route<HomePage> route = MaterialPageRoute(
builder: (context) => const HomePage(title: "页面2"),
settings: const RouteSettings(arguments: 300.0),
);
Navigator.push(context, route);
}
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: Text(widget.title)),
body: Hero(
tag: 'monkey', // 设置共享 tag
child: Image.asset("assets/images/monkey.jpg", width: getArguments(context)),
),
floatingActionButton: FloatingActionButton(onPressed: _onPressed));
double getArguments(BuildContext context) {
Object? arguments = ModalRoute.of(context)?.settings.arguments;
return arguments is double ? arguments : 50.0;
}
}
总结
在 Flutter 中,动画的状态与渲染是分离的。我们通过 Animation 生成动画曲线,使用 AnimationController 控制动画时间、启动动画。而动画的渲染,则需要 addListener 获取动画进度后,主动刷新才能实现动画的更新。
为了简化这一步骤,Flutter 提供了 AnimatedWidget 和 AnimatedBuilder 这两个组件,省去了状态监听和 UI 刷新的工作。而对于跨页面动画,Flutter 提供了 Hero 组件,只要两个组件有同样的 tag
,就能实现元素跨页面过渡的转场效果。
建议尽量使用 AnimatedWidget 或 AnimatedBuilder 来缩小受动画影响的组件范围,只重绘需要做动画的组件。
2023-1-8
本文来自博客园,作者:白乾涛,转载请注明原文链接:https://www.cnblogs.com/baiqiantao/p/17034775.html