美文网首页
Flutter 动画(Animation)

Flutter 动画(Animation)

作者: wuchao226 | 来源:发表于2019-08-22 11:29 被阅读0次

效果图

preview.gif

案例

import 'package:flutter/material.dart';

///语音识别
class SpeakPage extends StatefulWidget {
  @override
  _SpeakPageState createState() => _SpeakPageState();
}

///需要继承TickerProvider,如果有多个AnimationController,则应该使用TickerProviderStateMixin。
class _SpeakPageState extends State<SpeakPage> with SingleTickerProviderStateMixin {
  String speakTips = '长按说话';
  String speakResult = '';
  Animation<double> animation;
  AnimationController controller;

  @override
  void initState() {
    //创建 AnimationController 对象 AnimationController 用于控制动画,它包含动画的启动forward()、停止stop() 、反向播放 reverse()等方法。
    controller = AnimationController(
        vsync: this, duration: Duration(milliseconds: 1000));
    //CurvedAnimation来指定动画的曲线;Curves.easeIn 开始慢,后面快
    animation = CurvedAnimation(parent: controller, curve: Curves.easeIn)
      ..addStatusListener((status) {
        if (status == AnimationStatus.completed) {
         // 动画结束时,反转从尾到头播放,结束的状态是 dismissed
          controller.reverse();
        } else if (status == AnimationStatus.dismissed) {
          // 重新从头到尾播放
          controller.forward();
        }
      });
    super.initState();
  }

  @override
  void dispose() {
    //路由销毁时需要释放动画资源
    controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        padding: EdgeInsets.all(30),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: <Widget>[
            _topItem(),
            _bottomItem(),
          ],
        ),
      ),
    );
  }

  ///顶部视图
  _topItem() {
    return Column(
      children: <Widget>[
        Padding(
          padding: EdgeInsets.fromLTRB(0, 30, 0, 30),
          child: Text(
            '你可以这样说',
            style: TextStyle(fontSize: 16, color: Colors.black54),
          ),
        ),
        Text(
          '故宫门票\n北京一日游\n迪士尼乐园',
          textAlign: TextAlign.center,
          style: TextStyle(fontSize: 15, color: Colors.grey),
        ),
        Padding(
          padding: EdgeInsets.all(20),
          child: Text(
            speakResult,
            style: TextStyle(color: Colors.blue),
          ),
        ),
      ],
    );
  }

  ///底部视图
  _bottomItem() {
    //根据父容器宽高的百分比来设置子组件宽高
    return FractionallySizedBox(
      widthFactor: 1,
      child: Stack(
        children: <Widget>[
          GestureDetector(
            onTapDown: (e) {
              _speakStart();
            },
            onTapUp: (e) {
              _speakStop();
            },
            child: Column(
              children: <Widget>[
                Padding(
                  padding: EdgeInsets.all(10),
                  child: Text(
                    speakTips,
                    style: TextStyle(color: Colors.blue, fontSize: 12),
                  ),
                ),
                Stack(
                  children: <Widget>[
                    Container(
                      //占坑,避免动画执行过程中导致父布局大小变化
                      width: MIC_SIZE,
                      height: MIC_SIZE,
                    ),
                    Center(
                      child: AnimatedMic(
                        animation: animation,
                      ),
                    ),
                  ],
                ),
              ],
            ),
          ),
          //右边关闭按钮
          Positioned(
            right: 0,
            bottom: 20,
            child: GestureDetector(
              onTap: () {
                Navigator.pop(context);
              },
              child: Icon(
                Icons.close,
                size: 30,
                color: Colors.grey,
              ),
            ),
          ),
        ],
      ),
    );
  }

  void _speakStart() {
    controller.forward();
  }

  void _speakStop() {
    controller.reset();
    controller.stop();
  }
}

const double MIC_SIZE = 80;
///AnimatedWidget会自动调用addlistener和setState
class AnimatedMic extends AnimatedWidget {
  //透明度改变动画
  static final _opacityTween = Tween<double>(begin: 1, end: 0.5);

  //大小改变动画
  static final _sizeTween = Tween<double>(begin: MIC_SIZE, end: MIC_SIZE - 20);

  AnimatedMic({Key key, Animation<double> animation})
      : super(key: key, listenable: animation);

