Flutter原理篇:Animation原理分析

Animation是动画的关键类,本节主要对flutter动画原理的分析,先看一个例子

class _MyAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
  Animation<double>? animation;
  AnimationController? _controller;

  initState() {
    super.initState();
    _controller = AnimationController(
        duration: const Duration(milliseconds: 2000), vsync: this);
    animation = Tween(begin: 0.0, end: 300.0).animate(_controller!)
      ..addListener(() {
        setState(() {
          // Animation对象内的值改变了
        });
      });
    _controller?.forward();
  }
  Widget build(BuildContext context) {
     return Container(
                color: Colors.red,
                width: animation!.value,
                height: animation!.value,
                );
  }

1.这个源码是将一个container从0x0放大到300x300 大小的动画

2.这里创建了animation和AnimationController的变量,这是动画能够运行的关键

3.animation变量添加了addListener监听,即在动画运行的时候不断的刷新widget进行重绘

4.animation通过监听器注册将animation的value值传递给container并刷新绘制

5.AnimationController是驱动者,Tween则提供了补间动画的插值模型。animation作为最终的调用出口

一、AnimationController

我们先来看看AnimationController的构造函数都做了什么。因为AnimationController是构造动画的第一步

//AnimationController类的构造函数
AnimationController({
    double? value,
    this.duration,
    this.reverseDuration,
    this.debugLabel,
    this.lowerBound = 0.0,
    this.upperBound = 1.0,
    this.animationBehavior = AnimationBehavior.normal,
    required TickerProvider vsync,
  }) : assert(upperBound >= lowerBound),
       _direction = _AnimationDirection.forward {
    if (kFlutterMemoryAllocationsEnabled) {
      _maybeDispatchObjectCreation();
    }
    //_tick是函数,监听vsync的变化就会调用
    _ticker = vsync.createTicker(_tick);
    //初始化动画值,这里的lowerBound和upperBound是动画参数的最小值和最大值
    _internalSetValue(value ?? lowerBound);
  }

//更新当前动画的状态
void _internalSetValue(double newValue) {
    //构造AnimationController的时候初始化_value值,同时初始化动画状态_status
    _value = clampDouble(newValue, lowerBound, upperBound);
    if (_value == lowerBound) {
      //动画未开始
      _status = AnimationStatus.dismissed;
    } else if (_value == upperBound) {
      //动画已经结束
      _status = AnimationStatus.completed;
    } else {
      //动画进行中
      _status = (_direction == _AnimationDirection.forward) ?
        AnimationStatus.forward :
        AnimationStatus.reverse;
    }
  }

   

1.AnimationController在创建初期先初始化_ticker的vsync监听函数,同时传给widget的state持有一份,同时初始化AnimationController内部的_value_status的初始值

2.初始动画值从lowerBound(0.0)upperBound(1.0)之间。动画值有四种状态,开始状态dismissed,进行状态forward,结束状态completed和反向状态reverse

二、Tween插值器

接下来看看Tween的源码(动画插值器)

class Tween<T extends Object?> extends Animatable<T> {
  /// Creates a tween.
  Tween({
    this.begin,
    this.end,
  });
  //这里就是开始了线性插值的过程
  @override
  T lerp(double t) {
      //这里开始做运算得到最终值
      return (begin as dynamic) + ((end as dynamic) - (begin as dynamic)) * t as T;
  }
  @override
  T transform(double t) {
    if (t == 0.0) {
      //这里的T实际上就是Tween<Double>内传的泛型,如Double。
      return begin as T;
    }
    if (t == 1.0) {
      return end as T;
    }
    return lerp(t);
  }
}
abstract class Animatable<T> {
  T transform(double t);
  //这里就是_AnimatedEvaluation的value方法,相当于transform(animationController.value)
  T evaluate(Animation<double> animation) => transform(animation.value);
  Animation<T> animate(Animation<double> parent) {
    return _AnimatedEvaluation<T>(parent, this);
  }
  Animatable<T> chain(Animatable<double> parent) {
    return _ChainedEvaluation<T>(parent, this);
  }
}

class _AnimatedEvaluation<T> extends Animation<T> with AnimationWithParentMixin<double> {
  //这里的parent就是AnimationController,_evaluatable就是Tween
  _AnimatedEvaluation(this.parent, this._evaluatable);

  @override
  final Animation<double> parent;

  final Animatable<T> _evaluatable;

  //这里相当于Tween.evaluate(AnimationController),这样写方便看。
  @override
  T get value => _evaluatable.evaluate(parent);

}

知识点:所谓的插值器就是用于控制动画的速度的。我们知道,像补间动画,只需要定义动画的第一帧和最后一帧,系统就能将中间帧都生成出来,而生成这个中间帧的过程就叫插值,上图中lerp()就是进行了插值运算,

 1.AnimationController 会在 lowerBound 和 upperBound 之间进行过渡取值,但如果我们需要 0.0-1.0 区间之外的值或者其他类型的值来设置动画,就需要用到插值器(Tween)了,它可以在 begin 和 end 之间进行插值补间帧。

