Flutter实战一个动画【教学】

作者: 谁动了我的代码 | 来源:发表于2022-10-12 21:26 被阅读0次

    一、动画

    原理

    Flutter动画基于补间动画,在页面刷新时,会生成一个插值,随后根据插值不断更新RenderObject的状态,实现动画播放。

    Flutter动画类型

    • 隐式动画:使用自带的Animation控件直接显示动画,无法对动画的状态做完整的监听和控制;
    • 显式动画:自定义Controller、Tween、Curve,可以完整监听和控制动画播放;

    隐式动画

    如AnimatedContainer、AnimateAlign:

    
    Color _color = Colors.blue;
    
    @override
    Widget build(BuildContext context) {
      return AnimatedContainer(
        duration: const Duration(seconds: 1),
        width: 200,
        height: 200,
        color: _color,
      );
    }
    
    setState(() {
      _color = Colors.yellow;
    });
    

    隐式动画实际上是flutter对显式动画的一种封装,通常是ImplicitlyAnimatedWidget的子类,在内部还是利用显式动画的那一套工具实现的;

    abstract class ImplicitlyAnimatedWidgetState<T extends ImplicitlyAnimatedWidget> 
      extends State<T> with SingleTickerProviderStateMixin<T> {
    
      /// The animation controller driving this widget's implicit animations.
      @protected
      AnimationController get controller => _controller;
      late final AnimationController _controller = AnimationController(
        duration: widget.duration,
        debugLabel: kDebugMode ? widget.toStringShort() : null,
        vsync: this,
      );
    
      /// The animation driving this widget's implicit animations.
      Animation<double> get animation => _animation;
      late Animation<double> _animation = _createCurve();
    
      ...  
    }
    

    显式动画

    • 幕后主导:Ticker
    • 三大件:AnimationController、Tween、Curve
    • UI:AnimatedWidget(SlideTransition、ScaleTransition等)、AnimatedBuilder、自定义StatefulWidget


    Ticker

    帧定时器,负责驱动动画的运行,执行start方法后,每一帧渲染前(一个vsync信号)都会回调一次传入的函数。

    Ticker ticker = Ticker((elapsed) => print('hello'));
    

    直接操作Ticker比较复杂,因此创建动画控件时直接使用SingleTickerProviderStateMixin或TickerProviderStateMixin混入到动画控件的state中即可。

    AnimationController

    和它的名字一致,主要作用是控制和驱动动画的播放、停止等操作。

    创建AnimationController时需要传入一个TickerProvider,在Ticker进行逐帧回调时,不断生成0.0 ~ 1.0之间的double值(插值)[相当于每帧刷新一次插值]。

    AnimationController的播放控制能力也是通过修改当前插值实现的,如:

    • forward():实际上调用animateToInternal(upperBound),即将value增加到1.0;
    • reverse():实际上调用_animateToInternal(lowerBound),即将value减小到0.0;
    • repeat():反复将插值从最小值到最大值之间变换;
    • reset():将值调回到0.0。

    Tween<T>

    主要作用就是转换AnimationController的value。

    原理上来说,利用AnimationController生成的value就可以实现动画效果了,但是AnimationController的值只能是double类型,如果动画需要实现控件的color、rect、alignment的变换,仅靠一个double值很难实现想要的效果。

    给定一个begin和end值,Tween可以根据lerp方法,将AnimationController的value做一些转换。

    Tween有很多子类,如ColorTween、RectTween、AlignmentTween等,基本上满足实现动画的需要。可以自定义数据类型的变换,需要重写lerp方法,实现自己的变换函数。

    用法:Tween是Animatable的子类,通过animate方法可转为Animation类,绑定到AnimationController中。

    Animation sizeAnimation = Tween<double>(
      begin: 1.0,
      end: 2.0,
    ).animate(_animController);
    
    Animation colorAnimation = ColorTween(
      begin: Colors.blue,
      end: Colors.yellow,
    ).animate(_animController);
    

    Curve

    定义动画值的变化速度。AnimationController的value是线性速度生成的,Curve可以改变生成速度,达到加速、减速等动画效果。


    系统内置了几十种不同Curve实例,这也是最常用的控制,也可以自己基于cubic(二阶贝塞尔)创建。

    总结:

    • Ticker提供逐帧的回调;
    • AnimationController默认提供了从0.0到1.0的值变化,在Ticker回调时生成一个插值,用来进行动画控制;
    • 基于AnimationController的初始值,Tween对其进行转换,Curve对动画速度进行控制。
    • Tween的值包装进Animation对象中,提供了值和状态的监听,绑定到动画组件中实现动画效果。

    动画Widget

    通过AnimationController、Tween、Curve三大件,可以得到一个Animation对象;

    以平移动画为例:

    AnimationController _animController = AnimationController(
      duration: Duration(second: 1);
      vsync: this;
    );
    
    Animation sildeAnimation = Tween<Offset>(
      begin: Offset.zero,
      end: Offset(1, 1),
    ).animate(
      CurvedAnimation(parent: _animController, curve: Curves.ease),
    );
    

    用法一:AnimatedWidget子类

    SlideTransition(
      position: sildeAnimation,
      child: Container(
        width: 200,
        height: 200,
        color: Colors.blue,
      ),
    );
    

    用法二:AnimateBuilder

    AnimatedBuilder(
      animation: _animController,
      builder: (context, child) {
        return FractionalTranslation(
          translation: sildeAnimation.value,
          child: Container(
            width: 200,
            height: 200,
            color: Colors.blue,
          ),
        );
      },
    );
    

    用法三:StatefulWidget(需要手动setState())

    _animController.addListener(() {
      setState(() {
      });
    });
    
    FractionalTranslation(
      translation: sildeAnimation.value,
      child: Container(
        width: 200,
        height: 200,
        color: Colors.blue,
      ),
    );
    

    组合动画

    1、顺序动画(同步)

    TweenSequence按顺序执行每一个子Tween item,通过weigt参数控制每一个item的时间占比:

    _offsetAnimation = TweenSequence([
      TweenSequenceItem(
        tween: Tween<Offset>(begin: Offset.zero, end: const Offset(1, 1)),
        weight: 1,
      ),
      TweenSequenceItem(
        tween: Tween<Offset>(begin: const Offset(1, 1), end: Offset.zero),
        weight: 1,
      ),
    ]).animate(_animController);
    

    2、交织动画(异步)

    通过Interval控制每个子动画的起始和终止的时间;

    _offsetAnimation = Tween<Offset>(
      begin: Offset.zero,
      end: const Offset(2, 1),
    ).animate(
      CurvedAnimation(
        parent: _animController,
        curve: const Interval(0.0, 1.0, curve: Curves.linear),
      ),
    );
    
    _colorAnimation = ColorTween(
      begin: Colors.blue,
      end: Colors.yellow,
    ).animate(
      CurvedAnimation(
        parent: _animController,
        curve: const Interval(0.3, 0.7, curve: Curves.linear),
      ),
    );
    

    二、游戏

    以认单词游戏为例:动画数量多,控件数量多,状态管理复杂。

    1. 枚举动画的所有状态,分类整理成状态组,如人物动作、人物位置、地图位置等,每种状态组包含多个状态,例如人物动作中有站立、跑步、欢呼状态。
    2. 创建游戏剧本,将游戏中的每个状态按顺序保存,并使用链表结构将其串联;
    3. 创建GameController,持有游戏剧本,控制游戏剧本的走向; 将游戏的各个元素从Page中抽成子控件,如人物控件、地图控件、选择器控件等;
    4. 各个控件和GameController的通信通过Cubit实现;
    5. 控件中,通过BlocBuilder、BlocListener监听自己关心的状态,当监听游戏切换到自己的状态时,开始执行动画(或其他操作),结束后回调notifyStateFinish通知GameController,GameController会转到到下一个状态;
    6. GameController收到notifyStateFinish通知后,视为当前状态已经结束,按照游戏剧本切换到下一个状态,让Cubit去通知下一个组件,循环往复该过程;


    认单词游戏属于线性游戏流程,也就是游戏的流程固定,不会受到用户的操作影响而改变游戏的走向。如果是多分支的游戏,可以将游戏剧本也做成链表结构,创建多个子剧本,GameController再根据游戏的具体走向,连接到下一个子剧本中,以实现切换分支的功能。 ———————————————— 版权声明:本文为CSDN博主「Wanderlust1」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/weixin_41007222/article/details/125944301

    以上就是flutter的一个小动画的学习了,想成为flutter工程师还要学习许多;比喻:从最基础的语法dart、UI、线程、启动流程、framework框架、性能监控等等一系列的进阶学习。这里我推荐大家参考学习《flutter手册》里面是从最基础的语法dart开始教学。我建议新手或者想进阶flutter技术的,可以通过这个手册辅助自己进阶。

    文末

    Flutter是真正的跨平台不管是开发体验 、调试 、性能 (基本上比肩原生) 而我包含:IOS、安卓、Windows、Mac、Web,性能都是杠杠的,dart学习成本几乎很低,基本上一周就可以完全上手了,那些说学习成本很高的怕不是老了学不动了?在跨平台方面能做到这么好的目前也只有flutter了,有什么理由不用呢?

    相关文章

      网友评论

        本文标题:Flutter实战一个动画【教学】

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