  @override
  Widget build(BuildContext context) {
    // 外部传递过来的 Animation 对象
    final Animation<double> animation = listenable;
    return Opacity(
      opacity: _opacityTween.evaluate(animation),
      child: Container(
        //evaluate(Animation<double> animation) 获取动画当前映射值
        height: _sizeTween.evaluate(animation),
        width: _sizeTween.evaluate(animation),
        decoration: BoxDecoration(
          color: Colors.blue,
          borderRadius: BorderRadius.circular(MIC_SIZE / 2),
        ),
        child: Icon(
          Icons.mic,
          color: Colors.white,
          size: 30,
        ),
      ),
    );
  }
}

上面代码可以看出动画示例步骤:

  • 初始化一个 AnimationController 对象
AnimationController  controller = AnimationController(
        vsync: this, duration: Duration(milliseconds: 1000));
  • 初始化一个 Animation对象, 并将 AnimationController 作为参数传递进去。
  //CurvedAnimation来指定动画的曲线;Curves.easeIn 开始慢,后面快
    animation = CurvedAnimation(parent: controller, curve: Curves.easeIn)
      ..addStatusListener((status) {
        if (status == AnimationStatus.completed) {
         // 动画结束时,反转从尾到头播放,结束的状态是 dismissed
          controller.reverse();
        } else if (status == AnimationStatus.dismissed) {
          // 重新从头到尾播放
          controller.forward();
        }
      });
  • 调用 AnimationControllerforward() 方法执行动画
 controller.forward();
  • Widgetdispose()方法中调用释放资源
controller.dispose();

AnimationStatus.completed 表示动画在结束时停止的状态,这个时候我们让动画反向执行(从后往前);AnimationStatus.dismissed表示动画在开始时就停止的状态,这个时候我们让动画正常执行(从前往后)。这样就可以让动画无限执行了。

AnimatedWidget

使用 AnimatedWidget来实现动画,就不需要给动画 addListener(...) 和 setState((){}) 了,AnimatedWidget 自己会使用当前 Animation 的 value 来绘制自己。当然,这里 Animation 我们是以构造参数的方式传递进去的。

代码如下:

const double MIC_SIZE = 80;
///AnimatedWidget会自动调用addlistener和setState
class AnimatedMic extends AnimatedWidget {
  //透明度改变动画
  static final _opacityTween = Tween<double>(begin: 1, end: 0.5);

  //大小改变动画
  static final _sizeTween = Tween<double>(begin: MIC_SIZE, end: MIC_SIZE - 20);

  AnimatedMic({Key key, Animation<double> animation})
      : super(key: key, listenable: animation);

  @override
  Widget build(BuildContext context) {
    // 外部传递过来的 Animation 对象
    final Animation<double> animation = listenable;
    return Opacity(
      opacity: _opacityTween.evaluate(animation),
      child: Container(
        //evaluate(Animation<double> animation) 获取动画当前映射值
        height: _sizeTween.evaluate(animation),
        width: _sizeTween.evaluate(animation),
        decoration: BoxDecoration(
          color: Colors.blue,
          borderRadius: BorderRadius.circular(MIC_SIZE / 2),
        ),
        child: Icon(
          Icons.mic,
          color: Colors.white,
          size: 30,
        ),
      ),
    );
  }
}

上述代码中,我们定义了一个 AnimatedMic 继承了 AnimatedWidget,然后定义了一个构造方法,注意,构造方法中我们定义了一个 Animation 然后把这个 Animation 传到父类(super)中去了,我们可以看看listenable: animation 这个参数,是一个 Listenable 类型,如下:

/// The [Listenable] to which this widget is listening.
  ///
  /// Commonly an [Animation] or a [ChangeNotifier].
  final Listenable listenable;

然后再看看 Animation 类:

abstract class Animation<T> extends Listenable implements ValueListenable<T> {
...
}

可以看到 AnimationListenable 的子类,所以在我们自定义的 AnimatedMic 类中可以传一个 Animation 类型的的参数作为父类中 listenable 的值。

使用我们上面定义的 AnimatedMic 也很简单,直接作为 widget使用就好,部分代码如下:

Stack(
    children: <Widget>[
       Container(
          //占坑,避免动画执行过程中导致父布局大小变化
          width: MIC_SIZE,
          height: MIC_SIZE,
       ),
       Center(
         child: AnimatedMic(
         animation: animation,
         ),
       ),
    ],
)

可以看出我们在实例化 AnimatedMic 的时候传入了一个 Animation 对象。

参考

官方文档
Flutter动画
Flutter 中的 Animations

相关文章

网友评论

      本文标题:Flutter 动画(Animation)

      本文链接:https://www.haomeiwen.com/subject/dqigsctx.html