对于android来说,动画分为属性、帧、补间动画三大类,巧妙的使用动画,能够极大的提升用户的体验。对于flutter来说,作为强大的UI开发工具,也提供了动画的基本实现。
class AnimPageRoute extends StatefulWidget {
@override
State<StatefulWidget> createState() {
// TODO: implement createState
return AnimPageState();
}
}
class AnimPageState extends State<AnimPageRoute>
with SingleTickerProviderStateMixin {
Animation _animation;
AnimationController _controller;
Tween<double> _tween;
@override
void initState() {
// TODO: implement initState
super.initState();
//管理Animation
_controller =
AnimationController(duration: Duration(seconds: 3), vsync: this);
//设置起点和终点
_tween = Tween(begin: 0, end: 300);
//得到动画 CurvedAnimation设置动画曲线
_animation = _tween.animate(
CurvedAnimation(parent: _controller, curve: Curves.fastOutSlowIn));
//必须要setState才能刷新动画
_animation.addListener(() {
setState(() {});
});
//开始执行
_controller.forward();
}
这里一定要设置 _animation.addListener(),在回调中调用setState()方法,才能更新ui。效果如下:
上面每次刷新界面都要调用addListener()回调,这样做很麻烦,所以系统为我们提供了AnimatedWidget类。它其实内部还是调用了这个回调,只是封装了一下,精简源码如下:
class AnimPageState extends State<AnimPageRoute>
with SingleTickerProviderStateMixin {
Animation _animation;
AnimationController _controller;
Tween<double> _tween;
@override
void initState() {
// TODO: implement initState
super.initState();
//管理Animation
_controller =
AnimationController(duration: Duration(seconds: 3), vsync: this);
//设置起点和终点
_tween = Tween(begin: 0, end: 300);
//得到动画 CurvedAnimation设置动画曲线
_animation = _tween.animate(
CurvedAnimation(parent: _controller, curve: Curves.fastOutSlowIn));
//开始执行
_controller.forward();
}
@override
Widget build(BuildContext context) {
return AnimPageWidget(_animation);
}
}
//使用AnimatedWidget
class AnimPageWidget extends AnimatedWidget {
AnimPageWidget(Animation animation) : super(listenable: animation);
@override
Widget build(BuildContext context) {
Animation _animation = listenable;
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text('动画执行AnimPageWidget'),
),
body: Center(
child: SizedBox(
width: _animation.value,
height: _animation.value,
child: Image.asset('assets/images/ic_launcher.png'),
),
));
}
}
class AnimPageState extends State<AnimatedBuilder>
with SingleTickerProviderStateMixin {
Animation _animation;
AnimationController _controller;
Tween<double> _tween;
@override
void initState() {
// TODO: implement initState
super.initState();
//管理Animation
_controller =
AnimationController(duration: Duration(seconds: 3), vsync: this);
//设置起点和终点
_tween = Tween(begin: 0, end: 300);
//得到动画 CurvedAnimation设置动画曲线
_animation = _tween.animate(
CurvedAnimation(parent: _controller, curve: Curves.fastOutSlowIn));
//开始执行
_controller.forward();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('动画执行AnimatedBuilder'),
),
body: AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Center(
child: SizedBox(
width: _animation.value,
height: _animation.value,
child: Image.asset('assets/images/ic_launcher.png'),
),
);
}),
);
}
}
这里实现缩放动画的封装
//缩放动画
class ScaleAnimWidget extends StatelessWidget {
final Animation animation;
final Widget childWidget;
ScaleAnimWidget({@required this.animation, @required this.childWidget});
@override
Widget build(BuildContext context) {
// TODO: implement build
return AnimatedBuilder(
animation: animation,
builder: (context, child) {//这个child就是childWidget
return SizedBox(
width: animation.value,
height: animation.value,
child: child,
);
},
child: childWidget,
);
}
}
所谓的同步动画,就是多个动画效果一起执行,有点动画集的意思。以下代码实现了缩放和透明度的同步效果。
class AnimPageState extends State<AnimPageRoute>
with SingleTickerProviderStateMixin {
Animation _animation;
AnimationController _controller;
Tween<double> _tween;
Tween<double> _opacity;
@override
void initState() {
// TODO: implement initState
super.initState();
//管理Animation
_controller =
AnimationController(duration: Duration(seconds: 3), vsync: this);
//设置起点和终点
_tween = Tween(begin: 0, end: 300);
//透明度变化
_opacity = Tween(begin: 0, end: 1);
//得到动画 CurvedAnimation设置动画曲线
_animation = CurvedAnimation(parent: _controller, curve: Curves.linear);
//开始执行
_controller.forward();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('动画执行AnimatedBuilder封装'),
),
body: AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Center(
child: Opacity(
opacity: _opacity.evaluate(_animation),
child: SizedBox(
width: _tween.evaluate(_animation),
height: _tween.evaluate(_animation),
child: Image.asset('assets/images/ic_launcher.png'),
),
),
);
},
),
);
}
}
_tween控制大小变化,_opacity 控制透明度变化。
路由管理中,页面可以设置切换动画,Material组件中提供了MaterialPageRoute组件。ios提供了CupertinoPageRoute,自定义中使用PageRouteBuilder实现。对于MaterialPageRoute,android页面跳转是从右往左切换界面的,ios是从下往上切换界面的。下面将使用PageRouteBuilder自定义界面切换动画。
class RoutePageA extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text('RoutePageA--pushNamed'),
),
body: Center(
child: RaisedButton(
//异步等待结果回调
onPressed: () {
var result = Navigator.push(context, PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) {
return FadeTransition(
opacity: animation,
child: AnimPageRoute(),
);
}));
print(result);
},
child: Text('跳转到下一界面启动动画'),
),
),
);
}
}
AnimPageRoute()就是下一个界面。
Hero是指在页面之间可以进行飞行的Widget,相当于转场动画,在路由切换时,有一个共同的widget可以在新旧路由间进行切换。代码实现如下:
class RoutePageA extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text('hero动画'),
),
body: Center(
child: InkWell(
//异步等待结果回调
onTap: () {
var result =
Navigator.push(context, MaterialPageRoute(builder: (context) {
return RoutePageB();
}));
print(result);
},
child: Hero(
tag: 'pic',
child: ClipOval(
child: Container(
width: 50,
height: 50,
color: Colors.red,
))),
),
),
);
}
}
class RoutePageB extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text('hero动画'),
),
body: Center(
child: Hero(
tag: 'pic',
child: ClipOval(
child: Container(
width: 100,
height: 100,
color: Colors.red,
))),
));
}
}
A路由到B路由,有一个Hero动画,其中执行动画的widget必须被Hero包裹起来,使用相同的tag。效果如下:
比如我们要想实现一个动画序列,某个时间段执行什么样的动画,这样就可以借助交织动画实现。
class Stagger extends StatelessWidget {
AnimationController controller;
//透明度、宽高动画
Animation<double> opacity, width, height;
//内边距动画
Animation<EdgeInsets> padding;
//圆角动画
Animation<BorderRadius> border;
//颜色渐变动画
Animation<Color> color;
Stagger({Key key, this.controller})
: opacity = Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation(
parent: controller,
curve: Interval(0.00, 0.125, curve: Curves.linear))),
width = Tween(begin: 0.0, end: 100.0).animate(CurvedAnimation(
parent: controller,
curve: Interval(0.125, 0.250, curve: Curves.linear))),
height = Tween(begin: 0.0, end: 100.0).animate(CurvedAnimation(
parent: controller,
curve: Interval(0.250, 0.350, curve: Curves.linear))),
padding = EdgeInsetsTween(
begin: EdgeInsets.only(bottom: 5),
end: EdgeInsets.only(bottom: 15))
.animate(CurvedAnimation(
parent: controller,
curve: Interval(0.350, 0.500, curve: Curves.linear))),
border = BorderRadiusTween(
begin: BorderRadius.circular(5), end: BorderRadius.circular(25))
.animate(CurvedAnimation(
parent: controller,
curve: Interval(0.500, 0.750, curve: Curves.linear))),
color = ColorTween(begin: Colors.blue, end: Colors.orange).animate(
CurvedAnimation(
parent: controller,
curve: Interval(0.750, 1, curve: Curves.linear))),
super(key: key);
@override
Widget build(BuildContext context) {
// TODO: implement build
return AnimatedBuilder(
animation: controller,
builder: (context, child) {
return Container(
padding: padding.value,
alignment: Alignment.bottomCenter,
child: Opacity(
opacity: opacity.value,
child: Container(
width: width.value,
height: height.value,
decoration: BoxDecoration(
color: color.value,
borderRadius: border.value,
border: Border.all(width: 3, color: Colors.blue),
),
),
),
);
},
);
}
}
定义若干动画,并指定每个动画执行的时间。
class StaggerF extends StatefulWidget {
@override
State createState() {
// TODO: implement createState
return StaggerS();
}
}
class StaggerS extends State with TickerProviderStateMixin {
AnimationController _controller;
//开始动画
_startAnim() async {
await _controller.forward().orCancel;
await _controller.reverse().orCancel;
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text('交织动画'),
),
body: GestureDetector(
onTap: () {
_startAnim();
},
child: Center(
child: Container(
width: 300,
height: 300,
color: Colors.green,
child: Stagger(controller: _controller),
),
),
),
);
}
@override
void initState() {
// TODO: implement initState
super.initState();
_controller =
AnimationController(duration: Duration(seconds: 3), vsync: this);
_controller.addStatusListener((status) {
switch (status) {
case AnimationStatus.completed:
_controller.reverse();
break;
case AnimationStatus.dismissed:
_controller.forward();
break;
case AnimationStatus.forward:
case AnimationStatus.reverse:
break;
}
});
_controller.forward();
}
}
监听动画执行状态,循环执行动画。