Flutter 基础动画

112 阅读4分钟

参考链接

动画在目前应用开发中整体使用较少,这里只整理基础的核心动画原理,以备学习查阅。

显式动画

AnimationController

AnimationController,它是动画控制器,控制动画的启动、停止,还可以获取动画的运行状态,AnimationController 通常在 initState 方法中初始化。

  • vsync:当创建 AnimationController 时,需要传递一个vsync参数,存在vsync时会防止屏幕外动画消耗不必要的资源,单个 AnimationController 的时候使用 SingleTickerProviderStateMixin,多个 AnimationController 使用 TickerProviderStateMixin
  • duration:表示动画执行的时间。
class AnimationBaseDemo extends StatefulWidget {
  @override
  _AnimationBaseDemoState createState() => _AnimationBaseDemoState();
}

class _AnimationBaseDemoState extends State<AnimationBaseDemo>
with SingleTickerProviderStateMixin{

  double _size = 100;
  AnimationController _controller;

  @override
  void initState();
  super.initState();
  
  //AnimationController 的值默认是 0 到 1
  _controller = AnimationController(vsync: this,duration: Duration(milliseconds: 500))
  
  //监听动画执行,会多次调用
  ..addListener(() {
  setState(() {
    _size = 100+100*_controller.value;
   });
  })
 //监听动画状态
  ..addStatusListener((status) {
    print('status:$status');
  });

      
  @override
  Widget build(BuildContext context) {
    return Center(
      child: GestureDetector(
        onTap: () {
            _controller.forward();
        },
        child: Container(
          height: _size,
          width: _size,
          color: Colors.blue,
          alignment: Alignment.center,
          child: Text('点我变大',style: TextStyle(color: Colors.white,fontSize: 18),),
        ),
      ),
    );
  }
}

动画的状态分为四种:

  • dismissed:动画停止在开始处。
  • forward:动画正在从开始处运行到结束处(正向运行)。
  • reverse:动画正在从结束处运行到开始处(反向运行)。
  • completed:动画停止在结束处。

再来看下动画的控制方法:

  • forward:正向执行动画。
  • reverse:反向执行动画。
  • repeat:反复执行动画。
  • reset:重置动画。

AnimatedBuilder

AnimatedBuilder自动监听来自Animation对象的通知,不需要手动调用addListener()


void initState() {
  super.initState();
  controller =
      AnimationController(duration: const Duration(seconds: 1), vsync: this);
  animation = Tween<double>(begin: 0, end: 1.0).animate(controller);
}

Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text(widget.title),
    ),
    body: AnimatedBuilder(
      animation: controller,
      builder: (BuildContext context, Widget? child) {
        return Container(
          height: 200,
          padding: const EdgeInsets.all(10),
          margin: const EdgeInsets.all(10),
          decoration: BoxDecoration(
              shape: BoxShape.circle,
              gradient: LinearGradient(
                  begin: Alignment.topCenter,
                  end: Alignment.bottomCenter,
                  colors: const [Colors.yellow, Colors.red],
                  stops: [0, controller.value])),
        );
      },
    ),
    floatingActionButton: FloatingActionButton(
      onPressed: () {
        // 点击事件,设置动画开始
        if (controller.status == AnimationStatus.completed) {
          controller.reverse();
        } else {
          controller.forward();
        }
      },
      child: const Icon(Icons.play_arrow),
    ),
  );
}

Tween

AnimationController 设置的最小/大值类型是 double,但要设置颜色变化或其它变化就需要做其它映射。

class _TweenDemoState extends State<TweenDemo>
    with SingleTickerProviderStateMixin {
    
  AnimationController _controller;
  Animation<Color> _animation;

  @override
  void initState() {
    super.initState();
    
    _controller =
        AnimationController(vsync: this, duration: Duration(milliseconds: 500))
          ..addListener(() {
            setState(() {});
          });
    //动画的控制依然由 AnimationController 控制,
    //但需要 **Tween.animate(_controller)**  将控制器传递给Tween。      
    _animation =
        ColorTween(begin: Colors.blue, end: Colors.red).animate(_controller);
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: GestureDetector(
        onTap: () {
          _controller.forward();
        },
        child: Container(
          height: 100,
          width: 100,
          color: _animation.value,
          alignment: Alignment.center,
          child: Text(
            '点我变色',
            style: TextStyle(color: Colors.white, fontSize: 18),
          ),
        ),
      ),
    );
  }

  @override
  void dispose() {
    super.dispose();
    _controller.dispose();
  }
}

