当前位置:   article > 正文

Flutter动画(一)Ticker、Animate 原理

Flutter动画(一)Ticker、Animate 原理

在任何系统的UI框架中,动画原理都是类似的,即:在一段时间内,快速地多次改变UI外观;由于人眼会产生视觉暂留,所以最终看到的就是一个“连续”的动画。

Flutter中对动画进行了抽象,主要涉及 Animation、Curve、Controller、Tween这四个角色,它们一起配合来完成一个完整动画。

1.基本概念

1. Animation

Animation是一个抽象类,它本身和UI渲染没有任何关系,而它主要的功能是保存动画的插值和状态。Animation对象是一个在一段时间内依次生成一个区间(Tween)之间值的类。Animation对象在整个动画执行过程中输出的值可以是线性的、曲线的、一个步进函数或者任何其他曲线函数等等,这由Curve来决定。

我们可以通过Animation来监听动画每一帧以及执行状态的变化Animation有如下两个方法:

  1. addListener();它可以用于给Animation添加帧监听器,在每一帧都会被调用。帧监听器中最常见的行为是改变状态后调用setState()来触发UI重建。
  2. addStatusListener();它可以给Animation添加“动画状态改变”监听器;动画开始、结束、正向或反向(见AnimationStatus定义)时会调用状态改变的监听器。

2. Curve

动画过程可以是匀速的、匀加速的或者先加速后减速等。Flutter中通过Curve(曲线)来描述动画过程,我们把匀速动画称为线性的(Curves.linear),而非匀速动画称为非线性的。

要使用非匀速动画,我们可以通过CurvedAnimation来指定动画的曲线,如:

  1. final CurvedAnimation curve =
  2. CurvedAnimation(parent: controller, curve: Curves.easeIn);

Curve的关键是实现一个transform方法,就是

 transform(double t) → double 

t是时间点,取值是0到1之间。返回值是动画的进展,取值也是0到1之间。

3. AnimationController

AnimationController派生自Animation<double>,因此可以在需要Animation对象的任何地方使用。 但是,AnimationController具有控制动画的其他方法,包含动画的启动forward()、停止stop() 、反向播放 reverse()等方法。AnimationController可以在一段时间里生成一个区间(默认是0.0到1.0)中的值:

  1. final AnimationController controller = AnimationController(
  2. duration: const Duration(milliseconds: 2000),
  3. lowerBound: 10.0,
  4. upperBound: 20.0,
  5. vsync: this
  6. );

4. Tween

默认情况下,AnimationController对象值的范围是[0.0,1.0]。如果我们需要构建UI的动画值在不同的范围或不同的数据类型,则可以使用Tween来添加映射以生成不同的范围或数据类型的值。例如:

  1. final Tween colorTween =
  2. ColorTween(begin: Colors.transparent, end: Colors.black54);

Tween继承自Animatable<T>,而不是继承自Animation<T>Animatable中主要定义动画值的映射规则。

Tween对象不存储任何状态,相反,它提供了evaluate(Animation<double> animation)方法,它可以获取动画当前映射值。 Animation对象的当前值可以通过value()方法取到。evaluate函数还执行一些其他处理,例如分别确保在动画值为0.0和1.0时返回开始和结束状态。

要使用 Tween 对象,需要调用其animate()方法,然后传入一个控制器对象。例如,以下代码在 500 毫秒内生成从 0 到 255 的整数值。

  1. final AnimationController controller = AnimationController(
  2. duration: const Duration(milliseconds: 500),
  3. vsync: this,
  4. );
  5. Animation<int> alpha = IntTween(begin: 0, end: 255).animate(controller);

5.CurveTween

CurveTween有别于Tween的是,它不能指定动画值的上下限,它的作用是根据指定的Curve来转换animation的值。Tween可以通过chain来组合CurveTween,实现指定区间的动画曲线。

6.CurvedAnimation

CurvedAnimation是将动画曲线应用到已有Animation,来生成一个新的Animation。

简单论述下几者的关系:animated之间可以chain;animated可以对已有animation进行animate来生成新的animation,animation可以drive animated来生成animation;controller是一个完备的animation,可以直接使用,除非数值区间或者动画曲线不符合要求。基于以上论述,以下代码是等效的:

  1. //1
  2. final Animation<double> cAnimation1 = CurvedAnimation(
  3. parent: controller,
  4. curve: Curves.ease,
  5. );
  6. final Animation<double> animation = Tween(beigin:0.0, end:2.0).animate(cAnimation1)
  7. //2
  8. final Animation<double> animation = Tween(beigin:0.0, end:2.0).chain(CurveTween(curve: Curves.ease)).animate(controller)
  9. //3
  10. final Animation<double> cAnimation2 = controller.drive(
  11. CurveTween(curve: Curves.ease),
  12. );
  13. final Animation<double> animation = Tween(beigin:0.0, end:2.0).animate(cAnimation2)