2.上面的逻辑展示了Tween的插值过程,插值的参数是AnimationController.value的值。这个值在每一帧都会进行更新,Tween会根据这个值完成线性插值的过程。Tween的值最终会存入到_AnimatedEvaluation中,即Animation

Flutter 提供了一系列 Tween 的子类供我们使用(插值器):

  • ReverseTween,反向插值,其参数是一个 Tween 对象,会反向评估它的值。
  • ColorTween,颜色插值器,可传入如Color.blue等值
  • SizeTween,Size 类型的插值器,可用于涉及到 SizedBox 之类的尺寸
  • RectTween,Rect 类型的插值器
  • IntTween,Int 类型的插值器
  • StepTween,步数插值器
  • ConstantTween,常量值插值器,返回的总是begin的值,永远不变。

3.每个插值器都有自己实现的lerp()插值逻辑。其中IntTween和StepTween都是传int,唯一的区别是int直接使用的是整数值,而IntTween最终计算的值会进行四舍五入,而StepTween会返回小于或等于传入值的最大整数

三、动画驱动机制(forward)

当我们了解了动画的值是如何使用的,现在我们看看动画是如何驱动的

AnimationController.forwad源码

//AnimationController
//驱动开始的地方
TickerFuture forward({ double? from }) {
    //初始化动画运行方向,forward是正向,reverse反向
    _direction = _AnimationDirection.forward;
    if (from != null) {
      //执行forward的同时,更新动画的值。
      value = from;
    }
    return _animateToInternal(upperBound);
  }
TickerFuture _animateToInternal(double target, { Duration? duration, Curve curve = Curves.linear }) {
    double scale = 1.0;
    if (SemanticsBinding.instance.disableAnimations) {
      switch (animationBehavior) {
        case AnimationBehavior.normal:
          // Since the framework cannot handle zero duration animations, we run it at 5% of the normal
          // duration to limit most animations to a single frame.
          // Ideally, the framework would be able to handle zero duration animations, however, the common
          // pattern of an eternally repeating animation might cause an endless loop if it weren't delayed
          // for at least one frame.
          scale = 0.05;
        case AnimationBehavior.preserve:
          break;
      }
    }
    Duration? simulationDuration = duration;
    if (simulationDuration == null) {
      final double range = upperBound - lowerBound;
      //target就是传入的upperBound,最大值,如1.0,_value就是动画进度值
      //这里求得动画运行的百分比
      final double remainingFraction = range.isFinite ? (target - _value).abs() / range : 1.0;
      final Duration directionDuration =
        (_direction == _AnimationDirection.reverse && reverseDuration != null)
        ? reverseDuration!
        : this.duration!;
      //从上面获得的百分比进行时长的计算
      simulationDuration = directionDuration * remainingFraction;
    } else if (target == value) {
      // Already at target, don't animate.
      simulationDuration = Duration.zero;
    }
    stop();
    if (simulationDuration == Duration.zero) {
      if (value != target) {
        _value = clampDouble(target, lowerBound, upperBound);
        notifyListeners();
      }
      _status = (_direction == _AnimationDirection.forward) ?
        AnimationStatus.completed :
        AnimationStatus.dismissed;
      _checkStatusChanged();
      return TickerFuture.complete();
    }
    return _startSimulation(_InterpolationSimulation(_value, target, simulationDuration, curve, scale));
  }

//_InterpolationSimulation是Simulation的实现类,是个模拟插值器
class _InterpolationSimulation extends Simulation {
    
   .....
}

1.第二步,进入到_animateToInternal进行动画时长(simulationDuration)的计算及开始动画逻辑

  • simulationDuration时长的计算,根据剩余值与总值的比例进行计算
  • simulationDuration为0的情况进行处理,动画已经结束,对_value字段进行赋值并通知更新
  • 执行_startSimulation开始动画的逻辑

2.先进行百分比计算(target - _value).abs() / range , 再进行时长计算simulationDuration = directionDuration * remainingFraction;其中directionDuration就是构造AnimationController的时候传入的duration动画时长。

3.动画结束时,会进行AnimationStatus状态的更新,注意,这里会根据不同的方向给于状态结束的不同值,即forwad则给定AnimationStatus.completed,反向reverse则给定AnimationStatus.dismissed

动画开始执行_startSimulation

//AnimationController..
TickerFuture _startSimulation(Simulation simulation) {
    _simulation = simulation;
    //截止到上一帧动画已消耗的时间参数
    _lastElapsedDuration = Duration.zero;
    //确保值在lowerBound和upperBound范围内取值
    _value = clampDouble(simulation.x(0.0), lowerBound, upperBound);
    //开始动画请求,驱动动画执行_ticker!.start();
    final TickerFuture result = _ticker!.start();
    _status = (_direction == _AnimationDirection.forward) ?
      AnimationStatus.forward :
      AnimationStatus.reverse;
    //动画状态的更新
    _checkStatusChanged();
    return result;
  }

