Commit b79a7313 authored by Matt Perry's avatar Matt Perry

Refactor AnimationContainer to support drop-in Intentions.

This lets us be flexible in how to animate the properties of the container.
Currently used by SnackBar with a SlideIn intention.
parent 6a17d180
...@@ -53,6 +53,24 @@ class AnimatedEdgeDimsValue extends AnimatedValue<EdgeDims> { ...@@ -53,6 +53,24 @@ class AnimatedEdgeDimsValue extends AnimatedValue<EdgeDims> {
} }
} }
class AnimatedMatrix4Value extends AnimatedValue<Matrix4> {
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;
}
// 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);
}
}
class ImplicitlyAnimatedValue<T> { class ImplicitlyAnimatedValue<T> {
final AnimationPerformance performance = new AnimationPerformance(); final AnimationPerformance performance = new AnimationPerformance();
final AnimatedValue<T> _variable; final AnimatedValue<T> _variable;
...@@ -64,7 +82,7 @@ class ImplicitlyAnimatedValue<T> { ...@@ -64,7 +82,7 @@ class ImplicitlyAnimatedValue<T> {
} }
T get value => _variable.value; T get value => _variable.value;
void set value(T newValue) { void animateTo(T newValue) {
_variable.begin = _variable.value; _variable.begin = _variable.value;
_variable.end = newValue; _variable.end = newValue;
if (_variable.value != _variable.end) { if (_variable.value != _variable.end) {
...@@ -75,11 +93,118 @@ class ImplicitlyAnimatedValue<T> { ...@@ -75,11 +93,118 @@ class ImplicitlyAnimatedValue<T> {
} }
} }
abstract class AnimationIntention {
void initFields(AnimatedContainer original);
void syncFields(AnimatedContainer original, AnimatedContainer updated);
}
abstract class ImplicitlySyncFieldIntention<T> extends AnimationIntention {
ImplicitlySyncFieldIntention(this.duration);
Duration duration;
ImplicitlyAnimatedValue<T> field;
// Overrides.
T getter(AnimatedContainer container);
void setter(AnimatedContainer container, T value);
AnimatedValue<T> initField(T value);
void initFields(AnimatedContainer original) {
_updateField(original, getter(original));
}
void syncFields(AnimatedContainer original, AnimatedContainer updated) {
_updateField(original, getter(updated));
}
void _updateField(AnimatedContainer original, T newValue) {
if (field != null) {
// Animate to newValue (possibly null).
field.animateTo(newValue);
} else if (newValue != null) {
// Set the value and prepare it for future animations.
field = new ImplicitlyAnimatedValue<T>(initField(newValue), duration);
field.performance.addListener(() {
original.setState(() { setter(original, field.value); });
});
}
}
}
class ImplicitlySyncConstraintsIntention extends ImplicitlySyncFieldIntention<BoxConstraints> {
ImplicitlySyncConstraintsIntention(Duration duration) : super(duration);
BoxConstraints getter(AnimatedContainer container) => container.constraints;
void setter(AnimatedContainer container, BoxConstraints val) { container.constraints = val; }
AnimatedValue initField(BoxConstraints val) => new AnimatedBoxConstraintsValue(val);
}
class ImplicitlySyncDecorationIntention extends ImplicitlySyncFieldIntention<BoxDecoration> {
ImplicitlySyncDecorationIntention(Duration duration) : super(duration);
BoxDecoration getter(AnimatedContainer container) => container.decoration;
void setter(AnimatedContainer container, BoxDecoration val) { container.decoration = val; }
AnimatedValue initField(BoxDecoration val) => new AnimatedBoxDecorationValue(val);
}
class ImplicitlySyncMarginIntention extends ImplicitlySyncFieldIntention<EdgeDims> {
ImplicitlySyncMarginIntention(Duration duration) : super(duration);
EdgeDims getter(AnimatedContainer container) => container.margin;
void setter(AnimatedContainer container, EdgeDims val) { container.margin = val; }
AnimatedValue initField(EdgeDims val) => new AnimatedEdgeDimsValue(val);
}
class ImplicitlySyncPaddingIntention extends ImplicitlySyncFieldIntention<EdgeDims> {
ImplicitlySyncPaddingIntention(Duration duration) : super(duration);
EdgeDims getter(AnimatedContainer container) => container.padding;
void setter(AnimatedContainer container, EdgeDims val) { container.padding = val; }
AnimatedValue initField(EdgeDims val) => new AnimatedEdgeDimsValue(val);
}
class ImplicitlySyncTransformIntention extends ImplicitlySyncFieldIntention<Matrix4> {
ImplicitlySyncTransformIntention(Duration duration) : super(duration);
Matrix4 getter(AnimatedContainer container) => container.transform;
void setter(AnimatedContainer container, Matrix4 val) { container.transform = val; }
AnimatedValue initField(Matrix4 val) => new AnimatedMatrix4Value(val);
}
class ImplicitlySyncWidthIntention extends ImplicitlySyncFieldIntention<double> {
ImplicitlySyncWidthIntention(Duration duration) : super(duration);
double getter(AnimatedContainer container) => container.width;
void setter(AnimatedContainer container, double val) { container.width = val; }
AnimatedValue initField(double val) => new AnimatedValue<double>(val);
}
class ImplicitlySyncHeightIntention extends ImplicitlySyncFieldIntention<double> {
ImplicitlySyncHeightIntention(Duration duration) : super(duration);
double getter(AnimatedContainer container) => container.height;
void setter(AnimatedContainer container, double val) { container.height = val; }
AnimatedValue initField(double val) => new AnimatedValue<double>(val);
}
List<AnimationIntention> implicitlySyncFieldsIntention(Duration duration) {
return [
new ImplicitlySyncConstraintsIntention(duration),
new ImplicitlySyncDecorationIntention(duration),
new ImplicitlySyncMarginIntention(duration),
new ImplicitlySyncPaddingIntention(duration),
new ImplicitlySyncTransformIntention(duration),
new ImplicitlySyncWidthIntention(duration),
new ImplicitlySyncHeightIntention(duration)
];
}
class AnimatedContainer extends AnimatedComponent { class AnimatedContainer extends AnimatedComponent {
AnimatedContainer({ AnimatedContainer({
Key key, Key key,
this.child, this.child,
this.duration, this.intentions,
this.tag,
this.constraints, this.constraints,
this.decoration, this.decoration,
this.width, this.width,
...@@ -90,7 +215,6 @@ class AnimatedContainer extends AnimatedComponent { ...@@ -90,7 +215,6 @@ class AnimatedContainer extends AnimatedComponent {
}) : super(key: key); }) : super(key: key);
Widget child; Widget child;
Duration duration; // TODO(abarth): Support separate durations for each value.
BoxConstraints constraints; BoxConstraints constraints;
BoxDecoration decoration; BoxDecoration decoration;
EdgeDims margin; EdgeDims margin;
...@@ -99,109 +223,30 @@ class AnimatedContainer extends AnimatedComponent { ...@@ -99,109 +223,30 @@ class AnimatedContainer extends AnimatedComponent {
double width; double width;
double height; double height;
ImplicitlyAnimatedValue<BoxConstraints> _constraints; List<AnimationIntention> intentions;
ImplicitlyAnimatedValue<BoxDecoration> _decoration; dynamic tag; // Used by intentions to determine desired state.
ImplicitlyAnimatedValue<EdgeDims> _margin;
ImplicitlyAnimatedValue<EdgeDims> _padding;
ImplicitlyAnimatedValue<Matrix4> _transform;
ImplicitlyAnimatedValue<double> _width;
ImplicitlyAnimatedValue<double> _height;
void initState() { void initState() {
_updateFields(); for (AnimationIntention i in intentions)
} i.initFields(this);
void syncFields(AnimatedContainer source) {
child = source.child;
constraints = source.constraints;
decoration = source.decoration;
margin = source.margin;
padding = source.padding;
width = source.width;
height = source.height;
_updateFields();
}
void _updateFields() {
_updateConstraints();
_updateDecoration();
_updateMargin();
_updatePadding();
_updateTransform();
_updateWidth();
_updateHeight();
}
void _updateField(dynamic value, ImplicitlyAnimatedValue animatedValue, Function initField) {
if (animatedValue != null)
animatedValue.value = value;
else if (value != null)
initField();
}
void _updateConstraints() {
_updateField(constraints, _constraints, () {
_constraints = new ImplicitlyAnimatedValue<BoxConstraints>(new AnimatedBoxConstraintsValue(constraints), duration);
watch(_constraints.performance);
});
}
void _updateDecoration() {
_updateField(decoration, _decoration, () {
_decoration = new ImplicitlyAnimatedValue<BoxDecoration>(new AnimatedBoxDecorationValue(decoration), duration);
watch(_decoration.performance);
});
}
void _updateMargin() {
_updateField(margin, _margin, () {
_margin = new ImplicitlyAnimatedValue<EdgeDims>(new AnimatedEdgeDimsValue(margin), duration);
watch(_margin.performance);
});
}
void _updatePadding() {
_updateField(padding, _padding, () {
_padding = new ImplicitlyAnimatedValue<EdgeDims>(new AnimatedEdgeDimsValue(padding), duration);
watch(_padding.performance);
});
}
void _updateTransform() {
_updateField(transform, _transform, () {
_transform = new ImplicitlyAnimatedValue<Matrix4>(new AnimatedValue<Matrix4>(transform), duration);
watch(_transform.performance);
});
}
void _updateWidth() {
_updateField(width, _width, () {
_width = new ImplicitlyAnimatedValue<double>(new AnimatedValue<double>(width), duration);
watch(_width.performance);
});
}
void _updateHeight() {
_updateField(height, _height, () {
_height = new ImplicitlyAnimatedValue<double>( new AnimatedValue<double>(height), duration);
watch(_height.performance);
});
} }
dynamic _getValue(dynamic value, ImplicitlyAnimatedValue animatedValue) { void syncFields(AnimatedContainer updated) {
return animatedValue == null ? value : animatedValue.value; child = updated.child;
for (AnimationIntention i in intentions)
i.syncFields(this, updated);
} }
Widget build() { Widget build() {
return new Container( return new Container(
child: child, child: child,
constraints: _getValue(constraints, _constraints), constraints: constraints,
decoration: _getValue(decoration, _decoration), decoration: decoration,
margin: _getValue(margin, _margin), margin: margin,
padding: _getValue(padding, _padding), padding: padding,
transform: _getValue(transform, _transform), transform: transform,
width: _getValue(width, _width), width: width,
height: _getValue(height, _height) height: height
); );
} }
} }
...@@ -49,7 +49,7 @@ class Material extends Component { ...@@ -49,7 +49,7 @@ class Material extends Component {
Widget build() { Widget build() {
return new AnimatedContainer( return new AnimatedContainer(
duration: const Duration(milliseconds: 200), intentions: implicitlySyncFieldsIntention(const Duration(milliseconds: 200)),
decoration: new BoxDecoration( decoration: new BoxDecoration(
backgroundColor: _backgroundColor, backgroundColor: _backgroundColor,
borderRadius: edges[type], borderRadius: edges[type],
......
...@@ -2,10 +2,13 @@ ...@@ -2,10 +2,13 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async';
import 'package:sky/animation/animated_value.dart'; import 'package:sky/animation/animated_value.dart';
import 'package:sky/animation/animation_performance.dart'; import 'package:sky/animation/animation_performance.dart';
import 'package:sky/painting/text_style.dart'; import 'package:sky/painting/text_style.dart';
import 'package:sky/theme/typography.dart' as typography; import 'package:sky/theme/typography.dart' as typography;
import 'package:sky/widgets/animated_container.dart';
import 'package:sky/widgets/animated_component.dart'; import 'package:sky/widgets/animated_component.dart';
import 'package:sky/widgets/basic.dart'; import 'package:sky/widgets/basic.dart';
import 'package:sky/widgets/default_text_style.dart'; import 'package:sky/widgets/default_text_style.dart';
...@@ -43,47 +46,33 @@ class SnackBarAction extends Component { ...@@ -43,47 +46,33 @@ class SnackBarAction extends Component {
} }
} }
class SnackBar extends AnimatedComponent { // TODO(mpcomplete): generalize this to a SlideIn class.
class SnackBarSlideInIntention extends AnimationIntention {
SnackBar({ SnackBarSlideInIntention(this.duration, this.onStatusChanged);
Key key,
this.content,
this.actions,
this.showing,
this.onStatusChanged
}) : super(key: key) {
assert(content != null);
}
Widget content; Duration duration;
List<SnackBarAction> actions;
bool showing;
SnackBarStatusChangedCallback onStatusChanged; SnackBarStatusChangedCallback onStatusChanged;
void syncFields(SnackBar source) {
content = source.content;
actions = source.actions;
onStatusChanged = source.onStatusChanged;
if (showing != source.showing) {
showing = source.showing;
showing ? _show() : _hide();
}
}
AnimatedValue<Point> _position; AnimatedValue<Point> _position;
AnimationPerformance _performance; AnimationPerformance _performance;
void initState() { void initFields(AnimatedContainer container) {
_position = new AnimatedValue<Point>(new Point(0.0, 50.0), end: Point.origin); _position = new AnimatedValue<Point>(new Point(0.0, 50.0), end: Point.origin);
_performance = new AnimationPerformance() _performance = new AnimationPerformance()
..duration = _kSlideInDuration ..duration = _kSlideInDuration
..variable = _position ..variable = _position
..addListener(_checkStatusChanged); ..addListener(() { _updateProgress(container); });
watch(_performance); _performance.progress = 0.0;
if (showing) if (container.tag)
_show(); _show();
} }
void syncFields(AnimatedContainer original, AnimatedContainer updated) {
if (original.tag != updated.tag) {
original.tag = updated.tag;
original.tag ? _show() : _hide();
}
}
void _show() { void _show() {
_performance.play(); _performance.play();
} }
...@@ -93,14 +82,50 @@ class SnackBar extends AnimatedComponent { ...@@ -93,14 +82,50 @@ class SnackBar extends AnimatedComponent {
} }
SnackBarStatus _lastStatus; SnackBarStatus _lastStatus;
void _checkStatusChanged() { void _updateProgress(AnimatedContainer container) {
container.setState(() {
container.transform = new Matrix4.identity()
..translate(_position.value.x, _position.value.y);
});
SnackBarStatus status = _status; SnackBarStatus status = _status;
if (_lastStatus != null && status != _lastStatus && onStatusChanged != null) if (_lastStatus != null && status != _lastStatus && onStatusChanged != null)
onStatusChanged(status); scheduleMicrotask(() { onStatusChanged(status); });
_lastStatus = status; _lastStatus = status;
} }
SnackBarStatus get _status => _performance.isDismissed ? SnackBarStatus.inactive : SnackBarStatus.active; SnackBarStatus get _status => _performance.isDismissed ? SnackBarStatus.inactive : SnackBarStatus.active;
}
class SnackBar extends StatefulComponent {
SnackBar({
Key key,
this.content,
this.actions,
this.showing,
this.onStatusChanged
}) : super(key: key) {
assert(content != null);
}
Widget content;
List<SnackBarAction> actions;
bool showing;
SnackBarStatusChangedCallback onStatusChanged;
SnackBarSlideInIntention _intention;
void initState() {
_intention = new SnackBarSlideInIntention(_kSlideInDuration, onStatusChanged);
}
void syncFields(SnackBar source) {
content = source.content;
actions = source.actions;
onStatusChanged = source.onStatusChanged;
showing = source.showing;
}
Widget build() { Widget build() {
List<Widget> children = [ List<Widget> children = [
...@@ -115,11 +140,10 @@ class SnackBar extends AnimatedComponent { ...@@ -115,11 +140,10 @@ class SnackBar extends AnimatedComponent {
) )
]..addAll(actions); ]..addAll(actions);
Matrix4 transform = new Matrix4.identity(); return new AnimatedContainer(
transform.translate(_position.value.x, _position.value.y); intentions: [_intention],
return new Transform( tag: showing,
transform: transform, child: new Material(
child: new Material(
level: 2, level: 2,
color: const Color(0xFF323232), color: const Color(0xFF323232),
type: MaterialType.canvas, type: MaterialType.canvas,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment