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来指定动画的曲线,如:

Dart 复制代码
final CurvedAnimation curve =
    CurvedAnimation(parent: controller, curve: Curves.easeIn);

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

Dart 复制代码
 transform(double t) → double 

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

3. AnimationController

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

Dart 复制代码
final AnimationController controller = AnimationController( 
 duration: const Duration(milliseconds: 2000), 
 lowerBound: 10.0,
 upperBound: 20.0,
 vsync: this
);

4. Tween

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

Dart 复制代码
final Tween colorTween =
    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 的整数值。

Dart 复制代码
final AnimationController controller = AnimationController(
  duration: const Duration(milliseconds: 500), 
  vsync: this,
);
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,可以直接使用,除非数值区间或者动画曲线不符合要求。基于以上论述,以下代码是等效的:

Dart 复制代码
//1
final Animation<double> cAnimation1 = CurvedAnimation(
  parent: controller,
  curve: Curves.ease,
);
final Animation<double> animation = Tween(beigin:0.0, end:2.0).animate(cAnimation1)

//2
final Animation<double> animation = Tween(beigin:0.0, end:2.0).chain(CurveTween(curve: Curves.ease)).animate(controller)

//3
final Animation<double> cAnimation2 = controller.drive(
  CurveTween(curve: Curves.ease),
);
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

Dart 复制代码
class Ticker {

  TickerFuture? _future;

  bool get muted => _muted;
  bool _muted = false;
  set muted(bool value) {
    if (value == muted)
      return;
    _muted = value;
    if (value) {
      unscheduleTick();
    } else if (shouldScheduleTick) {
      scheduleTick();
    }
  }

  bool get isTicking {
    if (_future == null)
      return false;
    if (muted)
      return false;
    if (SchedulerBinding.instance!.framesEnabled)
      return true;
    if (SchedulerBinding.instance!.schedulerPhase != SchedulerPhase.idle)
      return true; // for example, we might be in a warm-up frame or forced frame
    return false;
  }

  @protected
  bool get shouldScheduleTick => !muted && isActive && !scheduled;

  void _tick(Duration timeStamp) {
    assert(isTicking);
    assert(scheduled);
    _animationId = null;

    _startTime ??= timeStamp;
    _onTick(timeStamp - _startTime!);

    if (shouldScheduleTick)
      scheduleTick(rescheduling: true);
  }


  @protected
  void scheduleTick({ bool rescheduling = false }) {
    assert(!scheduled);
    assert(shouldScheduleTick);
    _animationId = SchedulerBinding.instance!.scheduleFrameCallback(_tick, rescheduling: rescheduling);
  }

  @protected
  void unscheduleTick() {
    if (scheduled) {
      SchedulerBinding.instance!.cancelFrameCallbackWithId(_animationId!);
      _animationId = null;
    }
    assert(!shouldScheduleTick);
  }

  @mustCallSuper
  void dispose() {
    if (_future != null) {
      final TickerFuture localFuture = _future!;
      _future = null;
      assert(!isActive);
      unscheduleTick();
      localFuture._cancel(this);
    }
  }
}

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

Dart 复制代码
@optionalTypeArgs
mixin SingleTickerProviderStateMixin<T extends StatefulWidget> on State<T> implements TickerProvider {
  Ticker? _ticker;

  @override
  Ticker createTicker(TickerCallback onTick) {
    _ticker = Ticker(onTick, debugLabel: kDebugMode ? 'created by $this' : null)
    return _ticker!;
  }

  @override
  void dispose() {
    super.dispose();
  }

  @override
  void didChangeDependencies() {
    if (_ticker != null)
      _ticker!.muted = !TickerMode.of(context);
    super.didChangeDependencies();
  }

}

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

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

相关推荐
sg_knight2 小时前
Flutter性能优化终极指南:从JIT到AOT的深度调优
前端·flutter·性能优化·web·dart
zonda的地盘4 小时前
[Get 源码] GetPageRoute 与 GetxController 的自动回收机制
flutter
明似水5 小时前
Flutter iOS 项目中 VolumeControllerPlugin 报错解决方案
flutter·ios
折翅鵬5 小时前
Flutter报错:Warning: CocoaPods is installed but broken
flutter·cocoapods
pengyu6 小时前
【Flutter 状态管理 - 壹】 | 提升对声明式编程的认知
android·flutter·dart
zonda的地盘20 小时前
Flutter PlatformViewLink vs Texture
flutter
zonda的地盘21 小时前
‌AndroidView 配置 TLHC(Texture Layer Hybrid Composition)模式指南
flutter
zonda的地盘21 小时前
‌PlatformInterface 的双向通信能力解析
flutter
90后的晨仔1 天前
Dart 中的聚合类型与容器类型详解
前端·flutter
90后的晨仔1 天前
dart 中的位置参数和命名参数的区别?
前端·flutter