Flutter 显式动画
Flutter 中的动画系统基于类型化的 Animation 对象。 Widgets 既可以通过读取当前值和监听状态变化直接合并动画到 build 函数,也可以作为传递给其他 widgets 的更精细动画的基础。
渲染动画
创建
AnimationController
的同时,也赋予了一个 vsync 参数。 vsync 的存在防止后台动画消耗不必要的资源。您可以通过添加SingleTickerProviderStateMixin
到类定义,将有状态的对象用作 vsync
因为
addListener()
函数调用setState()
,所以每次Animation
生成一个新的数字,当前帧就被标记为dirty
,使得build()
再次被调用。在build()
函数中,container
会改变大小,因为它的高和宽都读取animation.value
,而不是固定编码值。当State
对象销毁时要清除控制器以防止内存溢出。
class _AnimateContentState extends State<AnimateContent>
with SingleTickerProviderStateMixin {
bool flag = false;
late AnimationController _controller;
late Animation<double> animation;
@override
void initState() {
// TODO: implement dispose
super.initState();
_controller =
AnimationController(duration: const Duration(seconds: 1), vsync: this);
//animate() 方法会返回一个 Animation
animation = Tween<double>(begin: 0, end: 300).animate(_controller)
..addListener(() {
setState(() {});
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
margin: const EdgeInsets.symmetric(vertical: 10),
width: animation.value,
height: animation.value,
child: const FlutterLogo(),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_controller.forward();
},
child: const Icon(Icons.add),
),
);
}
@override
void dispose() {
// TODO: implement dispose
super.dispose();
}
}
使用 AnimatedWidget 进行简化
- 如何使用 AnimatedWidget 帮助类(代替 addListener() 和 setState())创建动画 widget。
- 利用 AnimatedWidget 创建一个可以运行重复使用动画的 widget。如需区分 widget 过渡,可以使用 AnimatedBuilder,你可以在 使用 AnimatedBuilder 进行重构 部分查阅更多信息。
- Flutter API 中的
AnimatedWidget
:AnimatedBuilder
,RotationTransition
,ScaleTransition
,SizeTransition
,SlideTransition
。
AnimatedWidget
基本类可以从动画代码中区分出核心 widget 代码。 AnimatedWidget
不需要保持 State 对象来 hold 动画。可以添加下面的 AnimatedLogo 类:
class AnimatedLogo extends AnimatedWidget {
const AnimatedLogo({super.key, required Animation<double> animation})
: super(listenable: animation);
@override
Widget build(BuildContext context) {
final animation = listenable as Animation<double>;
return Center(
child: Container(
margin: const EdgeInsets.symmetric(vertical: 10),
height: animation.value,
width: animation.value,
child: const FlutterLogo(),
),
);
}
}
在绘制时,AnimatedLogo
会读取 animation
当前值。
class _AnimateContentState extends State<AnimateContent>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> animation;
@override
void initState() {
// TODO: implement initState
super.initState();
_controller =
AnimationController(duration: const Duration(seconds: 2), vsync: this);
animation = Tween<double>(begin: 0, end: 300).animate(_controller)
..addStatusListener((status) { //监听动画状态
if (status == AnimationStatus.completed) {
_controller.reverse();
} else if (status == AnimationStatus.dismissed) {
_controller.forward();
}
})
..addStatusListener((status) => print('$status'));
_controller.forward();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: AnimatedLogo(
animation: animation,
),
),
);
}
@override
void dispose() {
// TODO: implement dispose
super.dispose();
}
}
使用 AnimatedBuilder 进行重构
- AnimatedBuilder 知道如何渲染过渡效果
- 但
AnimatedBuilder
不会渲染 widget,也不会控制动画对象。- 使用
AnimatedBuilde
r 描述一个动画是其他 widget 构建方法的一部分。如果只是单纯需要用可重复使用的动画定义一个 widget,可参考文档:简单使用 AnimatedWidget。- Flutter API 中
AnimatedBuilders
:BottomSheet
,ExpansionTile
,PopupMenu
,ProgressIndicator
,RefreshIndicator
,Scaffold
,SnackBar
,TabBar
,TextField
。
LogoWidget 组件
// logo组件
class LogoWidget extends StatelessWidget {
const LogoWidget({super.key});
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.symmetric(vertical: 10),
child: const FlutterLogo(),
);
}
}
GrowTransition 组件
GrowTransition
widget 本身是无状态的,而且拥有定义过渡动画所需的一系列最终变量。 build()
函数创建并返回 AnimatedBuilder
, AnimatedBuilder
使用(Anonymous builder)方法并将 LogoWidget
对象作为参数。渲染过渡效果实际上是在(Anonymous builder)方法中完成的,该方法创建一个适当大小 Containe
r 强制 LogoWidget
配合。
class GrowTransition extends StatelessWidget {
const GrowTransition(
{required this.child, required this.animation, super.key});
final Widget child;
final Animation<double> animation;
@override
Widget build(BuildContext context) {
return Center(
child: AnimatedBuilder(
animation: animation,
builder: (context, child) {
return SizedBox(
height: animation.value,
width: animation.value,
child: child,
);
},
child: child,
),
);
}
}
最后, initState()
方法创建了 AnimationController
和 Tween
,然后用 animate()
绑定它们。神奇的是 build()
方法,它返回一个以LogoWidget
为 child
的 GrowTransition
对象,和一个驱动过渡的动画对象。
class AnimateContent extends StatefulWidget {
const AnimateContent({super.key});
@override
State<AnimateContent> createState() => _AnimateContentState();
}
class _AnimateContentState extends State<AnimateContent>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> animation;
@override
void initState() {
// TODO: implement initState
super.initState();
_controller =
AnimationController(duration: const Duration(seconds: 2), vsync: this);
animation = Tween<double>(begin: 0, end: 300).animate(_controller)
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
_controller.reverse();
} else if (status == AnimationStatus.dismissed) {
_controller.forward();
}
})
..addStatusListener((status) => print('$status'));
_controller.forward();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: GrowTransition(
animation: animation,
child: const LogoWidget(),
),
),
);
}
@override
void dispose() {
// TODO: implement dispose
super.dispose();
}
}
同步动画
用CurvedAnimation
对象来控制动画, 修改 AnimatedLogo
来封装Tween
对象,以及其 build()
方法在母动画对象上调用 Tween.evaluate()
来计算所需的尺寸和不透明度值
AnimateLogo 组件
class AnimateLogo extends AnimatedWidget {
const AnimateLogo({super.key, required Animation<double> animation})
: super(listenable: animation);
// Make the Tweens static because they don't change.
static final _opacityTween = Tween<double>(begin: 0.1, end: 1);
static final _sizeTween = Tween<double>(begin: 0, end: 300);
@override
Widget build(BuildContext context) {
final animation = listenable as Animation<double>;
return Center(
child: Opacity(
opacity: _opacityTween.evaluate(animation),// 用Tween.evaluate()方法来计算所需的值
child: Container(
margin: const EdgeInsets.symmetric(vertical: 10),
height: _sizeTween.evaluate(animation),// 用Tween.evaluate()方法来计算所需的值
width: _sizeTween.evaluate(animation),// 用Tween.evaluate()方法来计算所需的值
child: const FlutterLogo(),
),
),
);
}
}
class AnimateContent extends StatefulWidget {
const AnimateContent({super.key});
@override
State<AnimateContent> createState() => _AnimateContentState();
}
class _AnimateContentState extends State<AnimateContent>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> animation;
@override
void initState() {
// TODO: implement initState
super.initState();
_controller =
AnimationController(duration: const Duration(seconds: 2), vsync: this);
// animation = Tween<double>(begin: 0, end: 300).animate(_controller)
// 用CurvedAnimation对象来统一控制动画
animation = CurvedAnimation(parent: _controller, curve: Curves.easeIn)
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
_controller.reverse();
} else if (status == AnimationStatus.dismissed) {
_controller.forward();
}
})
..addStatusListener((status) => print('$status'));
_controller.forward();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: AnimateLogo(
animation: animation,
),
),
);
}
@override
void dispose() {
// TODO: implement dispose
super.dispose();
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了