In depth understanding of the principle of Flutter animation

1, Overview []( http://gityuan.com/2019/07/13/flutter_animator/# I. overview)

Animation effect is very important for the user experience of the system. Good animation can make the user feel the interface more smooth and improve the user experience.

1.1 animation type []( http://gityuan.com/2019/07/13/flutter_animator/#11- Animation type)

There are two main categories of Flutter Animation:

  • Compensation Animation: given the initial and final values, the system automatically complements the animation of the intermediate frame
  • Physical Animation: animation that follows the laws of physics, realizes three physical effects: spring, damping and gravity

Common animation modes in the application process:

  • Animation list or mesh: for example, adding or deleting elements;
  • Shared element transition: for example, the transition animation of opening another page from the current page;
  • Staggered animations: such as partially or completely staggered animations.

1.2 class diagram []( http://gityuan.com/2019/07/13/flutter_animator/#12- Class diagram)

Core class:

  • Animation object is a very core class in the whole animation;
  • AnimationController is used to manage Animation;
  • CurvedAnimation process is a nonlinear curve;
  • Tween mending animation
  • Listeners and StatusListeners are used to listen for animation state changes.

AnimationStatus is an enumeration type with 4 values;

Value explain
dismissed Animation stops at start
forward Animation is drawn from top to bottom
reverse Animation reverse, end-to-end
completed Animation stops at the end

1.3 animation instance []( http://gityuan.com/2019/07/13/flutter_animator/#13- Animation instance)

 //[see subsection 2.1]
AnimationController animationController = AnimationController(
    vsync: this, duration: Duration(milliseconds: 1000));
Animation animation = Tween(begin: 0.0,end: 10.0).animate(animationController);
animationController.addListener(() {
  setState(() {});
});
 //[see subsection 2.2]
animationController.forward();

Description of the process:

  • AnimationController, as a subclass of Animation, generates a series of values when the screen is refreshed. By default, it is the value from 0 to 1.
  • Tween's animate() method comes from the parent class Animatable. The object type returned by this method is "AnimatedEvaluation". The core work of this object is to call tween's transform() through value;

Call chain:

AnimationController.forward
  AnimationController.\_animateToInternal
    AnimationController.\_startSimulation
      Ticker.start()
        Ticker.scheduleTick()
          SchedulerBinding.scheduleFrameCallback()
            SchedulerBinding.scheduleFrame()
              ...
                Ticker.\_tick
                  AnimationController.\_tick
                  Ticker.scheduleTick

2, Principle analysis []( http://gityuan.com/2019/07/13/flutter_animator/# II. Principle analysis)

2.1 AnimationController initialization []( http://gityuan.com/2019/07/13/flutter_animator/#21-animationcontroller Initialization)

[-> lib/src/animation/animation_controller.dart]

AnimationController({
  double value,
  this.duration,
  this.debugLabel,
  this.lowerBound = 0.0,
  this.upperBound = 1.0,
  this.animationBehavior = AnimationBehavior.normal,
  @required TickerProvider vsync,
}) : _direction = _AnimationDirection.forward {
  _ticker = vsync.createTicker(_tick);  //[see subsection 2.1.1]
  _internalSetValue(value ?? lowerBound); //[see subsection 2.1.3]
}

Description of the method:

  • In the initialization process of AnimationController, the initial values of duration and vsync are generally set;
  • Upper bound and lower bound cannot be empty, and upper bound must be greater than or equal to lower bound;
  • The default animation direction for creation is forward ("animationdirection. Forward");
  • Call createTicker() method of vsync object of type TickerProvider to create Ticker object;

As an abstract class, the main subclasses of TickerProvider are SingleTickerProviderStateMixin and TickerProviderStateMixin. The difference between these two classes is whether to support the creation of multiple tickerproviders. Here, SingleTickerProviderStateMixin is expanded as an example.

2.1.1 createTicker[](http://gityuan.com/2019/07/13/flutter_animator/#211-createticker)

[-> lib/src/widgets/ticker_provider.dart]

mixin SingleTickerProviderStateMixin<T extends StatefulWidget> on State<T> implements TickerProvider {
  Ticker _ticker;

  Ticker createTicker(TickerCallback onTick) {
     //[see subsection 2.1.2]
    _ticker = Ticker(onTick, debugLabel: 'created by $this');
    return _ticker;
  }

2.1.2 Ticker initialization []( http://gityuan.com/2019/07/13/flutter_animator/#212-ticker Initialization)

[-> lib/src/scheduler/ticker.dart]

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

  final TickerCallback _onTick;
}

Assign the "tick() method in the animationcontrolerd object to the" onTick "member variable of the Ticker object, and then take a look at the" tick "method.

2.1.3 _internalSetValue[](http://gityuan.com/2019/07/13/flutter_animator/#213-_internalsetvalue)

[-> lib/src/animation/animation_controller.dart ::AnimationController]

void _internalSetValue(double newValue) {
  _value = newValue.clamp(lowerBound, upperBound);
  if (_value == lowerBound) {
    _status = AnimationStatus.dismissed;
  } else if (_value == upperBound) {
    _status = AnimationStatus.completed;
  } else {
    _status = (_direction == _AnimationDirection.forward) ?
      AnimationStatus.forward :
      AnimationStatus.reverse;
  }
}

Initialize the animation status according to the current value value

2.2 forward[](http://gityuan.com/2019/07/13/flutter_animator/#22-forward)

[-> lib/src/animation/animation_controller.dart ::AnimationController]

TickerFuture forward({ double from }) {
  //Default to forward animation direction
  _direction = _AnimationDirection.forward;
  if (from != null)
    value = from;
  return _animateToInternal(upperBound); //[see subsection 2.3]
}

_AnimationDirection is an enumeration type with two values: forward and reverse. In other words, the function of this method is to slide forward from,

2.3 _animateToInternal[](http://gityuan.com/2019/07/13/flutter_animator/#23-_animatetointernal)

[-> lib/src/animation/animation_controller.dart ::AnimationController]

TickerFuture _animateToInternal(double target, { Duration duration, Curve curve = Curves.linear }) {
  double scale = 1.0;
  if (SemanticsBinding.instance.disableAnimations) {
    switch (animationBehavior) {
      case AnimationBehavior.normal:
        scale = 0.05;
        break;
      case AnimationBehavior.preserve:
        break;
    }
  }
  Duration simulationDuration = duration;
  if (simulationDuration == null) {
    final double range = upperBound - lowerBound;
    final double remainingFraction = range.isFinite ? (target - _value).abs() / range : 1.0;
    //Evaluate the remaining time of simulation animation based on the percentage of remaining animation
    simulationDuration = this.duration * remainingFraction;
  } else if (target == value) {
    //Animation end point reached, no more animation
    simulationDuration = Duration.zero;
  }
  //Stop the old animation [see section 2.3.1]
  stop();
  if (simulationDuration == Duration.zero) {
    if (value != target) {
      _value = target.clamp(lowerBound, upperBound);
      notifyListeners();
    }
    _status = (_direction == _AnimationDirection.forward) ?
      AnimationStatus.completed :
      AnimationStatus.dismissed;
    _checkStatusChanged();
    //When the animation execution time is up, it will end directly
    return TickerFuture.complete();
  }
  //[see subsection 2.4]
  return _startSimulation(_InterpolationSimulation(_value, target, simulationDuration, curve, scale));
}

The default is the linear animation curve Curves.linear.

2.3.1 AnimationController.stop[](http://gityuan.com/2019/07/13/flutter_animator/#231-animationcontrollerstop)

void stop({ bool canceled = true }) {
  _simulation = null;
  _lastElapsedDuration = null;
  //[see subsection 2.3.2]
  _ticker.stop(canceled: canceled);
}

2.3.2 Ticker.stop[](http://gityuan.com/2019/07/13/flutter_animator/#232-tickerstop)

[-> lib/src/scheduler/ticker.dart]

void stop({ bool canceled = false }) {
  if (!isActive) //No longer active, return directly
    return;

  final TickerFuture localFuture = _future;
  _future = null;
  _startTime = null;

  //[see subsection 2.3.3]
  unscheduleTick();
  if (canceled) {
    localFuture._cancel(this);
  } else {
    localFuture._complete();
  }
}

2.3.3 Ticker.unscheduleTick[](http://gityuan.com/2019/07/13/flutter_animator/#233-tickerunscheduletick)

[-> lib/src/scheduler/ticker.dart]

void unscheduleTick() {
  if (scheduled) {
    SchedulerBinding.instance.cancelFrameCallbackWithId(_animationId);
    _animationId = null;
  }
}

2.3.4 initialization []( http://gityuan.com/2019/07/13/flutter_animator/#234-_interpolationsimulation Initialization)

[-> lib/src/animation/animation_controller.dart ::_InterpolationSimulation]

class _InterpolationSimulation extends Simulation {
  _InterpolationSimulation(this._begin, this._end, Duration duration, this._curve, double scale)
    : _durationInSeconds = (duration.inMicroseconds * scale) / Duration.microsecondsPerSecond;

  final double _durationInSeconds;
  final double _begin;
  final double _end;
  final Curve _curve;
}

This method creates an interpolation simulator object and initializes the start point, end point, animation Curve and time length. The Curve used here is a linear model, that is to say, the uniform motion is used.

2.4 _startSimulation[](http://gityuan.com/2019/07/13/flutter_animator/#24-_startsimulation)

[-> lib/src/animation/animation_controller.dart]

TickerFuture _startSimulation(Simulation simulation) {
  _simulation = simulation;
  _lastElapsedDuration = Duration.zero;
  _value = simulation.x(0.0).clamp(lowerBound, upperBound);
  //[see subsection 2.5]
  final TickerFuture result = _ticker.start();
  _status = (_direction == _AnimationDirection.forward) ?
    AnimationStatus.forward :
    AnimationStatus.reverse;
  //[see subsection 2.4.1]
  _checkStatusChanged();
  return result;
}

2.4.1 _checkStatusChanged[](http://gityuan.com/2019/07/13/flutter_animator/#241-_checkstatuschanged)

[-> lib/src/animation/animation_controller.dart]

void _checkStatusChanged() {
  final AnimationStatus newStatus = status;
  if (_lastReportedStatus != newStatus) {
    _lastReportedStatus = newStatus;
    notifyStatusListeners(newStatus); //Notification status change
  }
}

All status listeners in ﹣ statusListeners are called back here. The status here refers to the dismissed, forward, reverse and completed of AnimationStatus.

2.5 Ticker.start[](http://gityuan.com/2019/07/13/flutter_animator/#25-tickerstart)

[-> lib/src/scheduler/ticker.dart]

TickerFuture start() {
  _future = TickerFuture._();
  if (shouldScheduleTick) {
    scheduleTick();   //[see subsection 2.6]
  }
  if (SchedulerBinding.instance.schedulerPhase.index > SchedulerPhase.idle.index &&
      SchedulerBinding.instance.schedulerPhase.index < SchedulerPhase.postFrameCallbacks.index)
    _startTime = SchedulerBinding.instance.currentFrameTimeStamp;
  return _future;
}

Here, the shouldScheduleTick is equal to! Muted & & isactive &! Scheduled, that is, the active state that has not been scheduled will call Tick.

2.6 Ticker.scheduleTick[](http://gityuan.com/2019/07/13/flutter_animator/#26-tickerscheduletick)

[-> lib/src/scheduler/ticker.dart]

void scheduleTick({ bool rescheduling = false }) {
  //[see subsection 2.7]
  _animationId = SchedulerBinding.instance.scheduleFrameCallback(_tick, rescheduling: rescheduling);
}

Here, the "tick" will be called back for execution when the next vysnc is triggered, as shown in section 2.10.

2.7 scheduleFrameCallback[](http://gityuan.com/2019/07/13/flutter_animator/#27-scheduleframecallback)

[-> lib/src/scheduler/binding.dart]

int scheduleFrameCallback(FrameCallback callback, { bool rescheduling = false }) {
    //[see subsection 2.8]
  scheduleFrame();
  _nextFrameCallbackId += 1;
  _transientCallbacks[_nextFrameCallbackId] = _FrameCallbackEntry(callback, rescheduling: rescheduling);
  return _nextFrameCallbackId;
}

Save the ticker. Tick() method passed in front in the callback of FrameCallbackEntry, and record the FrameCallbackEntry in the "transientCallbacks" of Map type,

2.8 scheduleFrame[](http://gityuan.com/2019/07/13/flutter_animator/#28-scheduleframe)

[-> lib/src/scheduler/binding.dart]

void scheduleFrame() {
  if (_hasScheduledFrame || !_framesEnabled)
    return;
  ui.window.scheduleFrame();
  _hasScheduledFrame = true;
}

From article The setState update mechanism of Flutter , we can see that the ui.window.scheduleFrame() called here will register vsync listening. handleBeginFrame() is executed when the next vsync signal arrives.

2.9 handleBeginFrame[](http://gityuan.com/2019/07/13/flutter_animator/#29-handlebeginframe)

[-> lib/src/scheduler/binding.dart:: SchedulerBinding]

void handleBeginFrame(Duration rawTimeStamp) {
  Timeline.startSync('Frame', arguments: timelineWhitelistArguments);
  _firstRawTimeStampInEpoch ??= rawTimeStamp;
  _currentFrameTimeStamp = _adjustForEpoch(rawTimeStamp ?? _lastRawTimeStamp);
  if (rawTimeStamp != null)
    _lastRawTimeStamp = rawTimeStamp;
  ...

  //At this time, the phase is equal to SchedulerPhase.idle;
  _hasScheduledFrame = false;
  try {
    Timeline.startSync('Animate', arguments: timelineWhitelistArguments);
    _schedulerPhase = SchedulerPhase.transientCallbacks;
    //Callback method for executing animation
    final Map<int, _FrameCallbackEntry> callbacks = _transientCallbacks;
    _transientCallbacks = <int, _FrameCallbackEntry>{};
    callbacks.forEach((int id, _FrameCallbackEntry callbackEntry) {
      if (!_removedIds.contains(id))
        _invokeFrameCallback(callbackEntry.callback, _currentFrameTimeStamp, callbackEntry.debugStack);
    });
    _removedIds.clear();
  } finally {
    _schedulerPhase = SchedulerPhase.midFrameMicrotasks;
  }
}

The main function of this method is to traverse the "transient callbacks". From the previous section [2.7], we can see that this process will execute the ticker. \.

2.10 Ticker._tick[](http://gityuan.com/2019/07/13/flutter_animator/#210-ticker_tick)

[-> lib/src/scheduler/ticker.dart]

void _tick(Duration timeStamp) {
  _animationId = null;
  _startTime ??= timeStamp;
  //[see subsection 2.11]
  _onTick(timeStamp - _startTime);
  //Decide whether to reschedule according to the active state
  if (shouldScheduleTick)
    scheduleTick(rescheduling: true);
}

The main functions of this method are as follows:

  • In the initialization of Ticker in Section [2.1.2], it can be seen that here "onTick" is the "tick() method" of AnimationController;
  • Section [2.5] has described that when it is still active, it will be rescheduled again and return to the scheduleTick() in Section [2.6], so as to form a continuous drawing process of animation.

2.11 AnimationController._tick[](http://gityuan.com/2019/07/13/flutter_animator/#211-animationcontroller_tick)

[-> lib/src/animation/animation_controller.dart]

void _tick(Duration elapsed) {
  _lastElapsedDuration = elapsed;
  //Get past time
  final double elapsedInSeconds = elapsed.inMicroseconds.toDouble() / Duration.microsecondsPerSecond;
  _value = _simulation.x(elapsedInSeconds).clamp(lowerBound, upperBound);
  if (_simulation.isDone(elapsedInSeconds)) {
    _status = (_direction == _AnimationDirection.forward) ?
      AnimationStatus.completed :
      AnimationStatus.dismissed;
    stop(canceled: false); //When the animation is complete, stop
  }
  notifyListeners();   //Notification monitor [see section 2.11.1]
  _checkStatusChanged(); //Notification status monitor [see section 2.11.2]
}

2.11.1 notifyListeners[](http://gityuan.com/2019/07/13/flutter_animator/#2111-notifylisteners)

[-> lib/src/animation/listener_helpers.dart ::AnimationLocalListenersMixin]

void notifyListeners() {
  final List<VoidCallback> localListeners = List<VoidCallback>.from(_listeners);
  for (VoidCallback listener in localListeners) {
    try {
      if (_listeners.contains(listener))
        listener();
    } catch (exception, stack) {
      ...
    }
  }
}

addListener() of animationlocallisteners mixin will add listeners to the listeners

2.11.2 _checkStatusChanged[](http://gityuan.com/2019/07/13/flutter_animator/#2112-_checkstatuschanged)

[-> lib/src/animation/listener_helpers.dart ::AnimationLocalStatusListenersMixin]

void notifyStatusListeners(AnimationStatus status) {
  final List<AnimationStatusListener> localListeners = List<AnimationStatusListener>.from(_statusListeners);
  for (AnimationStatusListener listener in localListeners) {
    try {
      if (_statusListeners.contains(listener))
        listener(status);
    } catch (exception, stack) {
      ...
    }
  }
}

From the previous section [2.4.1], the notifyStatusListeners method is called when the status changes. addStatusListener() of animationlocalstatuslisteners mixin adds a status listener to the statusListeners.

3, Summary []( http://gityuan.com/2019/07/13/flutter_animator/# III. summary)

3.1 animation flowchart []( http://gityuan.com/2019/07/13/flutter_animator/#31- Animation flow chart)


Recommended reading: Tencent's technical team arranges, and it's easy to get a thorough introduction to the ten thousand character long text. The front end becomes bigger in seconds

2017-2020 byte beat Android interview real question analysis (download 10.82 million times in total, continuous update)

Original author: gityuan
Original link: http://gityuan.com/2019/07/13/flutter_animator/

Tags: Operation & Maintenance Spring simulator Android

Posted on Mon, 27 Apr 2020 05:33:23 -0400 by onlinegs