Flutter 动画与打包

作者: zcwfeng | 来源:发表于2020-07-27 18:41 被阅读0次

    动画与打包

    动画

    ​ Flutter中的动画系统基于Animation对象的,和之前的手势不同,它不是一个Widget,这是因为Animation对象本身和UI渲染没有任何关系。Animation是一个抽象类,就相当于一个定时器,它用于保存动画的插值和状态,并执行数值的变化。widget可以在build函数中读取Animation对象的当前值, 并且可以监听动画的状态改变。

    AnimationController

    ​ AnimationController用于控制动画,它包含动画的启动forward()、停止stop() 、反向播放 reverse()等方法。AnimationController会在动画的每一帧,就会生成一个新的值。默认情况下,AnimationController在给定的时间段内线性的生成从0.0到1.0(默认区间)的数字。

    AnimationController controller = AnimationController( 
     duration: const Duration(milliseconds: 2000), //动画时间
     lowerBound: 10.0,  //生成数字的区间 
     upperBound: 20.0,  //10.0 - 20.0
     vsync: this  //TickerProvider 动画驱动器提供者
    );
    

    Ticker

    ​ Ticker的作用是添加屏幕刷新回调,每次屏幕刷新都会调用TickerCallback。使用Ticker来驱动动画会防止屏幕外动画(动画的UI不在当前屏幕时,如锁屏时)消耗不必要的资源。因为Flutter中屏幕刷新时会通知Ticker,锁屏后屏幕会停止刷新,所以Ticker就不会再触发。最简单的做法为将SingleTickerProviderStateMixin添加到State的定义中。

    import 'package:flutter/material.dart';
    
    void main() => runApp(AnimationApp());
    
    class AnimationApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: "animation",
          home: Scaffold(
            appBar: AppBar(
              title: Text('animation'),
            ),
            body: AnimWidget(),
          ),
        );
      }
    }
    
    // 动画是有状态的
    class AnimWidget extends StatefulWidget {
      @override
      State<StatefulWidget> createState() {
        return _AnimWidgetState();
      }
    }
    
    class _AnimWidgetState extends State<AnimWidget>
        with SingleTickerProviderStateMixin {
      AnimationController controller;
      bool forward = true;
    
      @override
      void initState() {
        super.initState();
        controller = AnimationController(
          // 动画的时长
          duration: Duration(milliseconds: 2000),
          lowerBound: 10.0,
          upperBound: 100.0,
          // 提供 vsync 最简单的方式,就是直接混入 SingleTickerProviderStateMixin
          // 如果有多个AnimationController,则使用TickerProviderStateMixin。
          vsync: this,
        );
        //状态修改监听
        controller
          ..addStatusListener((AnimationStatus status) {
            debugPrint("状态:$status");
          })
          ..addListener(() {
            setState(() => {});
          });
    
        debugPrint("controller.value:${controller.value}");
      }
    
      @override
      Widget build(BuildContext context) {
        return Column(
          children: <Widget>[
            Container(
              width: controller.value,
              height: controller.value,
              color: Colors.blue,
            ),
            RaisedButton(
              child: Text("播放"),
              onPressed: () {
                if (forward) {
                  controller.forward();
                } else {
                  controller.reverse();
                }
                forward = !forward;
              },
            ),
            RaisedButton(
              child: Text("停止"),
              onPressed: () {
                controller.stop();
              },
            )
          ],
        );
      }
    }
    
    

    动画状态监听:在forword结束之后状态为completed。在reverse结束之后状态为dismissed

    Tween

    ​ 默认情况下,AnimationController对象值为:double类型,范围是0.0到1.0 。如果我们需要不同的范围或不同的数据类型,则可以使用Tween来配置动画以生成不同的范围或数据类型的值。要使用Tween对象,需要调用其animate()方法,然后传入一个控制器对象,同时动画过程中产生的数值由Tweenlerp方法决定。

    import 'package:flutter/material.dart';
    
    void main() => runApp(AnimationApp());
    
    class AnimationApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: "animation",
          home: Scaffold(
            appBar: AppBar(
              title: Text('animation'),
            ),
            body: AnimWidget(),
          ),
        );
      }
    }
    
    // 动画是有状态的
    class AnimWidget extends StatefulWidget {
      @override
      State<StatefulWidget> createState() {
        return _AnimWidgetState();
      }
    }
    
    class _AnimWidgetState extends State<AnimWidget>
        with SingleTickerProviderStateMixin {
      AnimationController controller;
    
      bool forward = true;
    
      Tween<Color> tween;
    
      @override
      void initState() {
        super.initState();
    
        controller = AnimationController(
          // 动画的时长
          duration: Duration(milliseconds: 2000),
          // 提供 vsync 最简单的方式,就是直接继承 SingleTickerProviderStateMixin
          vsync: this,
        );
        //使用Color
        tween = ColorTween(begin: Colors.blue, end: Colors.yellow);
        //添加动画值修改监听
        tween.animate(controller)..addListener(() => setState(() {}));
      }
    
      @override
      Widget build(BuildContext context) {
        return Column(
          children: <Widget>[
            Container(
              width: 100,
              height: 100,
              //获取动画当前值
              color: tween.evaluate(controller),
            ),
            RaisedButton(
              child: Text("播放"),
              onPressed: () {
                if (forward) {
                  controller.forward();
                } else {
                  controller.reverse();
                }
                forward = !forward;
              },
            ),
            RaisedButton(
              child: Text("停止"),
              onPressed: () {
                controller.stop();
              },
            )
          ],
        );
      }
    }
    
    

    Curve

    ​ 动画过程默认是线性的(匀速),如果需要非线形的,比如:加速的或者先加速后减速等。Flutter中可以通过Curve(曲线)来描述动画过程。

    import 'package:flutter/material.dart';
    
    void main() => runApp(AnimationApp());
    
    class AnimationApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: "animation",
          home: Scaffold(
            appBar: AppBar(
              title: Text('animation'),
            ),
            body: AnimWidget(),
          ),
        );
      }
    }
    
    // 动画是有状态的
    class AnimWidget extends StatefulWidget {
      @override
      State<StatefulWidget> createState() {
        return _AnimWidgetState();
      }
    }
    
    class _AnimWidgetState extends State<AnimWidget>
        with SingleTickerProviderStateMixin {
      AnimationController controller;
      Animation<double> animation;
      bool forward = true;
    
      @override
      void initState() {
        super.initState();
    
        controller = AnimationController(
          // 动画的时长
          duration: Duration(milliseconds: 2000),
          // 提供 vsync 最简单的方式,就是直接继承 SingleTickerProviderStateMixin
          vsync: this,
        );
    
        //弹性
        animation = CurvedAnimation(parent: controller, curve: Curves.bounceIn);
        //使用Color
        animation = Tween(begin: 10.0, end: 100.0).animate(animation)
          ..addListener(() {
            setState(() => {});
          });
      }
    
      @override
      Widget build(BuildContext context) {
        return Column(
          children: <Widget>[
            Container(
              //不需要转换
              width: animation.value,
              height: animation.value,
              //获取动画当前值
              color: Colors.blue,
            ),
            RaisedButton(
              child: Text("播放"),
              onPressed: () {
                if (forward) {
                  controller.forward();
                } else {
                  controller.reverse();
                }
                forward = !forward;
              },
            ),
            RaisedButton(
              child: Text("停止"),
              onPressed: () {
                controller.stop();
              },
            )
          ],
        );
      }
    }
    
    

    AnimatedWidget

    ​ 通过上面的学习我们能够感受到Animation对象本身和UI渲染没有任何关系。而通过addListener()setState() 来更新UI这一步其实是通用的,如果每个动画中都加这么一句是比较繁琐的。AnimatedWidget类封装了调用setState()的细节,简单来说就是自动调用setState()

    ​ Flutter中已经封装了很多动画,比如对widget进行缩放,可以直接使用ScaleTransition

    import 'package:flutter/material.dart';
    
    void main() => runApp(AnimationApp());
    
    class AnimationApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: "animation",
          home: Scaffold(
            appBar: AppBar(
              title: Text('animation'),
            ),
            body: AnimWidget(),
          ),
        );
      }
    }
    
    // 动画是有状态的
    class AnimWidget extends StatefulWidget {
      @override
      State<StatefulWidget> createState() {
        return _AnimWidgetState();
      }
    }
    
    class _AnimWidgetState extends State<AnimWidget>
        with SingleTickerProviderStateMixin {
      AnimationController controller;
      Animation<double> animation;
      bool forward = true;
    
      @override
      void initState() {
        super.initState();
    
        controller = AnimationController(
          // 动画的时长
          duration: Duration(milliseconds: 2000),
          // 提供 vsync 最简单的方式,就是直接继承 SingleTickerProviderStateMixin
          vsync: this,
        );
    
        //弹性
        animation = CurvedAnimation(parent: controller, curve: Curves.bounceIn);
        //使用Color
        animation = Tween(begin: 10.0, end: 100.0).animate(animation);
      }
    
      @override
      Widget build(BuildContext context) {
        return Column(
          children: <Widget>[
            ScaleTransition(
              child:  Container(
                width: 100,
                height: 100,
                color: Colors.blue,
              ),
              scale: controller,
            ),
            RaisedButton(
              child: Text("播放"),
              onPressed: () {
                if (forward) {
                  controller.forward();
                } else {
                  controller.reverse();
                }
                forward = !forward;
              },
            ),
            RaisedButton(
              child: Text("停止"),
              onPressed: () {
                controller.stop();
              },
            )
          ],
        );
      }
    }
    
    

    Hero动画

    ​ Hero动画就是在路由切换时,有一个共享的Widget可以在新旧路由间切换,由于共享的Widget在新旧路由页面上的位置、外观可能有所差异,所以在路由切换时会逐渐过渡,这样就会产生一个Hero动画。

    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return new MaterialApp(
          title: 'Flutter Demo',
          home: Scaffold(
              appBar: AppBar(
                title: Text("主页"),
              ),
              body: Route1()),
        );
      }
    }
    
    // 路由A
    class Route1 extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Container(
          alignment: Alignment.topCenter,
          child: InkWell(
            child: Hero(
              tag: "avatar", //唯一标记,前后两个路由页Hero的tag必须相同
              child: CircleAvatar(
                backgroundImage: AssetImage(
                  "assets/banner.jpeg",
                ),
              ),
            ),
            onTap: () {
              Navigator.push(context, MaterialPageRoute(builder: (_) {
                return Route2();
              }));
            },
          ),
        );
      }
    }
    
    class Route2 extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Center(
          child: Hero(
              tag: "avatar", //唯一标记,前后两个路由页Hero的tag必须相同
              child: Image.asset("assets/banner.jpeg")),
        );
      }
    }
    
    

    组合动画

    有些时候我们可能会需要执行一个动画序列执行一些复杂的动画。

    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return new MaterialApp(
          title: 'Flutter Demo',
          home: Route(),
        );
      }
    }
    
    class Route extends StatefulWidget {
      @override
      State<StatefulWidget> createState() {
        return RouteState();
      }
    }
    
    class RouteState extends State<Route> with SingleTickerProviderStateMixin {
      Animation<Color> color;
      Animation<double> width;
      AnimationController controller;
    
      @override
      void initState() {
        super.initState();
        controller = AnimationController(
          // 动画的时长
          duration: Duration(milliseconds: 2000),
          // 提供 vsync 最简单的方式,就是直接继承 SingleTickerProviderStateMixin
          vsync: this,
        );
    
        //高度动画
        width = Tween<double>(
          begin: 100.0,
          end: 300.0,
        ).animate(
          CurvedAnimation(
            parent: controller,
            curve: Interval(
              //间隔,前60%的动画时间 1200ms执行高度变化
              0.0, 0.6,
            ),
          ),
        );
    
        color = ColorTween(
          begin: Colors.green,
          end: Colors.red,
        ).animate(
          CurvedAnimation(
            parent: controller,
            curve: Interval(
              0.6, 1.0, //高度变化完成后 800ms 执行颜色编码
            ),
          ),
        );
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text("主页"),
          ),
          body: InkWell(
            ///1、不用显式的去添加帧监听器,再调用setState()
            ///2、缩小动画构建的范围,如果没有builder,setState()将会在父widget上下文调用,导致父widget的build方法重新调用,现在只会导致动画widget的build重新调用
            child: AnimatedBuilder(
                animation: controller,
                builder: (context, child) {
                  return Container(
                    color: color.value,
                    width: width.value,
                    height: 100.0,
                  );
                }),
            onTap: () {
              controller.forward().whenCompleteOrCancel(() => controller.reverse());
            },
          ),
        );
      }
    }
    
    

    打包

    ​ Flutter在打Release包时候回使用AOT,因此在对一个Flutter测试时候务必使用Release来进行测试。打包命令:flutter build apk 。当然我们需要打包时,还需要配置一些比如签名的内容。配置这些内容和普通Android工程没有区别,都是在build.gradle中进行,只是Flutter工程AS没有提供GUI。

    ​ 在Flutter工程的android/app下面的build.gradle可以修改包名、版本等信息,这就不用多说了。获得签名文件之后,将它复制到flutter的android目录:

    ​ 然后在app的build.gradle中配置:

    signingConfigs {
            release {
                keyAlias 'enjoy'
                keyPassword '123456'
                // 因为是放到父级的根目录,使用rootProject
                // 如果放在这个build.gradle的同级,直接使用file
                storeFile rootProject.file('enjoy.jks')
                storePassword '123456'
            }
        }
        buildTypes {
            release {
                // TODO: Add your own signing config for the release build.
                // Signing with the debug keys for now, so `flutter run --release` works.
                signingConfig signingConfigs.release
            }
        }
    

    饼图

    https://github.com/google/charts

    Stack布局中的fit属性与Image的fit类似,表示内容的扩充情况。默认为StackFit.loose表示Stack与内容一样大。如果设置为StackFit.passthrough则表示Stack父Widget的约束会传给Stack内部非Positioned的子Widget。效果如代码中的StackFit.dart

    相关文章

      网友评论

        本文标题:Flutter 动画与打包

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