Flutter动画系列-1
Flutter动画主要分为以下3类
- 隐式动画
- 显示动画
- 其他动画
动画控件的选择:要实现一个动画选择什么样的组件呢?如果要实现一个卡通动画(例如京东下拉刷新动画)就需要用第三方插件(Rive/Flare等)结合美工给我们的素材来实现了,如果是简单一些的手绘动画,可以使用CustomPainter完成你想要的效果,这个后面再讲;接下来主要讲一下隐式动画和显示动画,那这两种动画又分别在哪种场景使用呢?如果要实现一个翻页效果、控件由隐藏变为显示,控件的属性(宽高、颜色、透明度、位置)发生变化,可以用隐式动画;如果动画需要循环播放或者随时可能停止播放,再或者需要多个控件协同动画,就需要借助显示动画中的动画控制器AnimaterController来实现了。
隐式动画
今天这一篇文字主要讲一下隐式动画(或者叫全自动动画),Flutter里面最容易实现的动画,几行代码就能实现一个简单动画,控件名称一般叫做 Animatedxxx,例如AnimatedContainer、AnimatedOpacity、AnimatedPadding、AnimatedCrossFade、AnimatedSwitcher等,如果这些不能满足你的需求,还可以使用TweenAnimationBuilder自制补间动画
1、AnimatedContainer
下面先来简单介绍一下他们的用法,就拿AnimatedContainer为例
appBar: AppBar(
title: Text('动画'),
),
body: Container(
child: Center(
child: AnimatedContainer(
duration: Duration(seconds: 1),
width: _width,
height: 100,
color: Colors.orange,
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: (){
setState(() {
_width+=50;
});
},
child: Icon(Icons.add),
),
)
AnimatedContainer其实跟我们平时用的Container属性基本一样,只不过多了两个属性:duration(动画时长)、curve(默认线性动画),其中duration是必输的,否则会报错,这里我只改了它的宽度,其实Container所有属性都可以用动画来过渡。
AnimatedContainer(
duration: Duration(seconds: 1),
width: _width,
height: _height,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(_change ? 0 : _width/2)),
border: Border.all(color:_change ? Colors.red : Colors.orange, width: _change ? 0 : 20),
gradient: LinearGradient(colors: [Colors.red, Colors.orange]),
boxShadow: [
BoxShadow(spreadRadius: _change ? 0 : 10, blurRadius: _change ? 0 : 15)
]
),
)
我这里也没想到什么好的动画,就随便弄了几个属性供大家参考一下。 上面我们说curve默认是线性(Curves.linear),还可以设置其他属性,这篇文章介绍了curve各个取值的效果
2、AnimatedOpacity:控制显示隐藏
child: Center(
child: AnimatedOpacity(
duration: Duration(seconds: 1),
opacity: _opacity ? 0 : 1.0,
child: Container(
width: 100,
height: 100,
color: Colors.orange,
),
),
),
)
3、AnimatedSwitcher
两个widget切换时的动画
Center(
child: AnimatedSwitcher(
duration: Duration(seconds: 1),
child: _change
? Container(width: 200, height: 200, color: Colors.orange)
: Image.network(
'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fattach.bbs.miui.com%2Fforum%2Fmonth_1011%2F1011250123f7480cd63703c992.jpg&refer=http%3A%2F%2Fattach.bbs.miui.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1620601261&t=a58b0f0f98e0eb74854c304fa118dcbf',
width: 200,
height: 200),
),
),
AnimatedSwitcher比其他几个Animatedxxx Widget多一个属性:transitionBuilder,通过上面我们可以看到,两个Widget切换时是一种隐藏的动画,同时进到源码我们能看到动画的效果是FadeTransition
我们也可以换成自己想要的变换效果:
换成了一种缩放效果,假如说既想要缩放效果,又想要透明度从0到1的效果怎么办呢?这里其实可以嵌套:
有的同学在做Text 内容增加动画的时候可能遇到这种情况,命名我把Text 放在AnimatedSwitcher里面了,text改变的时候没有动画效果
这种情况下就要说一下Flutter的更新机制了,Flutter在每一帧刷新的时候会比对Widget的runtimeType,每次刷新的时候Text的runtimeType是相同的,Flutter认为子元素没有变化,所以状态没有变化,我们可以加一个key,让Flutter知道每次刷新Text 这个Widget是有变化的:
现在Text的动画效果又出来了。 我们继续看AnimatedSwitcher源码,还可以看到一个layoutBuilder属性,从这个名字就能看出来,应该是用来布局child的,默认用了一个Stack 来包裹我们的child,从这里我们也能大概猜到,在动画发生的时候,Flutter为我们创建了一个新的child,在动画切换过程中,新的child和以前的child都是存在的,动画结束后,把以前的child 隐藏了,只留下了新child
那我们参照他的源码写一个自己的布局:
4、AnimatedCrossFade
这个Widget也是切换的时候使用,也是淡入淡出的效果
Center(
child: AnimatedCrossFade(
firstChild: Container(width: 100, height: 100, color: Colors.orange),
secondChild: Image.network('https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fattach.bbs.miui.com%2Fforum%2Fmonth_1011%2F1011250123f7480cd63703c992.jpg&refer=http%3A%2F%2Fattach.bbs.miui.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1620601261&t=a58b0f0f98e0eb74854c304fa118dcbf', width: 100,),
duration: Duration(seconds: 1),
crossFadeState: _change ? CrossFadeState.showFirst : CrossFadeState.showSecond,
),
),
注意
其他的几个Animatedxxx Widget 用法类似,还有一个需要注意的地方就是AnimatedContainer只对自己本身的属性有动画效果,对child是没有动画效果的,什么意思呢,看下面代码:
child: Center(
child: AnimatedContainer(
duration: Duration(seconds: 1),
child: Container(
width: _width,
height: 100,
color: Colors.blue,
),
),
),
)
这种情况下AnimatedContainer多了一个child,并且变化的width加在了child上面,这种情况是没有动画的。
5、TweenAnimationBuilder
TweenAnimationBuilder可以自定义我们想要的动画,我们可以不借助前面讲过的AnimatedContainer/AnimatedPadding 等Widget,直接使用Container/Padding 实现动画,先来看一个简单地动画
TweenAnimationBuilder有几个属性来说一下,duration动画时长,必须要传,不传这个参数会报错,tween英文意思是xxx之间,介于两个值之间,传入的这两个值不仅仅是double,也可以是Color、Offset等,可以设置很多类型,在传参的时候最好指定类型
Tween<double>(begin: 10.0, end: _end)
我上面的代码偷了个懒,没有指定类型,但是假如begin传10,就会报错,因为width是double类型,你传的begin是int类型。builder参数我们应该见的比较多了,一般我们在这里面做一些操作,在这里Flutter每渲染一帧都会把begin 和 end中间的那些值返回给我们,我们拿到这些值赋值给Widget,在上面的代码返回的是从10.0~200.0,然后我们赋给Container的width,就能看到Container宽度从10.0变为200.0了。后面还有个child值,这个child是用来做什么的呢?因为我们在builder中只返回了一个简单的Container,没有使用繁杂的布局,假如我们这里有这样一段代码:
Center(
child: TweenAnimationBuilder(
duration: Duration(seconds: 1),
tween: Tween<double>(begin: 50.0, end: _end),
builder: (BuildContext context, value, child) {
return Container(
width: value,
height: 600,
color: Colors.orange,
child: Container(
padding: EdgeInsets.all(10),
child: Column(
children: List.generate(
30,
(index) => Text(index.toString(),
style: TextStyle(fontSize: 15))))));
},
),
)
执行动画变化的是最外层的Container,里面的Container没有变化,我们就可以把里面的Container拿到外面作为TweenAnimationBuilder的child属性,然后在builder中就会传到child 里面,就是下面这样:
TweenAnimationBuilder(
duration: Duration(seconds: 1),
tween: Tween<double>(begin: 50.0, end: _end),
builder: (BuildContext context, value, child) {
return Container(
width: value,
height: 600,
color: Colors.orange,
child:child
);
},
child: Container(
padding: EdgeInsets.all(10),
child: Column(
children: List.generate(
30,
(index) => Text(index.toString(),
style: TextStyle(fontSize: 15))))),
)
因为builder函数是渲染一帧都会调用,而外面这一层的child是不变的,可以直接拿过来用,这样在一定程度上能节约性能。
最后说一下,我们在builder中使用了Flutter回传的改变后的值,但是我们并没有调用平时用的setState,值就发生了变化,这是因为Flutter内部设置了一个监听,帮我调用了setState。
转载自:https://juejin.cn/post/6949474881796833311