动画
类设计

原理图

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();
}
}
/// 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动画原理