likes
comments
collection
share

Flutter动画系列-1

作者站长头像
站长
· 阅读数 3

Flutter动画主要分为以下3类

  1. 隐式动画
  2. 显示动画
  3. 其他动画

Flutter动画系列-1

动画控件的选择:要实现一个动画选择什么样的组件呢?如果要实现一个卡通动画(例如京东下拉刷新动画)就需要用第三方插件(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),
      ),
    )

Flutter动画系列-1

AnimatedContainer其实跟我们平时用的Container属性基本一样,只不过多了两个属性:duration(动画时长)、curve(默认线性动画),其中duration是必输的,否则会报错,这里我只改了它的宽度,其实Container所有属性都可以用动画来过渡。

Flutter动画系列-1

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:控制显示隐藏

Flutter动画系列-1

        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切换时的动画

Flutter动画系列-1

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 Flutter动画系列-1

Flutter动画系列-1

我们也可以换成自己想要的变换效果:

Flutter动画系列-1

Flutter动画系列-1

换成了一种缩放效果,假如说既想要缩放效果,又想要透明度从0到1的效果怎么办呢?这里其实可以嵌套:

Flutter动画系列-1

Flutter动画系列-1

有的同学在做Text 内容增加动画的时候可能遇到这种情况,命名我把Text 放在AnimatedSwitcher里面了,text改变的时候没有动画效果

Flutter动画系列-1

Flutter动画系列-1 这种情况下就要说一下Flutter的更新机制了,Flutter在每一帧刷新的时候会比对Widget的runtimeType,每次刷新的时候Text的runtimeType是相同的,Flutter认为子元素没有变化,所以状态没有变化,我们可以加一个key,让Flutter知道每次刷新Text 这个Widget是有变化的:

Flutter动画系列-1

Flutter动画系列-1

现在Text的动画效果又出来了。 我们继续看AnimatedSwitcher源码,还可以看到一个layoutBuilder属性,从这个名字就能看出来,应该是用来布局child的,默认用了一个Stack 来包裹我们的child,从这里我们也能大概猜到,在动画发生的时候,Flutter为我们创建了一个新的child,在动画切换过程中,新的child和以前的child都是存在的,动画结束后,把以前的child 隐藏了,只留下了新child

Flutter动画系列-1 那我们参照他的源码写一个自己的布局:

Flutter动画系列-1

Flutter动画系列-1

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 实现动画,先来看一个简单地动画

Flutter动画系列-1 Flutter动画系列-1

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
评论
请登录