美文网首页
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