动画

类设计

原理图

image

AnimationController()

/// An [AnimationController] needs a [TickerProvider], which is configured using
 /// the `vsync` argument on the constructor.
 ///
 /// The [TickerProvider] interface describes a factory for [Ticker] objects. A
 /// [Ticker] is an object that knows how to register itself with the
 /// [SchedulerBinding] and fires a callback every frame. The
 /// [AnimationController] class uses a [Ticker] to step through the animation
 /// that it controls.

class AnimationController extends Animation<double>
  with AnimationEagerListenerMixin, AnimationLocalListenersMixin, AnimationLocalStatusListenersMixin
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(lowerBound != null),
   assert(upperBound != null),
   assert(upperBound >= lowerBound),
   assert(vsync != null),
   _direction = _AnimationDirection.forward {
  _ticker = vsync.createTicker(_tick);
  _internalSetValue(value ?? lowerBound);
 }

vsync.createTicker(_tick)

SingleTickerProviderStateMixin.createTicker

//mixin SingleTickerProviderStateMixin<T extends StatefulWidget> on State<T> implements TickerProvider 
@override
 Ticker createTicker(TickerCallback onTick) {

_ticker = Ticker(onTick, debugLabel: kDebugMode ? 'created by $this' : null);
 // We assume that this is called from initState, build, or some sort of
 // event handler, and that thus TickerMode.of(context) would return true. We
 // can't actually check that here because if we're in initState then we're
 // not allowed to do inheritance checks yet.
 return _ticker;

_internalSetValue

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;
  }
 }

forward

/// Starts running this animation forwards (towards the end).
TickerFuture forward({ double from }) {
  _direction = _AnimationDirection.forward;
  if (from != null)
    value = from;
  return _animateToInternal(upperBound);//main
}

_animateToInternal

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

......

