flutter第五篇:动画
flutter中的动画主要分为隐式动画、显式动画、自定义隐式动画、自定义显式动画、Hero动画5种。
隐式动画:Animatedxxx
AnimatedContainer,当其decoration、width、height、alignment属性改变时,触发动画。动画执行时间由AnimatedContainer的duration属性指定。如果将duration属性设为3s,那么将AnimatedContainer的width属性由100改到200时,其宽度是逐渐增加的,从100到200要耗时3s。
AnimatedPadding,当其padding值改变时,触发动画。
AnimatedPositioned,当其left、right、top、bottom属性改变时,触发动画。AnimatedPositioned同Positioned一样,只能在Stack中使用。商品加入购物车时,如果需要一个图标飞入购物车的动效,那么就可以尝试使用AnimatedPositioned。
AnimatedOpacity,当其opacity改变时,触发动画。透明度从A到B,耗时duration。
AnimatedDefaultTextStyle,当其style属性改变时,触发动画。可以实现文本缩放时有动效。
AnimatedSwitcher,当其child改变时,触发动画。前后child如果类型相同的话,得给child设置key,否则没有动画效果。默认是FadeTransition效果,即淡入淡出。如果想改成其他效果,则需要设置AnimatedSwitcher的transitionBuilder属性,transitionBuilder是个函数,让其返回xxxTransition,如ScaleTransition(scale: animation, child: child),即缩放动效。如果想实现动效叠加,以transitionBuilder返回ScaleTransition为例,则需要令ScaleTransition的child属性值为另一个xxxTransition,如FadeTransition。
隐式动画,在触发时需要调用setState(),让State的build()方法重新执行。
显式动画:xxxTransition。
RotationTransition,让元素旋转,rotation的意思是旋转。child属性是普通元素,turns属性是Animation<double>,可以使用AnimationController实例,假设是animationController。调用animationController的repeat()方法可以让元素一直旋转,调用forward()方法可以让元素顺时针旋转一圈,调用reverse()方法可以让元素逆时针旋转一圈,调用reset()方法可以让元素重置到初始角度,调用stop()方法可以让元素停止旋转。AnimationController是Animation<double>的子类,任何使用Animation<double>的地方都可以使用AnimationController。AnimationController构造方法必须指定vsync属性,值是一个TickerProvider实例,我们让xxxState with SingleTickerProviderStateMixin,然后传this即可。还必须指定duration属性,值是一个Duration实例,表示动画执行的时间,不传的话,虽然编译时不会报错,但动画执行时会报错。AnimationController有控制动画的方法,其forward()方法可以启动正向动画,reverse()方法可以启动反向动画,repeat()可以重复动画,reset()可以重置动画,stop()可以停止动画。多次调用forward(),只会有一次效果,reverse()同理。执行动画的过程,其实就是animation<double>的value逐渐变化的过程,可以通过给animationController添加监听器,打印animationController.value看出。执行forward()方法,animationController.value是从0渐变到1。执行reverse()方法,animationController.value是从1渐变到0。执行repeat()方法,animationController.value是从0渐变到1,然后突变为0,再从0渐变到1,如此往复。如果想从0渐变到1,然后再从1渐变到0,这样子往复,则需要在调用repeat()时,设置reverse为true。如果不想从0变到1,而是想从0.5变到0.8,那么需要在创建AnimationController实例时指定其lowerBound属性为0.5,upperBound属性为0.8。lowerBound、upperBound可以是任意数值,只要upperBound>=lowerBound即可。如果animationController是多个动画组件共用的,不方便在初始化时就设置上下界,那么也可以在给动画组件的相应属性赋值时,调用其drive()方法,传入一个Tween实例,在创建Tween实例时指定begin和end属性,进而指定animationController的上下界,如animationController.drive(Tween<double>(begin: 0.5, end: 1))。调用Animation<T>实例的drive<U>(Animatable<U> child)方法可获取Animation<U>实例。Animation<U>支持泛型,U的类型取决于Animatable<U>的U的类型。Animatable<T>的子类是Tween,Animatable<double>的子类是CurveTween。Tween<Decoration>的子类是DecorationTween,Tween<Alignment>的子类是Tween<Alignment>。。。还有很多Tween<xxx>的子类。在创建Tween实例时,最好显式指定类型,如Tween<double>(begin: 0, end: 1)。
FadeTransition,改变元素的透明度,fade的意思是淡入、淡出。opacity属性是一个Animation<double>实例,用animationController。
ScaleTransition,对元素进行缩放。scale属性是一个Animation<double>实例,用animationController。
SlideTransition,对元素进行位移。position属性是一个Animation<Offset>实例,可通过调用animationController.drive(Animatable<U> child)方法获取Animation<U>实例。
创建Tween实例时可以指定begin和end属性,begin和end的类型即为T。如想得到Animation<Offset>实例,则只需创建Tween实例时,begin或offset属性为Offset实例,如Tween<Offset>(begin: Offset(0, 0), end: Offset(0.2, 0.2)).animate(controller),或controller.drive(Tween<Offset>(begin: Offset(0, 0), end: Offset(0.2, 0.2)))。第一个0.2表示向右移0.2*(元素的宽度),第二个0.2表示向下移动0.2*(元素的高度)。
AnimatedIcon,改变图标,由一个图标变为另一个图标。progress属性是一个Animation<double>实例,用animationController。icon属性值是个AnimatedIconData实例,在AnimatedIcons类中预置了很多AnimatedIconData实例,可通过对AnimatedIcons打点获得,如AnimatedIcons.play_pause,表示由播放图标变为暂停图标。AnimatedIcons类中预置的AnimatedIconData实例有限,若不能实现需求,如想要实现search图标变为close图标,由于没有定义AnimatedIcons.search_close,这时候就得通过交错式动画实现了。
显式动画,在触发时不需要调用setState()。
交错式动画
交错式动画,指的是一次触发,多个组件依次执行动画。
如我们想实现页面上的一个左箭头icon在0.5s内逐渐消失,之后在0.5s内逐渐出来一个右箭头icon。
可以使用ScaleTransition来实现上述交错式动画。创建两个ScaleTransition,第一个ScaleTransition的child是一个左箭头icon,第二个ScaleTransition的child是一个右箭头icon,两个ScaleTransition共用一个AnimationController实例,假设为controller。左箭头的scale属性设为controller.drive(Tween<double>(begin: 2, end: 0).chain(CurveTween(curve: Interval(0, 0.5)))),即不再是孤零零的controller,而是调用controller的drive()方法得到的新的Animation实例。drive()方法需要传一个Animatable实例,我们用其子类Tween。构造Tween实例时,指定begin属性值为2,表示左箭头一开始的size是icon的size的2倍,指定end值为0,表示左箭头最后缩没了。若要指定动画的执行、结束时间,需要调用Tween实例的chain()方法,得到一个新的Animatable实例,新实例的T的类型与旧实例的T的类型相同。chain()方法固定要传一个Animatable<double>实例,我们用其子类CurveTween。构造CurveTween实例时必须指定curve属性,值是个Curve实例。Curve是个抽象类,我们用其子类Interval。构造Interval实例时,第一个参数指定动画开始执行的时间,第二个参数指定动画执行结束的时间。这两个参数都是小数,真正时间还要乘以Duration指定的时间。即假如Duration指定为10s,第一个参数是0.1,第二个参数是0.2,则其实表示的是动画从第1s开始执行,到第2s执行结束。总结起来,左箭头的scale属性表示左箭头一开始放到icon 指定size的2倍,从触发后立刻开始缩小,到0.5*Duration缩小到消失。同理把右箭头的scale属性设为controller.drive(Tween<double>(begin: 0, end: 2).chain(CurveTween(curve: Interval(0.5, 1)))),表示一开始不显示,从触发后0.5*Duration开始放大,到1*Duration放大到icon指定size的2倍。可以链接多个chain(),如指定执行时间之后,再指定执行速率,animationController.drive(Tween<double>(begin: 0, end: 2).chain(CurveTween(curve: Interval(0.5, 1))).chain(CurveTween(curve: Curves.bounceIn)))
通过上面例子不难看出,其实交错式动画就是利用多个组件共用一个AnimationController,通过合理地给每个组件分配时间区间,让其在一定的时间区间内执行特定的动画,从而实现一连串动画。
在上面讲解AnimatedIcon时提到,要想实现search图标变为close图标,需要通过交错式动画。实现逻辑:创建一个Stack,里面有两个元素,第一个元素是一个xxxTransition,假设是ScaleTransition,其child是一个close图标,第二个元素是一个ScaleTransition,其child是一个search图标。触发动画后,在前50%时间内,让第二个元素由100%缩小到0,在后50%时间内,让第一个元素由0放大到100%。
Stack(
children: [
ScaleTransition(
scale: animationController.drive(Tween<double>(begin: 0, end: 1).chain(CurveTween(curve: Interval(0.5, 1)))),
child: Icon(Icons.close),
),
ScaleTransition(
scale: animationController.drive(Tween<double>(begin: 1, end: 0).chain(CurveTween(curve: Interval(0, 0.5)))),
child: Icon(Icons.search),
)
],
)
具体使用哪个Transition,是根据想实现的效果而定的。如想实现淡入淡出的效果,则需要把ScaleTransition换为FadeTransition。
自定义隐式动画:
使用TweenAnimationBuilder。tween属性赋值为一个Tween实例,当改变Tween实例的end时会触发动画。duration指定动画执行时间,builder属性是Widget Function(BuildContext context, T value, Widget? child),函数第二个参数是Tween实例中指定的从begin变到end的值,我们在构造返回的Widget时要使用这个值,比如把value赋值给组件的width属性或赋值给组件的height属性或赋值给组件的size属性等。在触发动画后,这个值从begin渐变为end,从而实现动画。T的类型取决于Tween实例的begin、end的类型。Tween实例的begin和end的值为具体的值,不是倍数了,如Tween<double>(begin: 10, end: 100),则builder属性的第二个参数的值会从10渐变为100。函数的第三个参数是用于性能优化的,如果我们返回的Widget有child,如Container的child是一个内含两个Text的Column。那么可以把TweenAnimationBuilder的child属性赋值为一个内含两个Text的Column,然后把Container的child属性赋值为builder属性的第三个参数child。这样虽然执行动画过程中builder函数会多次执行,但是Container的child不会多次构造。自定义隐式动画也需要setState()。
示例1、点击浮动按钮后,容器宽度发生变化,且有动效:
TweenAnimationBuilder( tween: Tween<double>(begin: 10, end: b1 ? 10 : 100), duration: const Duration(seconds: 3), builder: (BuildContext context, double value, Widget? child) { return Container( width: value, height: 100, decoration: const BoxDecoration(color: Colors.red), ); })
FloatingActionButton( onPressed: () { setState(() { b1 = !b1; }); }, child: const Icon(Icons.home), )
布尔型变量b1定义在xxxState中,初始值为false。
示例2、点击浮动按钮后,容器背景色发生变化,且有动效:
TweenAnimationBuilder( tween: Tween<double>(begin: 1, end: b2 ? 1 : 0), duration: const Duration(seconds: 3), builder: (BuildContext context, double value, Widget? child) { return Container( width: 100, height: 100, decoration: BoxDecoration(color: Color.fromRGBO(255, 0, 0, value)), ); })
示例3、启用child,减少性能消耗
TweenAnimationBuilder(
tween: Tween<double>(begin: 50, end: b3 ? 50 : 100),
duration: const Duration(seconds: 3),
builder: (BuildContext context, double value, Widget? child) {
return Container(
width: value,
height: 100,
decoration: const BoxDecoration(color: Colors.red),
child: child,
);
},
child: const Column(
children: [Text("1"), Expanded(child: Text("2"))],
),
)
自定义显式动画:
使用AnimatedBuilder。animation属性赋值为一个AnimationController实例,如animationController,builder属性指定一个返回Widget的函数Function(BuildContext context, Widget? child)。在构造Widget时,用到controller.value,或者用到controller.drive(Tween(begin: a, end: b)).value,这个value是动态变化的,在animationController指定的时间内变化完成,从而实现动画效果。builder函数的第二个参数和自定义隐式动画中builder函数的第三个参数用法和作用一样。如我们想改变Container的透明度,则可以用Opacity包裹Container,builder返回Opacity,Opacity的opacity属性值用到controller.value。如我们想让Container进行位移,则可以让Container的transform属性用到controller.value。controller.value的值会在controller的lowerBound和upperBound间变化,前者默认是0.0,后者默认是1.0。
AnimatedBuilder(
animation: animationController,
builder: (BuildContext context, Widget? child) {
return Opacity(
opacity: animationController.drive(Tween<double>(begin: 0.2, end: 0.9)).value,
child: Container(
width: 100,
height: 100,
decoration: const BoxDecoration(color: Colors.red),
));
})
上例,容器的透明度会在0.2-0.9之间变化。
Curve
Curve用来指定动画执行过程是匀速的,还是匀加速的,还是先加速后减速等等,如Curves.linear表示匀速,Curves.ease表示先加速后减速, Curves.easeIn表示先慢后快,Curves.easeOut表示先快后慢。。。