2.实现细节

1.vsync:this

我们看到前面AnimationController构造函数里,有个vsync参数,我们把this传了进去,而且我们还在this所指向的state混入了SingleTickerProviderStateMixin。我们来看看实现源码:

vsync 对象创建了一个 _ticker ,而传入的 _tick 是一个回调函数。查看源码它是用于更新 value ,也就是说 AnimationController.value 是在此回调中发生改变。

我们将视角回调 _ticker = vsync.createTicker(_tick); 来看看 Ticker

2.Ticker

  1. class Ticker {
  2. TickerFuture? _future;
  3. bool get muted => _muted;
  4. bool _muted = false;
  5. set muted(bool value) {
  6. if (value == muted)
  7. return;
  8. _muted = value;
  9. if (value) {
  10. unscheduleTick();
  11. } else if (shouldScheduleTick) {
  12. scheduleTick();
  13. }
  14. }
  15. bool get isTicking {
  16. if (_future == null)
  17. return false;
  18. if (muted)
  19. return false;
  20. if (SchedulerBinding.instance!.framesEnabled)
  21. return true;
  22. if (SchedulerBinding.instance!.schedulerPhase != SchedulerPhase.idle)
  23. return true; // for example, we might be in a warm-up frame or forced frame
  24. return false;
  25. }
  26. @protected
  27. bool get shouldScheduleTick => !muted && isActive && !scheduled;
  28. void _tick(Duration timeStamp) {
  29. assert(isTicking);
  30. assert(scheduled);
  31. _animationId = null;
  32. _startTime ??= timeStamp;
  33. _onTick(timeStamp - _startTime!);
  34. if (shouldScheduleTick)
  35. scheduleTick(rescheduling: true);
  36. }
  37. @protected
  38. void scheduleTick({ bool rescheduling = false }) {
  39. assert(!scheduled);
  40. assert(shouldScheduleTick);
  41. _animationId = SchedulerBinding.instance!.scheduleFrameCallback(_tick, rescheduling: rescheduling);
  42. }
  43. @protected
  44. void unscheduleTick() {
  45. if (scheduled) {
  46. SchedulerBinding.instance!.cancelFrameCallbackWithId(_animationId!);
  47. _animationId = null;
  48. }
  49. assert(!shouldScheduleTick);
  50. }
  51. @mustCallSuper
  52. void dispose() {
  53. if (_future != null) {
  54. final TickerFuture localFuture = _future!;
  55. _future = null;
  56. assert(!isActive);
  57. unscheduleTick();
  58. localFuture._cancel(this);
  59. }
  60. }
  61. }

TickerSchedulerBinding 驱动。flutter 每绘制一帧就会回调 Ticker._onTick(),所以每绘制一帧 AnimationController.value 就会发生变化。

接下来看一下 Ticker 其他成员与方法:

  • muted : 设置为 ture 时钟仍然可以运行,但不会调用该回调。
  • isTicking: 是否可以在下一帧调用其回调,如设备的屏幕已关闭,则返回false。
  • _tick(): 时间相关的计算交给 _onTick(),受到 muted 影响。
  • scheduleTick(): 将 _tick() 回调交给 SchedulerBinding 管理,flutter 每绘制一帧都会调用它。
  • unscheduleTick(): 取消回调的监听。

_onTick在AnimationController中实现:

这里主要更新value、status以及notifyListeners。对于renderObject,调用notifyListeners会调用markNeedsPaint方法,在下一帧进行重绘。

把_onTick传入创建Ticker的方法:

3.SingleTickerProviderStateMixin

  1. @optionalTypeArgs
  2. mixin SingleTickerProviderStateMixin<T extends StatefulWidget> on State<T> implements TickerProvider {
  3. Ticker? _ticker;
  4. @override
  5. Ticker createTicker(TickerCallback onTick) {
  6. _ticker = Ticker(onTick, debugLabel: kDebugMode ? 'created by $this' : null)
  7. return _ticker!;
  8. }
  9. @override
  10. void dispose() {
  11. super.dispose();
  12. }
  13. @override
  14. void didChangeDependencies() {
  15. if (_ticker != null)
  16. _ticker!.muted = !TickerMode.of(context);
  17. super.didChangeDependencies();
  18. }
  19. }

SingleTickerProviderStateMixin 就是我们在 Statevsync:this ,它做了一个桥梁连接了 StateTicker

以上源码重要一点:是在 didChangeDependencies() 中将 muted = !TickerMode.of(context) 初始化一遍。 TickerModeInheritedWidgetwidget 中的属性。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/320181?site
推荐阅读
相关标签
  

闽ICP备14008679号