likes
comments
collection
share

Flutter动画系列-2

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

显式动画

显式动画需要借助动画控制器AnimaterController实现动画效果,可以随时开启、停止、循环播放,或者多个控件一起完成一个移动、缩放等效果,实现多种动画效果,这类控件一般命名为xxxTransition,如RotationTransition(旋转)、SlideTransition(位移)、FadeTransition(淡入淡出)等,同样如果自带控件不能满足你的需求,还可以使用AnimatedBuilder完成自己想要的动画,如果隐式动画和显示动画都没能满足你的需求,还可以直接使用CustomPainter使用Canvan绘制完成自己想要的效果

1、RotationTransition

先看一个简单的旋转动画

Flutter动画系列-2

@override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('显式动画'),
      ),
      body: Center(
        child: RotationTransition(
            turns: _controller,
            child: Container(
              width: 200,
              height: 150,
              color: Colors.orange,
            )
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: (){
          _controller.forward();
        },
      ),
    );
  }

我们发现RotationTrasition让我们传一个turns参数,以前我们在介绍AnimatedContainer等简单动画的时候让我们传的是duration动画时间,现在这个turns 是一个什么呢,我在代码里也已经写了,传的是一个controller,就是AnimationController的实例,duration是在这个实例初始化的时候传入,当然也可以在后续需要的时候传入或修改

Flutter动画系列-2 在代码里还可以看到我们传了一个vsync:this 而且我们还混入了一个类:SingleTickerProviderStateMixin,这个vsync是垂直信号同步,进入到SingleTickerProviderStateMixin源码中

Flutter动画系列-2 我们看到Flutter帮我们创建了一个Ticker,从字面意思来解释ticker是钟表,我们知道有些设备是60Hz,有些是90Hz,60Hz的设备是每一秒渲染60次,ticker是每次渲染的回调,controller绑定一个ticker,所以controller也会在设备每一帧回调时去刷新控件,然后controller会为我们在0~1(默认是0~1)之间填充一些值,在builder中我们就可以拿到这些变化的值,完成动画渲染的区间,我们可以添加一个监听来打印controller的值_controller.addListener(() { print(_controller.value); }); ticker还为我们做了一些优化,例如在动画的过程中,我们按home回到设备主屏幕,这时候动画就停止了,当我们再次回到页面的时候动画又重新开始了。需要注意的是controller需要在页面销毁的时候回收,也就是在dispose方法中调用controller的dispose,让动画动起来就调用controller的forward()方法,如果想让动画一直旋转,就调用repeat()方法,reset是重置,回归到最开始的位置,stop是直接停止。 刚才说controller默认动画是从0到1,我们想让他转1/4圈怎么办?有2个属性可以设置lowerBound和upperBound:

_controller = AnimationController(
      lowerBound: 0,
      upperBound: 0.25,
      duration: Duration(seconds: 1),
      vsync: this
    );

如果我们的页面里定义了多个controller,就不能用SingleTickerProviderStateMixin了,需要使用TickerProviderStateMixin,在这种情况下可能运行会慢一些了

2、FadeTransition

FadeTransition淡入淡出跟上面旋转用法基本一样,只不过它现在要传一个opacity不透明度属性,

FadeTransition(
            opacity: _controller,
            child: Container(
              width: 200,
              height: 150,
              color: Colors.orange,
            )
)

3、 ScaleTransition

同理移动动画SlideTransition缩放动画需要传一个position属性,这时候我们发现直接给SlideTransition传它需要的position不行了,因为我们上面说过controller的默认值lowerBound为0,upperBound为1,是double类型,而这里需要的是Animate<Offset>类型,其实上面RotationTransition的turns属性还有一种写法: turns: Tween(begin: 0.0, end: 1.0).animate(_controller), 或者: turns: _controller.drive(Tween(begin: 0.0, end: 1.0)), 好像这两种的第一种在网上更为常见,所以移动动画的position 在这里可以这样写:

SlideTransition(
            position: Tween(begin: Offset(0, 0), end: Offset(0, 0.5)).animate(_controller),
            child: Container(
              width: 200,
              height: 150,
              color: Colors.orange,
            )
)

这里Tween end值传的是Offset(0, 0.5) 这个0.5是自身高度的0.5倍,需要注意一下而不是0.5个单位长度

Flutter动画系列-2

刚才说了turns: Tween(begin: 0.0, end: 1.0).animate(_controller)这种写法在网上比较常见,因为Tween还可以叠加:

SlideTransition(
            position: Tween(begin: Offset(0, 0), end: Offset(0, 1))
            .chain(CurveTween(curve: Curves.easeIn))
                .animate(_controller),
            child: Container(
              width: 200,
              height: 150,
              color: Colors.orange,
            )
        )

curve设置可以设置一个区间:Interval(0, 0.5)意思是从0到0.5倍给定的时间来执行动画,后面0.5到1.0倍的时间处于空闲状态。 这个Interval都在什么情况下用呢?接下来看一个小例子:

Flutter动画系列-2

double _interval = 0;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('显式动画'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: List.generate(5, (index) {
            _interval+= 0.2;
            return MoveBox(controller: _controller, color: Colors.orange[100 * (index + 1)], interval: Interval(_interval - 0.2, _interval),);
          }),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: (){
          _controller.repeat(reverse: true);
          // _controller.forward();
        },
      ),
    );
  }
class MoveBox extends StatelessWidget {
  const MoveBox({
    Key key,
    @required AnimationController controller,
    this.color,
    this.interval,
  }) : _controller = controller, super(key: key);

  final AnimationController _controller;
  final Color color;
  final Interval interval;

  @override
  Widget build(BuildContext context) {
    return SlideTransition(
        position: Tween(begin: Offset(0, 0), end: Offset(0.1, 0))
        .chain(CurveTween(curve: Curves.bounceOut))
        .chain(CurveTween(curve: interval))
            .animate(_controller),
        child: Container(
          width: 300,
          height: 80,
          color: color,
        )
    );
  }
}

上面就用到了Interval来控制动画什么时候开始。

在上一篇隐式动画提到如果想自定义动画而不想用Flutter自带的控件可以使用TweenAnimationBuilder,显示动画也可以使用AnimatedBuilder自定义动画:

body: Center(
        child: AnimatedBuilder(
            animation: _controller,
            builder: (BuildContext context, Widget child) {
              return Opacity(
                opacity: _controller.value,
                child: Container(width: 150, height: 150, color: Colors.orange),
              );
            }
        ),
      ),

得到一个闪烁的动画

Flutter动画系列-2

上面这种写法opacity: _controller.value,总感觉好像不是很妥当,因为通过前面的文章我们都习惯了用Tween来控制区间,其实我们更常用的写法是下面这种: opacity: Tween(begin: 0.0, end: 1.0).animate(_controller).value 又见到了我们熟悉的Tween,这样我们就又可以嵌套多种效果了:

Flutter动画系列-2

Flutter动画系列-2

Flutter动画系列-2

最后再来详细说一下Ticker,初始化controller的时候需要混入一个ticker类,并且vsync:this,来完成我们的动画,我们也可以不用绑定ticker,自己可以实现一个ticker完成动画效果:

@override
  void initState() {

    Ticker ticker = Ticker((_){
      setState(() {
        _height+=5;
        if (_height > 300) _height = 0;
      });
    });
    ticker.start();
    super.initState();
  }
body: Center(
        child: Container(
          width: 200,
          height: _height,
          color: Colors.orange,
        ),
      )

_height 初始值为0

Flutter动画系列-2

但是,Flutter不建议我们这样直接操作Ticker,所以才为我们创建了一个混入类,并且为我们做了很多优化,在60Hz分辨率的设备上这个动画执行时间是1秒钟,在120Hz分辨率的设备上0.5秒动画效果就执行完了,所以还是用系统写好的ticker吧。

转载自:https://juejin.cn/post/6949821554037358605
评论
请登录