Matrix4
配置组件的transform属性可以让组件的child进行平移、缩放、旋转。值从Matrix4.translationValues(0, 0, 0)变为Matrix4.translationValues(100, 0, 0),则组件会向右平移100。变为Matrix4.translationValues(-100, 0, 0),则组件会向左平移100。变为Matrix4.translationValues(0, 100, 0),则组件会向下平移100。利用这个特性,可以实现让某个组件消失或出现。
hero动画
hero动画指的是组件在页面间飞行,如在A页面上有一张图片,我们想实现【点击此图片,跳到B页面并在B页面上展示此图片,两个页面上的图片可能大小不一样,在B页面上点击空白区域,跳回A页面,且有飞入、飞出动效】,就好像查看微信朋友圈的图片一样,点击某一张图片,会在新页面打开此图片,且有飞出效果。在新页面点击黑色区域,会跳回朋友圈页面,且图片有飞入效果。当把图片九宫格展示在屏幕上半部分或下半部分时,效果尤其明显。
鼠先锋哪个是:
在A页面上用Hero包裹图片,Hero的tag属性设置为一个字符串,在B页面上用Hero包裹图片,Hero的tag属性值和A页面上的Hero的tag属性值一样。
A页面:
InkWell(
child: Hero(
tag: randomStr,
child: Image.network(
imgUrl,
fit: BoxFit.cover,
),
),
onTap: () {
Get.toNamed("/hero", arguments: {"imgUrl": imgUrl, "tag": randomStr});
},
)
randomStr是一串随机字符串,给Hero的tag属性赋值为此随机串,又把此随机串传给了B页面(即/hero对应的页面),用于给B页面的Hero的tag属性赋值。并且把图片地址也传给了B页面。
B页面:
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: InkWell(
onTap: () {
Get.back();
},
splashFactory: NoSplash.splashFactory,
highlightColor: Colors.transparent,
child: Center(
child: Hero(
tag: Get.arguments["tag"],
child: Image.network(
Get.arguments["imgUrl"],
fit: BoxFit.cover,
)),
),
),
);
}
如此,就实现了hero动画。
如果我们想在hero动画的基础上,实现在B页面上能够用手对图片进行缩放,或者左滑查看下一张图片(右滑查看上一张图片),或者旋转图片,那么就得使用插件了。可以使用photo_view插件,详见flutter第十篇。
浙公网安备 33010602011771号