上一节我们简单介绍了一下animation的基本使用方法, 算是对flutter的动画有了个初步了解。
从之前的代码中可以看到,要让某个Widget动起来, 或者说要给Widget添加动画, 最少需要满足3个方面:
- 设置控制器(controller)和动画数值变化(animation)区间, 并进行绑定,
- 需要设置监听(listener), 然后在监听里更新数值(setState),
- 将数值(animation.value)绑定到具体的Widget上, Widget自动根据数值变化更新UI
Flutter给了2个办法简化以上流程。
- 1- 使用AnimatedWidget创建一个可重用动画的widget,
- 2- 或者使用AnimatedBuilder从widget中分离出动画过渡。
AnimatedWidget
把需要动起来的Widget改写成AnimatedWidget子类, UI更新的工作就可以丢出去了。改写之前的例子, 让头像Header动起来,如下代码:
import 'package:flutter/material.dart';
class MyAnimatedWidgetDemo extends StatefulWidget {
@override
_MyAnimatedWidgetState createState() => _MyAnimatedWidgetState();
}
class _MyAnimatedWidgetState extends State<MyAnimatedWidgetDemo>
with SingleTickerProviderStateMixin {
AnimationController controller;
Animation animation;
@override
void initState() {
super.initState();
controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: 1000),
);
animation = Tween(begin: 0.0, end: 1.0).animate(controller);
controller.repeat(reverse: true);
}
@override
void dispose() {
controller?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('继承AnimatedWidget助手类')),
body: Center(child: AnimatedHeader(animation: animation)),
);
}
}
class AnimatedHeader extends AnimatedWidget {
AnimatedHeader({Key key, Animation animation})
: super(key: key, listenable: animation);
@override
Widget build(BuildContext context) {
final Animation animation = listenable;
return Container(
width: 100 * (1 + animation.value),
height: 100 * (1 + animation.value),
child: Image.asset('assets/images/head.jpg'),
);
}
}
代码中没有给controller添加数值监听, 而是直接将数值变化(animation)直接传递给了动画组件AnimatedHeader
。AnimatedHeader自行根据animation更新UI。其实工作原理和之前是一样的。
实际效果也和之前一样。
ps:代码中没有对动画状态进行监听, 直接用了repeat方法做循环播放。
controller.repeat(reverse: true);
AnimatedBuilder
上面代码存在的一个问题:AnimatedHeader里的child藏的比较深, 无法推广到普遍适用的范畴。 例如如果需要设计好几个相同动画效果的widget,需要一个一个单独再写一次。
更好的解决方案是将职责分离, 使用AnimatedBuilder可以使动画工具化。
分离child
把头像图片的widget先分离出来
class HeaderProfile extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Image.asset('assets/images/head.jpg');
}
}
分离动画(实现类)
需要采用AnimatedBuilder来实现动画功能分离, 同时需要传入需要渲染的child和渲染方式(animation)。
AnimatedBuilder继承自抽象的AnimationWidget ,目的为了构建通用的AnimationWidget 实现类,不用每次使用AnimationWidget 都要创建一个实现类。
class GrowTransition extends StatelessWidget {
GrowTransition({this.child, this.animation});
final Widget child;
final Animation<double> animation;
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: animation,
builder: (BuildContext context, Widget child) {
return Container(
height: animation.value,
width: animation.value,
child: child,
);
},
child: child,
);
}
}
调用
需要将某个Widget赋予该动画效果时, 直接使用即可。
class MyAnimatedBuilderDemo extends StatefulWidget {
@override
_MyAnimatedBuilderDemoState createState() => _MyAnimatedBuilderDemoState();
}
class _MyAnimatedBuilderDemoState extends State<MyAnimatedBuilderDemo>
with SingleTickerProviderStateMixin {
AnimationController controller;
Animation animation;
@override
void initState() {
super.initState();
controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: 1000),
);
animation = Tween(begin: 100.0, end: 300.0).animate(controller);
controller.repeat(reverse: true);
}
@override
void dispose() {
controller?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('采用AnimatedBuilder助手类')),
body: Center(
child: GrowTransition(
animation: animation,
child: HeaderProfile(),
),
),
);
}
}
效果如下:
系统预制的AnimatedBuilder实现类
Flutter中其实已经预制了不少AnimatedBuilder实现类, 如下所示(估计还会增加)。
具体可以直接使用Flutter的api。
- AlignTransition
- AnimatedAlign
- AnimatedContainer
- AnimatedCrossFade
- AnimatedDefaultTextStyle
- AnimatedIcon
- AnimatedList
- AnimatedModalBarrier
- AnimatedOpacity
- AnimatedPadding
- AnimatedPositioned
- AnimatedPositionedDirectional
- AnimatedSwitcher
- DecoratedBoxTransition
- DefaultTextStyleTransition
- FadeTransition
- PositionedTransition
- RelativePositionedTransition
- RotationTransition
- ScaleTransition
- SizeTransition
- SlideTransition
- TweenAnimationBuilder
以上实现类大致分为2类(以下结论不一定正确, 没有全部测试过),
- 显式动画: 以Transition结尾的类,例如OpacityTransitions,PositionedTransition等。 显式动画指的是需要手动设置动画的时间,运动曲线,取值范围的动画。将值传递给动画部件如: RotationTransition,最后使用一个AnimationController 控制动画的开始和结束。
- 隐式动画: 以Animated开头的类, 例如AnimatedContainer, AnimatedOpacity等, 通过设置动画的起始值和最终值来触发。当使用 setState 方法改变部件的动画属性值时,框架会自动计算出一个从旧值过渡到新值的动画。
下面我们来拉几个实现类做例子
我们用两种方法做一个点击头像慢慢消隐/显现的动画
FadeTransition
import 'package:flutter/material.dart';
class MyFadeTransitionDemo extends StatefulWidget {
@override
_MyFadeTransitionDemoState createState() => _MyFadeTransitionDemoState();
}
class _MyFadeTransitionDemoState extends State<MyFadeTransitionDemo>
with SingleTickerProviderStateMixin {
Duration duration = Duration(milliseconds: 1000);
AnimationController controller;
Animation<double> opacityAnimation;
@override
void initState() {
super.initState();
controller = AnimationController(vsync: this, duration: duration);
opacityAnimation = Tween<double>(begin: 0.2, end: 1.0).animate(controller);
}
@override
void dispose() {
controller?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('点击消隐-FadeTransition组件')),
body: Center(
child: FadeTransition(
opacity: opacityAnimation,
child: InkWell(
onTap: () {
if (controller.status == AnimationStatus.completed) {
controller.reverse();
}
if (controller.status == AnimationStatus.dismissed) {
controller.forward();
}
},
child: Image.asset('assets/images/head.jpg')),
),
),
);
}
}
实现效果如下:
AnimatedOpacity
上代码
import 'package:flutter/material.dart';
class MyAnimatedOpacityDemo extends StatefulWidget {
@override
_MyAnimatedOpacityDemoState createState() => _MyAnimatedOpacityDemoState();
}
class _MyAnimatedOpacityDemoState extends State<MyAnimatedOpacityDemo> {
bool isActive = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('点击消隐-AnimatedOpacity组件')),
body: Center(
child: AnimatedOpacity(
opacity: isActive ? 0.2 : 1.0,
duration: Duration(milliseconds: 1000),
child: InkWell(
onTap: () {
setState(() {
isActive = !isActive;
});
},
child: Image.asset('assets/images/head.jpg')),
),
),
);
}
}
效果和上面是一样的。
要想构建漂亮的用户界面, 动画必不可少, 一些icon的点缀性小动画能极大增强用户体验。
Flutter的动画入门难度比Android低多了, 后面的章节我们再来练习几个动画实例。