//Ticker类
TickerFuture start() {
    //开始一个future方法,可以看成是一个待完成的动画
    _future = TickerFuture._();
    //开始请求心跳。
    if (shouldScheduleTick) {
      scheduleTick();
    }
    if (SchedulerBinding.instance.schedulerPhase.index > SchedulerPhase.idle.index &&
        SchedulerBinding.instance.schedulerPhase.index < SchedulerPhase.postFrameCallbacks.index) {
      _startTime = SchedulerBinding.instance.currentFrameTimeStamp;
    }
    return _future!;
  }

@protected
  void scheduleTick({ bool rescheduling = false }) {
    //通过SchedulerBinding注册_tick函数给心跳包,即每当vsync函数到来时就更新_tick回调函数
    _animationId = SchedulerBinding.instance.scheduleFrameCallback(_tick, rescheduling: rescheduling);
  }

1.第一步,更新AnimationStatus的动画执行状态。

2.第二步,调用_checkStatusChanged更新由addStatusListener注册的监听。即执行addStatusListener的回调告知AnimationStatus的状态。

3.第三步,注册vsync监听,等待监听回调ticker的_tick方法。

4.第四步,修正动画开始时间

在注册vsync监听前会先判断shouldScheduleTick是否为true

//是否处于静默状态
bool get muted => _muted;

//判断是否存在动画
bool get isActive => _future != null;

//是否正在等待心跳
@protected
bool get scheduled => _animationId != null;

//此变量即是判断是否需要一次心跳。
@protected
  bool get shouldScheduleTick => !muted && isActive && !scheduled;

该变量主要是判断是否需要心跳,即是否需要注册vsync监听

我们看看_tick方法做了什么

//Ticker
void _tick(Duration timeStamp) {
    assert(isTicking);
    assert(scheduled);
    _animationId = null;
    //记录动画首次开始的时间戳
    _startTime ??= timeStamp;
    //执行_onTick方法,即前面传进来的方法。
    _onTick(timeStamp - _startTime!);

    
    //判断是否继续进行vsync监听注册
    if (shouldScheduleTick) {
      scheduleTick(rescheduling: true);
    }
  }

//AnimationController
void _tick(Duration elapsed) {
    //动画已经执行了的时间
    _lastElapsedDuration = elapsed;
    //单位转换,将毫秒转换为秒
    final double elapsedInSeconds = elapsed.inMicroseconds.toDouble() / Duration.microsecondsPerSecond;
    //范围控制,同时执行插值操作
    _value = clampDouble(_simulation!.x(elapsedInSeconds), lowerBound, upperBound);
    //根据秒数判断动画是否完成
    if (_simulation!.isDone(elapsedInSeconds)) {
      //如果完成则更新状态
      _status = (_direction == _AnimationDirection.forward) ?
        AnimationStatus.completed :
        AnimationStatus.dismissed;
      //停止动画
      stop(canceled: false);
    }
    //广播更新_value值
    notifyListeners();
    _checkStatusChanged();
  }

总结:记录动画时间并更新value值,并广播通知更新value字段已变更(即更新widget)

1.动画第一次开始记录动画的开始时间,并记录动画已经执行的时长

2.根据时长判断动画是否已经完成,如果完成则更新动画完成的状态。

3.如果动画已经完成则调用stop方法停止动画。

4.更新value并广播使用该value的widget进行更新

5.判断是否继续注册vsync继续监听刷新动画。

//通知value值更新
  void notifyListeners() {
    final List<VoidCallback> localListeners = _listeners.toList(growable: false);
    for (final VoidCallback listener in localListeners) {
      InformationCollector? collector;
     
      try {
        if (_listeners.contains(listener)) {
          //这里开始通知监听器
          listener();
        }
      } catch (exception, stack) {
        
      }
    }
  }
//通知状态改变
void _checkStatusChanged() {
    final AnimationStatus newStatus = status;
    if (_lastReportedStatus != newStatus) {
      _lastReportedStatus = newStatus;
      notifyStatusListeners(newStatus);
    }
  }
//停止动画,并重置相关变量,调用ticker的stop方法处理
void stop({ bool canceled = true }) {
    _simulation = null;
    _lastElapsedDuration = null;
    _ticker!.stop(canceled: canceled);
  }
void stop({ bool canceled = false }) {
    if (!isActive) {
      return;
    }
    final TickerFuture localFuture = _future!;
    //重置相关状态
    _future = null;
    _startTime = null;
    assert(!isActive);
    //停止注册vsync的更新
    unscheduleTick();
    if (canceled) {
      localFuture._cancel(this);
    } else {
      localFuture._complete();
    }
  }

这里就只有两个操作,一个通知value值的更新,一个通知动画状态的改变

notifyListeners每次在帧更新的时候就通知value值的更新,通知更新动画状态。

  • 53
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

超盘守

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值