在为 Widget 添加动画效果的过程中我们不难发现,Animation 仅提供动画的数据,因此我们还需要监听动画执行进度,并在回调中使用 setState 强制刷新界面才能看到动画效果。考虑到这些步骤都是固定的,Flutter 提供了两个类来帮我们简化这一步骤,即 AnimatedWidget 与 AnimatedBuilder。
AnimatedWidget
class WidgetAnimateWidget extends StatefulWidget {
@override
StatecreateState()=>_WidgetAnimateWidgetState();
}
class _WidgetAnimateWidgetState extends Statewith SingleTickerProviderStateMixin {
AnimationController?controller;
late Animation animation;
@override
void initState() {
super.initState();
// 创建动画周期为1秒的AnimationController对象
controller =AnimationController(
vsync:this, duration:const Duration(milliseconds:3000));
final CurvedAnimation curve =CurvedAnimation(
parent:controller!, curve:Curves.easeIn);
// 创建从50到200线性变化的Animation对象
animation =Tween(begin:10.0, end:200.0).animate(curve);
// 启动动画
controller!.repeat(reverse:true);
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home:Scaffold(
body:AnimatedLogo(animation:animation,)
));
}
@override
void dispose() {
// 释放资源
controller!.dispose();
super.dispose();
}
}
class AnimatedLogo extends AnimatedWidget {
AnimatedLogo({Key? key,required Animation animation})
:super(key: key, listenable: animation);
Widget build(BuildContext context) {
Animation animation =listenable as Animation;
return Center(
child:Container(
height:animation.value,
width:animation.value,
child:FlutterLogo(),
),
);
}
}
在 AnimatedLogo 的 build 方法中,我们使用 Animation 的 value 作为 logo 的宽和高。这样做对于简单组件的动画没有任何问题,但如果动画的组件比较复杂,一个更好的解决方案是,将动画和渲染职责分离:logo 作为外部参数传入,只做显示;而尺寸的变化动画则由另一个类去管理。这个分离工作,我们可以借助 AnimatedBuilder 来完成。与 AnimatedWidget 类似,AnimatedBuilder 也会自动监听 Animation 对象的变化,并根据需要将该控件树标记为 dirty 以自动刷新 UI。
class BuilderAnimateWidget extends StatefulWidget {
@override
StatecreateState() {
return _BuilderAnimateState();
}
}
class _BuilderAnimateState extends Statewith SingleTickerProviderStateMixin {
late AnimationController controller;
late Animationanimation;
@override
void initState() {
super.initState();
// 创建动画周期为1秒的AnimationController对象
controller =AnimationController(
vsync:this, duration:const Duration(milliseconds:3000));
final CurvedAnimation curve =CurvedAnimation(
parent:controller, curve:Curves.easeInOut);
// 创建从50到200线性变化的Animation对象
animation =Tween(begin:10.0, end:200.0).animate(curve);
// 启动动画
controller.repeat(reverse:true);
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home:Scaffold(
body:Center(
child:AnimatedBuilder(
animation:animation,
//child:FlutterLogo(),
builder: (context, child) =>Container(
width:animation.value,
height:animation.value,
child:FlutterLogo(),
)
)
)
));
}
@override
void dispose() {
// 释放资源
controller.dispose();
super.dispose();
}
}
hero 动画
通过 Hero,我们可以在两个页面的共享元素之间,做出流畅的页面切换效果。
为了实现共享元素变换,我们需要将这两个组件分别用 Hero 包裹,并同时为它们设置相同的 tag “hero”。
class Page2 extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar:AppBar(title:Text('Page1'),),
body:GestureDetector(
child:Row(children: [
Hero(
tag:'hero',// 设置共享 tag
child:Container(
width:100, height:100,
child:FlutterLogo())
),
Text('点击Logo查看Hero效果')
],),
onTap: () {
Navigator.of(context).push(MaterialPageRoute(builder: (_)=>Page2()));
},
)
);
}
}
class Page2 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar:AppBar(title:Text('Page2'),),
body:Hero(
tag:'hero',// 设置共享 tag
child:Container(
width:300, height:300,
child:FlutterLogo()
))
);
}
}