From d3817ff49ce6bd5a3eba17f5323086b16d2794f6 Mon Sep 17 00:00:00 2001 From: Matt Perry Date: Tue, 4 Aug 2015 16:34:42 -0400 Subject: [PATCH] When animating, use the same curve until it completes. This ensures we don't run into discontinuities when reversing an animation halfway through. I refactored AnimationValue to have knowledge of the reverse curves and intervals. --- .../sky/lib/animation/animated_value.dart | 86 +++++++++++-------- .../lib/animation/animation_performance.dart | 23 ++++- sky/packages/sky/lib/animation/curves.dart | 16 ++++ sky/packages/sky/lib/animation/direction.dart | 8 ++ sky/packages/sky/lib/animation/forces.dart | 7 +- .../sky/lib/rendering/toggleable.dart | 12 +-- .../sky/lib/widgets/animated_container.dart | 30 ++----- sky/packages/sky/lib/widgets/navigator.dart | 1 - sky/packages/sky/lib/widgets/popup_menu.dart | 8 +- sky/packages/sky/lib/widgets/transitions.dart | 10 +-- 10 files changed, 112 insertions(+), 89 deletions(-) create mode 100644 sky/packages/sky/lib/animation/direction.dart diff --git a/sky/packages/sky/lib/animation/animated_value.dart b/sky/packages/sky/lib/animation/animated_value.dart index a27878d7e..1158f1641 100644 --- a/sky/packages/sky/lib/animation/animated_value.dart +++ b/sky/packages/sky/lib/animation/animated_value.dart @@ -5,84 +5,96 @@ import "dart:sky"; import 'package:sky/animation/curves.dart'; +import 'package:sky/animation/direction.dart'; import 'package:sky/base/lerp.dart'; +export 'package:sky/animation/curves.dart' show Interval; + abstract class AnimatedVariable { - void setProgress(double t); + void setProgress(double t, Direction direction); String toString(); } -class Interval { - final double start; - final double end; +abstract class CurvedVariable implements AnimatedVariable { + CurvedVariable({this.interval, this.reverseInterval, this.curve, this.reverseCurve}); + + Interval interval; + Interval reverseInterval; + Curve curve; + Curve reverseCurve; + + double _transform(double t, Direction direction) { + Interval interval = _getInterval(direction); + if (interval != null) + t = interval.transform(t); + if (t == 1.0) // Or should we support inverse curves? + return t; + Curve curve = _getCurve(direction); + if (curve != null) + t = curve.transform(t); + return t; + } - double adjustTime(double t) { - return ((t - start) / (end - start)).clamp(0.0, 1.0); + Interval _getInterval(Direction direction) { + if (direction == Direction.forward || reverseInterval == null) + return interval; + return reverseInterval; } - Interval(this.start, this.end) { - assert(start >= 0.0); - assert(start <= 1.0); - assert(end >= 0.0); - assert(end <= 1.0); + Curve _getCurve(Direction direction) { + if (direction == Direction.forward || reverseCurve == null) + return curve; + return reverseCurve; } } -class AnimatedValue extends AnimatedVariable { - AnimatedValue(this.begin, { this.end, this.interval, this.curve: linear }) { +class AnimatedValue extends CurvedVariable { + AnimatedValue(this.begin, { this.end, Interval interval, Curve curve, Curve reverseCurve }) + : super(interval: interval, curve: curve, reverseCurve: reverseCurve) { value = begin; } T value; T begin; T end; - Interval interval; - Curve curve; - void setProgress(double t) { + T lerp(double t) => begin + (end - begin) * t; + + void setProgress(double t, Direction direction) { if (end != null) { - double adjustedTime = interval == null ? t : interval.adjustTime(t); - if (adjustedTime == 1.0) { - value = end; - } else { - // TODO(mpcomplete): Reverse the timeline and curve. - value = begin + (end - begin) * curve.transform(adjustedTime); - } + t = _transform(t, direction); + value = (t == 1.0) ? end : lerp(t); } } String toString() => 'AnimatedValue(begin=$begin, end=$end, value=$value)'; } -class AnimatedList extends AnimatedVariable { +class AnimatedList extends CurvedVariable { List variables; - Interval interval; - AnimatedList(this.variables, { this.interval }); + AnimatedList(this.variables, { Interval interval, Curve curve, Curve reverseCurve }) + : super(interval: interval, curve: curve, reverseCurve: reverseCurve); - void setProgress(double t) { - double adjustedTime = interval == null ? t : interval.adjustTime(t); + void setProgress(double t, Direction direction) { + double adjustedTime = _transform(t, direction); for (AnimatedVariable variable in variables) - variable.setProgress(adjustedTime); + variable.setProgress(adjustedTime, direction); } String toString() => 'AnimatedList([$variables])'; } class AnimatedColorValue extends AnimatedValue { - AnimatedColorValue(Color begin, { Color end, Curve curve: linear }) + AnimatedColorValue(Color begin, { Color end, Curve curve }) : super(begin, end: end, curve: curve); - void setProgress(double t) { - value = lerpColor(begin, end, t); - } + Color lerp(double t) => lerpColor(begin, end, t); } class AnimatedRect extends AnimatedValue { - AnimatedRect(Rect begin, { Rect end, Curve curve: linear }) + AnimatedRect(Rect begin, { Rect end, Curve curve }) : super(begin, end: end, curve: curve); - void setProgress(double t) { - value = lerpRect(begin, end, t); - } + Rect lerp(double t) => lerpRect(begin, end, t); } diff --git a/sky/packages/sky/lib/animation/animation_performance.dart b/sky/packages/sky/lib/animation/animation_performance.dart index d1a556fbc..29b2b3717 100644 --- a/sky/packages/sky/lib/animation/animation_performance.dart +++ b/sky/packages/sky/lib/animation/animation_performance.dart @@ -5,10 +5,11 @@ import 'dart:async'; import 'package:sky/animation/animated_value.dart'; +import 'package:sky/animation/direction.dart'; import 'package:sky/animation/forces.dart'; import 'package:sky/animation/timeline.dart'; -export 'package:sky/animation/forces.dart' show Direction; +export 'package:sky/animation/direction.dart' show Direction; enum AnimationStatus { dismissed, // stoped at 0 @@ -39,6 +40,12 @@ class AnimationPerformance { Direction _direction; Direction get direction => _direction; + // This controls which curve we use for variables with different curves in + // the forward/reverse directions. Curve direction is only reset when we hit + // 0 or 1, to avoid discontinuities. + Direction _curveDirection; + Direction get curveDirection => _curveDirection; + // If non-null, animate with this force instead of a tween animation. Force attachedForce; @@ -74,6 +81,10 @@ class AnimationPerformance { AnimationStatus.reverse; } + void updateVariable(AnimatedVariable variable) { + variable.setProgress(progress, curveDirection); + } + Future play([Direction direction = Direction.forward]) { _direction = direction; return resume(); @@ -136,6 +147,13 @@ class AnimationPerformance { _lastStatus = currentStatus; } + void _updateCurveDirection() { + if (status != _lastStatus) { + if (_lastStatus == AnimationStatus.dismissed || _lastStatus == AnimationStatus.completed) + _curveDirection = direction; + } + } + Future _animateTo(double target) { Duration remainingDuration = duration * (target - timeline.value).abs(); timeline.stop(); @@ -145,8 +163,9 @@ class AnimationPerformance { } void _tick(double t) { + _updateCurveDirection(); if (variable != null) - variable.setProgress(t); + variable.setProgress(t, curveDirection); _notifyListeners(); _checkStatusChanged(); } diff --git a/sky/packages/sky/lib/animation/curves.dart b/sky/packages/sky/lib/animation/curves.dart index 8e285762e..f973c3994 100644 --- a/sky/packages/sky/lib/animation/curves.dart +++ b/sky/packages/sky/lib/animation/curves.dart @@ -21,6 +21,22 @@ class Linear implements Curve { } } +class Interval implements Curve { + final double start; + final double end; + + Interval(this.start, this.end) { + assert(start >= 0.0); + assert(start <= 1.0); + assert(end >= 0.0); + assert(end <= 1.0); + } + + double transform(double t) { + return ((t - start) / (end - start)).clamp(0.0, 1.0); + } +} + class ParabolicFall implements Curve { const ParabolicFall(); diff --git a/sky/packages/sky/lib/animation/direction.dart b/sky/packages/sky/lib/animation/direction.dart new file mode 100644 index 000000000..1a72ed771 --- /dev/null +++ b/sky/packages/sky/lib/animation/direction.dart @@ -0,0 +1,8 @@ +// Copyright 2015 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. + +enum Direction { + forward, + reverse +} diff --git a/sky/packages/sky/lib/animation/forces.dart b/sky/packages/sky/lib/animation/forces.dart index e309f0ba5..6448892cb 100644 --- a/sky/packages/sky/lib/animation/forces.dart +++ b/sky/packages/sky/lib/animation/forces.dart @@ -3,12 +3,7 @@ // found in the LICENSE file. import 'package:newton/newton.dart'; - -// TODO(mpcomplete): This doesn't belong here. -enum Direction { - forward, - reverse -} +import 'package:sky/animation/direction.dart'; // Base class for creating Simulations for the animation Timeline. abstract class Force { diff --git a/sky/packages/sky/lib/rendering/toggleable.dart b/sky/packages/sky/lib/rendering/toggleable.dart index 24ec5c740..24450a90b 100644 --- a/sky/packages/sky/lib/rendering/toggleable.dart +++ b/sky/packages/sky/lib/rendering/toggleable.dart @@ -44,15 +44,7 @@ abstract class RenderToggleable extends RenderConstrainedBox { void set value(bool value) { if (value == _value) return; _value = value; - // TODO(abarth): Setting the curve on the position means there's a - // discontinuity when we reverse the timeline. - if (value) { - _position.curve = easeIn; - _performance.play(); - } else { - _position.curve = easeOut; - _performance.reverse(); - } + performance.play(value ? Direction.forward : Direction.reverse); } ValueChanged _onChanged; @@ -63,7 +55,7 @@ abstract class RenderToggleable extends RenderConstrainedBox { } final AnimatedValue _position = - new AnimatedValue(0.0, end: 1.0); + new AnimatedValue(0.0, end: 1.0, curve: easeIn, reverseCurve: easeOut); AnimatedValue get position => _position; AnimationPerformance _performance; diff --git a/sky/packages/sky/lib/widgets/animated_container.dart b/sky/packages/sky/lib/widgets/animated_container.dart index ec53bbba4..cb45dfc84 100644 --- a/sky/packages/sky/lib/widgets/animated_container.dart +++ b/sky/packages/sky/lib/widgets/animated_container.dart @@ -16,35 +16,23 @@ class AnimatedBoxConstraintsValue extends AnimatedValue { AnimatedBoxConstraintsValue(BoxConstraints begin, { BoxConstraints end, Curve curve: linear }) : super(begin, end: end, curve: curve); - void setProgress(double t) { - // TODO(abarth): We should lerp the BoxConstraints. - value = end; - } + // TODO(abarth): We should lerp the BoxConstraints. + BoxConstraints lerp(double t) => end; } class AnimatedBoxDecorationValue extends AnimatedValue { AnimatedBoxDecorationValue(BoxDecoration begin, { BoxDecoration end, Curve curve: linear }) : super(begin, end: end, curve: curve); - void setProgress(double t) { - if (t == 1.0) { - value = end; - return; - } - value = lerpBoxDecoration(begin, end, t); - } + BoxDecoration lerp(double t) => lerpBoxDecoration(begin, end, t); } class AnimatedEdgeDimsValue extends AnimatedValue { AnimatedEdgeDimsValue(EdgeDims begin, { EdgeDims end, Curve curve: linear }) : super(begin, end: end, curve: curve); - void setProgress(double t) { - if (t == 1.0) { - value = end; - return; - } - value = new EdgeDims( + EdgeDims lerp(double t) { + return new EdgeDims( lerpNum(begin.top, end.top, t), lerpNum(begin.right, end.right, t), lerpNum(begin.bottom, end.bottom, t), @@ -57,17 +45,13 @@ class AnimatedMatrix4Value extends AnimatedValue { AnimatedMatrix4Value(Matrix4 begin, { Matrix4 end, Curve curve: linear }) : super(begin, end: end, curve: curve); - void setProgress(double t) { - if (t == 1.0) { - value = end; - return; - } + Matrix4 lerp(double t) { // TODO(mpcomplete): Animate the full matrix. Will animating the cells // separately work? Vector3 beginT = begin.getTranslation(); Vector3 endT = end.getTranslation(); Vector3 lerpT = beginT*(1.0-t) + endT*t; - value = new Matrix4.identity()..translate(lerpT); + return new Matrix4.identity()..translate(lerpT); } } diff --git a/sky/packages/sky/lib/widgets/navigator.dart b/sky/packages/sky/lib/widgets/navigator.dart index b36b362da..1e83a3d3d 100644 --- a/sky/packages/sky/lib/widgets/navigator.dart +++ b/sky/packages/sky/lib/widgets/navigator.dart @@ -7,7 +7,6 @@ import 'dart:async'; import 'package:sky/animation/animated_value.dart'; import 'package:sky/animation/animation_performance.dart'; import 'package:sky/animation/curves.dart'; -import 'package:sky/animation/forces.dart'; import 'package:sky/widgets/basic.dart'; import 'package:sky/widgets/focus.dart'; import 'package:sky/widgets/transitions.dart'; diff --git a/sky/packages/sky/lib/widgets/popup_menu.dart b/sky/packages/sky/lib/widgets/popup_menu.dart index 9a3eaf2ec..ea34b1767 100644 --- a/sky/packages/sky/lib/widgets/popup_menu.dart +++ b/sky/packages/sky/lib/widgets/popup_menu.dart @@ -50,7 +50,6 @@ class PopupMenu extends AnimatedComponent { AnimatedValue _width; AnimatedValue _height; List> _itemOpacities; - AnimatedList _animationList; AnimationPerformance _performance; void initState() { @@ -101,8 +100,9 @@ class PopupMenu extends AnimatedComponent { ..add(_width) ..add(_height) ..addAll(_itemOpacities); - _animationList = new AnimatedList(variables); - _performance.variable = _animationList; + AnimatedList list = new AnimatedList(variables) + ..reverseInterval = new Interval(0.0, _kMenuCloseIntervalEnd); + _performance.variable = list; } void _updateBoxPainter() { @@ -124,14 +124,12 @@ class PopupMenu extends AnimatedComponent { void _open() { - _animationList.interval = null; _performance.play(); if (navigator != null) navigator.pushState(this, (_) => _close()); } void _close() { - _animationList.interval = new Interval(0.0, _kMenuCloseIntervalEnd); _performance.reverse(); } diff --git a/sky/packages/sky/lib/widgets/transitions.dart b/sky/packages/sky/lib/widgets/transitions.dart index f7ea3e027..0738e0126 100644 --- a/sky/packages/sky/lib/widgets/transitions.dart +++ b/sky/packages/sky/lib/widgets/transitions.dart @@ -98,7 +98,7 @@ class SlideTransition extends TransitionBase { } Widget build() { - position.setProgress(performance.progress); + performance.updateVariable(position); Matrix4 transform = new Matrix4.identity() ..translate(position.value.x, position.value.y); return new Transform(transform: transform, child: child); @@ -131,7 +131,7 @@ class FadeTransition extends TransitionBase { } Widget build() { - opacity.setProgress(performance.progress); + performance.updateVariable(opacity); return new Opacity(opacity: opacity.value, child: child); } } @@ -162,7 +162,7 @@ class ColorTransition extends TransitionBase { } Widget build() { - color.setProgress(performance.progress); + performance.updateVariable(color); return new DecoratedBox( decoration: new BoxDecoration(backgroundColor: color.value), child: child @@ -200,9 +200,9 @@ class SquashTransition extends TransitionBase { Widget build() { if (width != null) - width.setProgress(performance.progress); + performance.updateVariable(width); if (height != null) - height.setProgress(performance.progress); + performance.updateVariable(height); return new SizedBox(width: _maybe(width), height: _maybe(height), child: child); } } -- GitLab