flutter/packages/flutter/lib/src/animation/animation_controller.dart
Ian Hickson 43b0104114 Revert "Merge pull request #2639 from Hixie/always_specify_types"
This reverts commit f41b3411da35929b09009e47cb52474389e42874, reversing
changes made to e33d8d96212f3e337a6660f1eb1118bffc945bf5.

This was a bad check-in due to my mangling uploading a new version of the branch from a different machine.

This reverts https://github.com/flutter/flutter/pull/2639 and will be replaced by https://github.com/flutter/flutter/pull/2640
2016-03-12 00:34:37 -08:00

285 lines
9.2 KiB
Dart

// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:ui' as ui show lerpDouble;
import 'package:flutter/scheduler.dart';
import 'package:newton/newton.dart';
import 'animation.dart';
import 'curves.dart';
import 'forces.dart';
import 'listener_helpers.dart';
/// The direction in which an animation is running.
enum _AnimationDirection {
/// The animation is running from beginning to end.
forward,
/// The animation is running backwards, from end to beginning.
reverse
}
/// A controller for an animation.
///
/// An animation controller can drive an animation forward or backward and can
/// set the animation to a particular value. The controller also defines the
/// bounds of the animation and can drive an animation using a physics
/// simulation.
class AnimationController extends Animation<double>
with AnimationEagerListenerMixin, AnimationLocalListenersMixin, AnimationLocalStatusListenersMixin {
/// Creates an animation controller.
///
/// * value is the initial value of the animation.
/// * duration is the length of time this animation should last.
/// * debugLabel is a string to help identify this animation during debugging (used by toString).
/// * lowerBound is the smallest value this animation can obtain and the value at which this animation is deemed to be dismissed.
/// * upperBound is the largest value this animation can obtain and the value at which this animation is deemed to be completed.
AnimationController({
double value,
this.duration,
this.debugLabel,
this.lowerBound: 0.0,
this.upperBound: 1.0
}) {
assert(upperBound >= lowerBound);
_value = (value ?? lowerBound).clamp(lowerBound, upperBound);
_ticker = new Ticker(_tick);
}
/// Creates an animation controller with no upper or lower bound for its value.
///
/// * value is the initial value of the animation.
/// * duration is the length of time this animation should last.
/// * debugLabel is a string to help identify this animation during debugging (used by toString).
///
/// This constructor is most useful for animations that will be driven using a
/// physics simulation, especially when the physics simulation has no
/// pre-determined bounds.
AnimationController.unbounded({
double value: 0.0,
this.duration,
this.debugLabel
}) : lowerBound = double.NEGATIVE_INFINITY,
upperBound = double.INFINITY,
_value = value {
assert(value != null);
_ticker = new Ticker(_tick);
}
/// The value at which this animation is deemed to be dismissed.
final double lowerBound;
/// The value at which this animation is deemed to be completed.
final double upperBound;
/// A label that is used in the [toString] output. Intended to aid with
/// identifying animation controller instances in debug output.
final String debugLabel;
/// Returns an [Animated<double>] for this animation controller,
/// so that a pointer to this object can be passed around without
/// allowing users of that pointer to mutate the AnimationController state.
Animation<double> get view => this;
/// The length of time this animation should last.
Duration duration;
Ticker _ticker;
Simulation _simulation;
/// The current value of the animation.
///
/// Setting this value notifies all the listeners that the value
/// changed.
///
/// Setting this value also stops the controller if it is currently
/// running; if this happens, it also notifies all the status
/// listeners.
double get value => _value;
double _value;
void set value(double newValue) {
assert(newValue != null);
stop();
_value = newValue.clamp(lowerBound, upperBound);
notifyListeners();
_checkStatusChanged();
}
/// Whether this animation is currently animating in either the forward or reverse direction.
bool get isAnimating => _ticker.isTicking;
_AnimationDirection _direction;
AnimationStatus get status {
if (!isAnimating && value == upperBound)
return AnimationStatus.completed;
if (!isAnimating && value == lowerBound)
return AnimationStatus.dismissed;
return _direction == _AnimationDirection.forward ?
AnimationStatus.forward :
AnimationStatus.reverse;
}
/// Starts running this animation forwards (towards the end).
Future forward({ double from }) {
if (from != null)
value = from;
_direction = _AnimationDirection.forward;
return animateTo(upperBound);
}
/// Starts running this animation in reverse (towards the beginning).
Future reverse({ double from }) {
if (from != null)
value = from;
_direction = _AnimationDirection.reverse;
return animateTo(lowerBound);
}
/// Drives the animation from its current value to target.
Future animateTo(double target, { Duration duration, Curve curve: Curves.linear }) {
Duration simulationDuration = duration;
if (simulationDuration == null) {
double range = upperBound - lowerBound;
double remainingFraction = range.isFinite ? (target - _value).abs() / range : 1.0;
simulationDuration = this.duration * remainingFraction;
}
stop();
if (simulationDuration == Duration.ZERO) {
assert(value == target);
_checkStatusChanged();
return new Future.value();
}
assert(simulationDuration > Duration.ZERO);
assert(!isAnimating);
return _startSimulation(new _InterpolationSimulation(_value, target, simulationDuration, curve));
}
/// Starts running this animation in the forward direction, and
/// restarts the animation when it completes.
///
/// Defaults to repeating between the lower and upper bounds.
Future repeat({ double min, double max, Duration period }) {
min ??= lowerBound;
max ??= upperBound;
period ??= duration;
return animateWith(new _RepeatingSimulation(min, max, period));
}
/// Flings the timeline with an optional force (defaults to a critically
/// damped spring) and initial velocity. If velocity is positive, the
/// animation will complete, otherwise it will dismiss.
Future fling({ double velocity: 1.0, Force force }) {
force ??= kDefaultSpringForce;
_direction = velocity < 0.0 ? _AnimationDirection.reverse : _AnimationDirection.forward;
return animateWith(force.release(value, velocity));
}
/// Drives the animation according to the given simulation.
Future animateWith(Simulation simulation) {
stop();
return _startSimulation(simulation);
}
Future _startSimulation(Simulation simulation) {
assert(simulation != null);
assert(!isAnimating);
_simulation = simulation;
_value = simulation.x(0.0).clamp(lowerBound, upperBound);
Future result = _ticker.start();
_checkStatusChanged();
return result;
}
/// Stops running this animation.
void stop() {
_simulation = null;
_ticker.stop();
}
/// Stops running this animation.
void dispose() {
stop();
}
AnimationStatus _lastReportedStatus = AnimationStatus.dismissed;
void _checkStatusChanged() {
AnimationStatus newStatus = status;
if (_lastReportedStatus != newStatus) {
_lastReportedStatus = newStatus;
notifyStatusListeners(newStatus);
}
}
void _tick(Duration elapsed) {
double elapsedInSeconds = elapsed.inMicroseconds.toDouble() / Duration.MICROSECONDS_PER_SECOND;
_value = _simulation.x(elapsedInSeconds).clamp(lowerBound, upperBound);
if (_simulation.isDone(elapsedInSeconds))
stop();
notifyListeners();
_checkStatusChanged();
}
String toStringDetails() {
String paused = isAnimating ? '' : '; paused';
String label = debugLabel == null ? '' : '; for $debugLabel';
String more = '${super.toStringDetails()} ${value.toStringAsFixed(3)}';
return '$more$paused$label';
}
}
class _InterpolationSimulation extends Simulation {
_InterpolationSimulation(this._begin, this._end, Duration duration, this._curve)
: _durationInSeconds = duration.inMicroseconds / Duration.MICROSECONDS_PER_SECOND {
assert(_durationInSeconds > 0.0);
assert(_begin != null);
assert(_end != null);
}
final double _durationInSeconds;
final double _begin;
final double _end;
final Curve _curve;
double x(double timeInSeconds) {
assert(timeInSeconds >= 0.0);
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);
}
double dx(double timeInSeconds) => 1.0;
bool isDone(double timeInSeconds) => timeInSeconds > _durationInSeconds;
}
class _RepeatingSimulation extends Simulation {
_RepeatingSimulation(this.min, this.max, Duration period)
: _periodInSeconds = period.inMicroseconds / Duration.MICROSECONDS_PER_SECOND {
assert(_periodInSeconds > 0.0);
}
final double min;
final double max;
final double _periodInSeconds;
double x(double timeInSeconds) {
assert(timeInSeconds >= 0.0);
final double t = (timeInSeconds / _periodInSeconds) % 1.0;
return ui.lerpDouble(min, max, t);
}
double dx(double timeInSeconds) => 1.0;
bool isDone(double timeInSeconds) => false;
}