implicit_animations.dart阅读

这个文件定义了Flutter很多的基础动画,主要是基于Container的各个属性的动画封装,它的实现原理根据定义的时间曲线获取对应的补间值, 然后不断的更新widget的配置信息(如果有变动), 在vsync扫描时就会不断的更新每一帧的界面

连在一起看就成了动画效果, 实现它需要有时间函数曲线, 补间值, vsync同步信号

1.视图更新的同步信号(Vsync)

  • Vsync信息由flutter engine提供, 当前的widget只需要with一个 TickerProviderStateMixin 类, 就能监听到系统的vsync信号, 以下面的 SingleTickerProvider 为栗子, 它直接控制和监听 SchedulerBinding.instance 来实现vsync的回调事件转发给AnimationController

    mixin SingleTickerProviderStateMixin<T extends StatefulWidget> on State<T> implements TickerProvider {
      //创建ticker,这里创建了一个tiker,而 `TickerProvider` 是个抽象类,所以vsync肯定和它有关


    @override


    Ticker createTicker(TickerCallback onTick) { ...


    _ticker = Ticker(onTick, debugLabel: kDebugMode ? 'created by $this' : null);


    @override


    void didChangeDependencies() { ...


    _ticker.muted = !TickerMode.of(context);


    }


    class Ticker {


    //Ticker的初始化只有一个参数,没有继承其他的内


    Ticker(this._onTick, { this.debugLabel }) {


    //控制ticker的事件队列,确保次序正确


    TickerFuture _future;


    //决定当前ticker是否只是监听vsync不执行callback,避免视图不现实的内存开销


    set muted(bool value) {


    isMuted ...


    unscheduleTick();


    shouldScheduleTick ...


    scheduleTick();


    //开启一个定时刷新,如果当前能执行动画(!isMetued)则执行它的callback事件


    TickerFuture start() {


    ...


    if (shouldScheduleTick) {


    scheduleTick();


    ...


    return _future;


    }


    void stop({ bool canceled = false }) { ...


    unscheduleTick();


    if (canceled) {


    localFuture._cancel(this);


    } else {


    localFuture._complete();


    }


    }


    //记录animationId,避免重复schedule


    @protected


    bool get scheduled => _animationId != null;


    //执行tick的三要素


    @protected


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


    //


    void _tick(Duration timeStamp) {..


    _onTick(timeStamp - _startTime);


    if (shouldScheduleTick)


    scheduleTick(rescheduling: true);


    }


    //最终将tick事件发送给 `SchedulerBinding.instance` ,持续定于window的每一帧


    @protected


    void scheduleTick({ bool rescheduling = false }) { ...


    _animationId = SchedulerBinding.instance.scheduleFrameCallback(_tick, rescheduling: rescheduling);


    }


    @protected


    void unscheduleTick() { ...


    SchedulerBinding.instance.cancelFrameCallbackWithId(_animationId);


    }


    //用于接管另外一个ticker,释放原来的ticker,使用自己的ticker事件


    void absorbTicker(Ticker originalTicker) {...


    if (shouldScheduleTick)


    scheduleTick();


    originalTicker._future = null; // so that it doesn't get disposed when we dispose of originalTicker


    originalTicker.unscheduleTick();


    }


    originalTicker.dispose();


    }

2. 动画的时间曲线

  • 通过Widget初始化时构造一个AnimationController, 它接收一个总的时常, 同是提供了ticker构造工厂函数, 而ticker可以监听vsync信号,
  • 通过监听vsync事件和frameCallback的duration, 再结合动画的时间区间, 计算出每一帧绘制时的时间, 这样就生成一个动画的时间曲线
  • 除了生成时间曲线以外, 它还提供了动画控制的能力, 比如重复开始, 反转, 每执行一个操作它都会去从新生成一个当前的时间值, 确保动画的进度按照代码设定的逻辑执行

    class _AnimatedWidgetDemoState extends State<AnimatedWidgetDemo>
        with SingleTickerProviderStateMixin {


    AnimationController _animationController;


    @override


    void initState() {


    super.initState();


    _animationController = AnimationController(


    vsync: this,


    duration: Duration(seconds: 2),


    )..repeat();


    }


    //在AnimationController创建的时候,会创建ticker


    class AnimationController ...


    AnimationController({ ...


    _ticker = vsync.createTicker(_tick);


    ...


    }


    void _tick(Duration elapsed) { ...


    //事件比例计算,也就是当前的动画进度


    _value = _simulation.x(elapsedInSeconds).clamp(lowerBound, upperBound) as double;


    notifyListeners();


    _checkStatusChanged();


    }


    //重复执行


    TickerFuture repeat({ double min, double max, bool reverse = false, Duration period }) {


    ...


    stop();


    return _startSimulation(_RepeatingSimulation(_value, min, max, reverse, period, _directionSetter));


    }


    //开始执行动画


    TickerFuture forward({ double from }) { ...


    _direction = _AnimationDirection.forward;


    return _animateToInternal(upperBound);


    }


    TickerFuture _animateToInternal(double target, { Duration duration, Curve curve = Curves.linear }) {...


    stop(); ...


    return _startSimulation(_InterpolationSimulation(_value, target, simulationDuration, curve, scale));


    }


    TickerFuture _startSimulation(Simulation simulation) { ...


    _value = simulation.x(0.0).clamp(lowerBound, upperBound) as double;


    final TickerFuture result = _ticker.start();


    _checkStatusChanged();


    return result;


    }


    //可以看到AnimationController对动画的控制最终都会传递给 `Ticker` ,它主要负责时间进度的计算,计算完毕后根据代码设定的逻辑通知ticker是否需要监听下一帧的回调事件。
  • 以下为Flutter定义的Curve曲线,用于适配X,Y之间的比例关系

    ParametricCurve (curves.dart)
        _BottomSheetSuspendedCurve (scaffold.dart)


    _BottomSheetSuspendedCurve (bottom_sheet.dart)


    Curve2D (curves.dart)


    CatmullRomSpline (curves.dart)


    Curve (curves.dart)


    FlippedCurve (curves.dart)


    _Linear (curves.dart)


    ElasticInOutCurve (curves.dart)


    SawTooth (curves.dart)


    Cubic (curves.dart)


    ElasticOutCurve (curves.dart)


    Interval (curves.dart)


    ElasticInCurve (curves.dart)


    _BounceInOutCurve (curves.dart)


    CatmullRomCurve (curves.dart)


    _BounceOutCurve (curves.dart)


    Threshold (curves.dart)


    _DecelerateCurve (curves.dart)


    _BounceInCurve (curves.dart)

3. 补间值

  • 它代表的是动画在每一帧渲染时, Widget(RenderObject的最终提交渲染信息)的属性的值, 简单的动画一般只有开始和结束2个值,需要由时间曲线函数动画的起始值共同作用来完成
  • 动画过程也是线性的, 随着时间的推移, 按线性规律逐步完成的, 为了动画过渡的更加完美, 需要额外对补间值进行加工, 在保证时间因子是线性移动的前提下, 通过引入Curve函数, 每个时间节点的 属性值按照重新计算, 这样我们的补间值就变得丰富多样。
  • 按照这个规律我们可以把线性的补间值变成各种变化规律的补间值

    在flutter仓库的源码中定义了很多的动画

  • 补间值转换, 在flutter的动画设计中可以把它看作是一个以时间为 X 轴, 动画进度为 Y 的函数, 函数的具体实现都封装在 transform方法中

  • 基于此规律我们可以定义自己Curve, 生成自己的曲线函数, 这样就能实时的调整动画的补间值, 以下为Flutter目前现有的一些Curve,基本上满足了绝大部分应用场景。

  • 补间值的具体类, 基本上满足了绝大部分的场景, 直接就能使用

    Animatable (tween.dart)
        Tween (tween.dart)


    AlignmentTween (tweens.dart)


    FractionalOffsetTween (tweens.dart)


    AlignmentGeometryTween (tweens.dart)


    RelativeRectTween (transitions.dart)


    Matrix4Tween (implicit_animations.dart)


    BoxConstraintsTween (implicit_animations.dart)


    EdgeInsetsTween (implicit_animations.dart)


    DecorationTween (implicit_animations.dart)


    BorderTween (implicit_animations.dart)


    BorderRadiusTween (implicit_animations.dart)


    TextStyleTween (implicit_animations.dart)


    EdgeInsetsGeometryTween (implicit_animations.dart)


    ThemeDataTween (theme.dart)


    _FontWeightTween (sliding_segmented_control.dart)


    ShapeBorderTween (material.dart)


    _InputBorderTween (input_decorator.dart)


    MaterialPointArcTween (arc.dart)


    ReverseTween (tween.dart)


    StepTween (tween.dart)


    SizeTween (tween.dart)


    ColorTween (tween.dart)


    ConstantTween (tween.dart)


    IntTween (tween.dart)


    RectTween (tween.dart)


    MaterialRectCenterArcTween (arc.dart)


    MaterialRectArcTween (arc.dart)

示例Demo

  • 下面以最常用的 AnimatedContainer 为例, 通过它来构造一个动画, 下面是一个简易的Demo,

    通过点击手势来触发ValueListenableBuilder重新构建, 切换动画的的初始值

AnimatedContainer示例
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

class AnimatedContainerDemo extends StatelessWidget {
  final CheckStatusListener statusListener = CheckStatusListener(true);

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        statusListener.value = !statusListener.value;
      },
      child: ValueListenableBuilder<bool>(
          valueListenable: statusListener,
          builder: (BuildContext context, bool selected, Widget child) {
            return Center(
              child: AnimatedContainer(
                width: selected ? 200.0 : 100.0,
                height: selected ? 100.0 : 200.0,
                color: selected ? Colors.red : Colors.blue,
                alignment: selected
                    ? Alignment.center
                    : AlignmentDirectional.topCenter,
                duration: Duration(seconds: 2),
                curve: Curves.fastOutSlowIn,
                child: FlutterLogo(size: 75),
              ),
            );
          }),
    );
  }
}

class CheckStatusListener extends ValueNotifier<bool>
    implements ValueListenable<bool> {
  CheckStatusListener(bool isSelected) : super(isSelected);
}

AnimatedContainer代码实现

  • 从构造方法就可以看出它支持设置多种不同的补间值

    class AnimatedContainer extends ImplicitlyAnimatedWidget {
    AnimatedContainer({


    Key key,


    this.alignment,


    this.padding,


    Color color,


    Decoration decoration,


    this.foregroundDecoration,


    double width,


    double height,


    BoxConstraints constraints,


    this.margin,


    this.transform,


    this.child,


    Curve curve = Curves.linear,


    @required Duration duration,


    VoidCallback onEnd,


    })


    ...


    class _AnimatedContainerState extends AnimatedWidgetBaseState<AnimatedContainer> {


    AlignmentGeometryTween _alignment;


    EdgeInsetsGeometryTween _padding;


    DecorationTween _decoration;


    DecorationTween _foregroundDecoration;


    BoxConstraintsTween _constraints;


    EdgeInsetsGeometryTween _margin;


    Matrix4Tween _transform;


    //它的父类也是一个空壳,只是包装了setState方法,这个方法会触发这个widget及children重建,慎用,不要将复杂过多的逻辑用这种方案时间,一般使用与小部件的widget,继续向上查找它的Ancestor


    abstract class AnimatedWidgetBaseState<T extends ImplicitlyAnimatedWidget> extends ImplicitlyAnimatedWidgetState<T> {


    @override


    void initState() {


    super.initState();


    controller.addListener(_handleAnimationChanged);


    }


    void _handleAnimationChanged() {


    setState(() { /* The animation ticked. Rebuild with new animation value */ });


    }


    }


    //父类实现了tickerProvider协议,可以通过Tiker发送监听屏幕刷新事件,同时也持有了`AnimationController`用于控制基本的动画行为


    abstract class ImplicitlyAnimatedWidgetState<T extends ImplicitlyAnimatedWidget> extends State<T> with SingleTickerProviderStateMixin<T> {


    @protected


    AnimationController get controller => _controller;


    Animation<double> _animation;


    @override


    void initState() {


    super.initState();


    _controller = AnimationController( ...


    _controller.addStatusListener((AnimationStatus status) { ...


    _updateCurve(); //这里将 Curve函数和时间关联在一起,它和Curve将Tween关联在一起是一样的,因为最终都是通过3个数的乘积的到最终补间值


    _constructTweens(); //初始化设置子类的Tween


    didUpdateTweens(); //供子类在视图更新时刷新Tween


    }


    @override


    void didUpdateWidget(T oldWidget) {


    super.didUpdateWidget(oldWidget);


    ...


    _updateCurve();


    _controller.duration = widget.duration;


    if (_constructTweens()) {


    forEachTween((Tween<dynamic> tween, dynamic targetValue, TweenConstructor<dynamic> constructor) {


    _updateTween(tween, targetValue);


    return tween;


    });


    _controller


    ..value = 0.0


    ..forward();


    didUpdateTweens();


    }


    }


    //更新widget自带的curve函数,更新动画的时间进度,间接修改补间值


    void _updateCurve() { ...


    _animation = CurvedAnimation(parent: _controller, curve: widget.curve);


    //自动更新补间动画值


    void _updateTween(Tween<dynamic> tween, dynamic targetValue) {


    ..begin = tween.evaluate(_animation)


    ..end = targetValue;


    }


    //充分利多态的特性,实现子类Tween的构造,并执行


    bool _constructTweens() {


    bool shouldStartAnimation = false;


    forEachTween((Tween<dynamic> tween, dynamic targetValue, TweenConstructor<dynamic> constructor) {


    if (targetValue != null) {


    tween ??= constructor(targetValue);


    if (_shouldAnimateTween(tween, targetValue))


    shouldStartAnimation = true;


    } else {


    tween = null;


    }


    return tween;


    });


    return shouldStartAnimation;


    }


    //子类通过重写这个方法


    //1. 子类传递 `Tween<T> tween, T targetValue,`给父类,举个栗子,Tween<T> tween就是子类的 AlginmentTween,targetValue就是Aliginment.topLeft


    //2. 父类调用`constructor`方法,子类生成对应的AlginmentTween,这样子类就持有了AlginmentTween,在子类的build方法中获取父类的`animation`,就能实时的改变Contianer相关的属性了(此处的tween回传给父类主要是为了检测动画进度)


    //typedef TweenVisitor<T> = Tween<T> Function(Tween<T> tween, T targetValue, TweenConstructor<T> constructor);


    @protected


    void forEachTween(TweenVisitor<dynamic> visitor);


    //用于hook子视图在widgetupdate时更新补间值


    @protected


    void didUpdateTweens() { }


    }
  • 子类_AnimatedContainerState在每次构建中更新当前的补间值

    class _AnimatedContainerState extends AnimatedWidgetBaseState<AnimatedContainer> {...
      @override


    Widget build(BuildContext context) {


    return Container(


    child: widget.child,


    alignment: _alignment?.evaluate(animation),


    padding: _padding?.evaluate(animation),


    decoration: _decoration?.evaluate(animation),


    foregroundDecoration: _foregroundDecoration?.evaluate(animation),


    constraints: _constraints?.evaluate(animation),


    margin: _margin?.evaluate(animation),


    transform: _transform?.evaluate(animation),


    );


    }
  • 创建Tween的具体实现在父类初始化是触发forEachTween来创建子类的Tween

    ``` dart
    abstract class ImplicitlyAnimatedWidgetState<T extends ImplicitlyAnimatedWidget> extends State<T> with SingleTickerProviderStateMixin<T> { ...


    void initState() { ...


    _constructTweens(); -> forEachTween((Tween<dynamic> tween, dynamic targetValue, TweenConstructor<dynamic> constructor) {


    void didUpdateWidget(T oldWidget) {...


    forEachTween((Tween<dynamic> tween, dynamic targetValue, TweenConstructor<dynamic> constructor) {


    子类重写`forEachTween`,传入当前的`_alignmentTween`(父类动画进度检查用)和`widget.alignmentValue`


    class _AnimatedContainerState extends AnimatedWidgetBaseState<AnimatedContainer> { ...


    @override


    void forEachTween(TweenVisitor<dynamic> visitor) {


    _alignment = visitor(_alignment, widget.alignment, (dynamic value) =>


    AlignmentGeometryTween(begin: value as AlignmentGeometry)) as AlignmentGeometryTween;//用于父类构造通过Tween开启动画用


    ...


    }


    ```

小结

  • 理解AnimatedContainer动画的封装主要是要了解forEachTween的执行过程,它是一个嵌套函数,同时实现了父子协同工作的方式, AnimationController通过持有Ticker,可以很方便的注册和取消系统下一帧的回调,实时的计算当前的动画进度
  • 动画的三要素:

    • Vsync信号,视图的刷新周期
    • Tween: 动画的补间值
    • TimeFunction: 时间函数,动画在某个时刻内它的动画进度值
  • 复杂的动画都是基于动画的 补间值和动画时间函数计算得来的.