渐变颜色的动画效果


controller =
    AnimationController(duration: const Duration(seconds: 1), vsync: this);
animation = Tween<double>(begin: 0, end: 1.0).animate(controller);

    
AnimatedBuilder(
  animation: controller,
  builder: (BuildContext context, Widget? child) {
    return Container(
      height: 200,
      padding: const EdgeInsets.all(10),
      margin: const EdgeInsets.all(10),
      decoration: BoxDecoration(
          shape: BoxShape.circle,
          gradient: LinearGradient(
              begin: Alignment.topCenter,
              end: Alignment.bottomCenter,
              colors: const [Colors.yellow, Colors.red],
              stops: [0, controller.value])),
    );
  },
)

Curve

动画中还有一个Curve动画行进曲线的概念,负责控制动画变化的速率,通俗地讲就是使动画的效果能够以匀速、加速、减速、抛物线等各种速率变化。

 _animation = Tween(begin: 100.0, end: 200.0)
        .chain(CurveTween(curve: Curves.bounceIn))
        .animate(_controller);

或者

_animation = _controller
    .drive(CurveTween(curve: Curves.bounceIn))
    .drive(Tween(begin: 100.0, end: 200.0));

系统已经提供了38种常用到动画曲线,以下是常用的几种曲线类型: linear

decelerate

bounceIn

bounceOut

elasticIn

Interval

Flutter中组合动画使用IntervalInterval继承自Curve

动画从0.5(一半)开始到结束,如果动画时长为6秒,则从第3秒开始。 Intervalbegin 和end参数值的范围是0.0到1.0。

Animation _sizeAnimation = Tween(begin: 100.0, end: 300.0).animate(CurvedAnimation(
    parent: _animationController, curve: Interval(0.5, 1.0)));

先执行颜色变化,再执行大小变化

class AnimationDemo extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _AnimationDemo();
}

class _AnimationDemo extends State<AnimationDemo>
    with SingleTickerProviderStateMixin {
    
  AnimationController _animationController;
  Animation _colorAnimation;
  Animation _sizeAnimation;

  @override
  void initState() {
    _animationController =
        AnimationController(duration: Duration(seconds: 5), vsync: this)
    ..addListener((){setState(() {
      
    });});

    //如果需要两个动画同时执行,只需将2个 Interval 的值都改 Interval(0.0, 1.0) 。

    _colorAnimation = ColorTween(begin: Colors.red, end: Colors.blue).animate(
        CurvedAnimation(
            parent: _animationController, curve: Interval(0.0, 0.5)));

    _sizeAnimation = Tween(begin: 100.0, end: 300.0).animate(CurvedAnimation(
        parent: _animationController, curve: Interval(0.5, 1.0)));


    //开始动画
    _animationController.forward();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: <Widget>[
          Container(
              height: _sizeAnimation.value,
              width: _sizeAnimation.value,
              color: _colorAnimation.value),
        ],
      ),
    );
  }

  @override
  void dispose() {
    _animationController.dispose();
    super.dispose();
  }
}

类似需要求也可以用 TweenSequence实现

_animation = TweenSequence([

  //前40%的时间大小从100->200
  TweenSequenceItem(
      tween: Tween(begin: 100.0, end: 200.0)
          .chain(CurveTween(curve: Curves.easeIn)),
      weight: 40),
  
  //200不变20%的时间
  TweenSequenceItem(tween: ConstantTween<double>(200.0), weight: 20),
  
  //最后40%的时间大小从200->300
  TweenSequenceItem(tween: Tween(begin: 200.0, end: 300.0), weight: 40),
  
  
]).animate(_animationController);

隐式动画

项⽬中如果大量使用上面那种完整的动画写法,代码会变得非常冗余,所以 Flutter 官⽅默认提供了⼀些动画 封装,也可以叫做隐式动画,常⻅是 Animated* 开头的 Widget, 例如 AnimatedPositioned 、 AnimatedContainer 、AnimatedPadding 、 AnimatedOpacity 等控件,其内部已经完全封装好逻辑,只需要配置对应参数就可以触发动画。

body: Center(
    child: AnimatedContainer(
        width: _width,
        height: _height,
        decoration: BoxDecoration(
            color: _color,
            borderRadius: _borderRadius,
        ),
        durationDuration(seconds1),
        curve: Curves.fastOutSlowIn,
    ),
)

如上代码所示,只需改变 _width 和 _height 就可以触发动画效果,当然改变其他某些属性也可以进行动画,比如下面的 alignmentcolor等。