• 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(
                     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: 时间函数,动画在某个时刻内它的动画进度值
    • 复杂的动画都是基于动画的 补间值和动画时间函数计算得来的.

  • 相关阅读:
    zookeeper使用和原理探究(一)
    Zookeeper基本原理
    论照顾小孩与项目管理
    perl启动后台进程
    Oracle数据库迁移
    C# 语言Pagerank两种实现
    没文化真可怕--Silverlight 列冻结
    oracle中使用SQL递归语句的例子
    Visual Studio 2010 智能跟踪文件目录
    oracle wm_concat函数的应用(多行合成一行)
  • 原文地址:https://www.cnblogs.com/wwoo/p/implicitanimationsdart-yue-du.html
Copyright © 2020-2023  润新知