Duration simulationDuration = duration;
 if (simulationDuration == null) {
  final double range = upperBound - lowerBound;
  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();
 ......

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

stop

void stop({ bool canceled = true }) {
 _simulation = null;
 _lastElapsedDuration = null;
 _ticker.stop(canceled: canceled);
//Ticker
/// Stops calling this [Ticker]'s callback.
void stop({ bool canceled = false }) {
  if (!isActive)
   return;

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

unscheduleTick();
 if (canceled) {
  localFuture._cancel(this);
 } else {
  localFuture._complete();
 }
}

/// Cancels the frame callback that was requested by [scheduleTick], if any.
@protected
 void unscheduleTick() {
  if (scheduled) {
   SchedulerBinding.instance.cancelFrameCallbackWithId(_animationId);
   _animationId = null;
 }

_startSimulation

TickerFuture _startSimulation(Simulation simulation) {

_simulation = simulation;
 _lastElapsedDuration = Duration.zero;
 _value = simulation.x(0.0).clamp(lowerBound, upperBound);
 final TickerFuture result = _ticker.start();//main
 _status = (_direction == _AnimationDirection.forward) ?
  AnimationStatus.forward :
  AnimationStatus.reverse;
 _checkStatusChanged();
 return result;

Ticker.start

/// Starts the clock for this [Ticker]. If the ticker is not [muted], then this
 /// also starts calling the ticker's callback once per animation frame.

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

scheduleTick

/// Schedules a tick for the next frame.
 ///
 /// This should only be called if [shouldScheduleTick] is true.
 @protected
 void scheduleTick({ bool rescheduling = false }) {
  assert(!scheduled);
  assert(shouldScheduleTick);
  _animationId = SchedulerBinding.instance.scheduleFrameCallback(_tick, rescheduling: rescheduling);//main, callback method: _tick
 }

SchedulerBinding.scheduleFrame

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

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

//Window
void scheduleFrame() native 'Window_scheduleFrame';

Ticker frameCallback method _tick

void _tick(Duration timeStamp) {
  _animationId = null;

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

  // The onTick callback may have scheduled another tick already, for
  // example by calling stop then start again.
  if (shouldScheduleTick)
   scheduleTick(rescheduling: true);
 }

AnimationController._tick

void _tick(Duration elapsed) {
  _lastElapsedDuration = elapsed;
  final double elapsedInSeconds = elapsed.inMicroseconds.toDouble() / Duration.microsecondsPerSecond;
  assert(elapsedInSeconds >= 0.0);
  _value = _simulation.x(elapsedInSeconds).clamp(lowerBound, upperBound);//main, update value
  if (_simulation.isDone(elapsedInSeconds)) {
   _status = (_direction == _AnimationDirection.forward) ?
    AnimationStatus.completed :
    AnimationStatus.dismissed;
   stop(canceled: false);
  }
  notifyListeners();
  _checkStatusChanged();
 }

_InterpolationSimulation.x(double timeInSeconds)

class _InterpolationSimulation extends Simulation

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

@override
 double x(double timeInSeconds) {
  final double t = (timeInSeconds / _durationInSeconds).clamp(0.0, 1.0);
  if (t == 0.0)
   return _begin;
  else if (t == 1.0)
   return _end;
  else
   return _begin + (_end - _begin) * _curve.transform(t);
 }

Animatable.animate

/// Returns a new [Animation] that is driven by the given animation but that
 /// takes on values determined by this object.
 ///
 /// Essentially this returns an [Animation] that automatically applies the
 /// [evaluate] method to the parent's value.
 ///
 /// See also:
 ///
 /// * [AnimationController.drive], which does the same thing from the
 ///  opposite starting point.

Animation<T> animate(Animation<double> parent) {
  return _AnimatedEvaluation<T>(parent, this);
 }

_AnimatedEvaluation.getValue

_AnimatedEvaluation<T> extends Animation<T> with AnimationWithParentMixin<double> {
  @override
 final Animation<double> parent;

 final Animatable<T> _evaluatable;

@override
 T get value => _evaluatable.evaluate(parent);
}

Animatable.evaluate

T evaluate(Animation<double> animation) => transform(animation.value);
T transform(double t);
/// A linear interpolation between a beginning and ending value.
Tween<T extends dynamic> extends Animatable<T> {
 /// Returns the interpolated value for the current value of the given animation.
 @override
 T transform(double t) {
  if (t == 0.0)
   return begin;
  if (t == 1.0)
   return end;
  return lerp(t);
 }

/// Returns the value this variable has at the given animation clock value.
 @protected
 T lerp(double t) {
  return begin + (end - begin) * t;
 }
}

Demo

import 'package:flutter/animation.dart';            
import 'package:flutter/material.dart';            
void main() => runApp(LogoApp());            
{
 _LogoAppState createState() => _LogoAppState();            
}            
class _LogoAppState extends State<LogoApp> {            
class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {            
  Animation<double> animation;            
  AnimationController controller;            

  @override            
  void initState() {            
    super.initState();            
    controller = AnimationController(duration: const Duration(seconds: 2), vsync: this);            
    animation = Tween<double>(begin: 0, end: 300).animate(controller)            
      ..addListener(() {            
        setState(() {            
          // The state that has changed here is the animation object’s value.            
        });            
      });            
    controller.forward();            
  }            

  @override            
  Widget build(BuildContext context) {            
    return Center(            
      child: Container(            
        margin: EdgeInsets.symmetric(vertical: 10),            
        height: 300,            
        width: 300,            
        height: animation.value,            
        width: animation.value,            
        child: FlutterLogo(),            
      ),            
    );            
  }            

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

AnimatedWidget

/// A widget that rebuilds when the given [Listenable] changes value.
abstract class AnimatedWidget extends StatefulWidget {

  /// Creates a widget that rebuilds when the given listenable changes.
  ///
  /// The [listenable] argument is required.
  const AnimatedWidget({
    Key key,
    @required this.listenable,
  })
  
    /// Subclasses typically do not override this method.
  @override
  _AnimatedState createState() => _AnimatedState();
  
  class _AnimatedState extends State<AnimatedWidget> {
  @override
  void initState() {
    super.initState();
    widget.listenable.addListener(_handleChange);
  }
  
    @override
  void dispose() {
    widget.listenable.removeListener(_handleChange);
    super.dispose();
  }
  
    void _handleChange() {
    setState(() {
      // The listenable's state is our build state, and it changed already.
    });
  }

AnimatedBuilder

/// A general-purpose widget for building animations.
class AnimatedBuilder extends AnimatedWidget {
  /// Creates an animated builder.
  ///
  /// The [animation] and [builder] arguments must not be null.
  const AnimatedBuilder({
    Key key,
    @required Listenable animation,
    @required this.builder,
    this.child,
  }) 

参考

https://flutter.dev/docs/development/ui/animations

深入理解Flutter动画原理