Commit 948f3db2 authored by Ian Hickson's avatar Ian Hickson

Refactor AnimatedContainer to be reusable.

This will allow AnimatedPositioned to reuse all the same logic.
parent 682243ef
...@@ -56,80 +56,31 @@ class AnimatedMatrix4Value extends AnimatedValue<Matrix4> { ...@@ -56,80 +56,31 @@ class AnimatedMatrix4Value extends AnimatedValue<Matrix4> {
} }
} }
/// A container that gradually changes its values over a period of time. /// An abstract widget for building components that gradually change their
/// /// values over a period of time.
/// This class is useful for generating simple implicit transitions between abstract class AnimatedWidgetBase extends StatefulComponent {
/// different parameters to [Container]. For more complex animations, you'll AnimatedWidgetBase({
/// likely want to use a subclass of [Transition] or control a [Performance]
/// yourself.
class AnimatedContainer extends StatefulComponent {
AnimatedContainer({
Key key, Key key,
this.child,
this.constraints,
this.decoration,
this.foregroundDecoration,
this.margin,
this.padding,
this.transform,
this.width,
this.height,
this.curve: Curves.linear, this.curve: Curves.linear,
this.duration this.duration
}) : super(key: key) { }) : super(key: key) {
assert(margin == null || margin.isNonNegative);
assert(padding == null || padding.isNonNegative);
assert(decoration == null || decoration.debugAssertValid());
assert(foregroundDecoration == null || foregroundDecoration.debugAssertValid());
assert(curve != null); assert(curve != null);
assert(duration != null); assert(duration != null);
} }
final Widget child;
/// Additional constraints to apply to the child.
final BoxConstraints constraints;
/// The decoration to paint behind the child.
final Decoration decoration;
/// The decoration to paint in front of the child.
final Decoration foregroundDecoration;
/// Empty space to surround the decoration.
final EdgeDims margin;
/// Empty space to inscribe inside the decoration.
final EdgeDims padding;
/// The transformation matrix to apply before painting the container.
final Matrix4 transform;
/// If non-null, requires the decoration to have this width.
final double width;
/// If non-null, requires the decoration to have this height.
final double height;
/// The curve to apply when animating the parameters of this container. /// The curve to apply when animating the parameters of this container.
final Curve curve; final Curve curve;
/// The duration over which to animate the parameters of this container. /// The duration over which to animate the parameters of this container.
final Duration duration; final Duration duration;
_AnimatedContainerState createState() => new _AnimatedContainerState(); AnimatedWidgetBaseState createState();
} }
class _AnimatedContainerState extends State<AnimatedContainer> { typedef AnimatedValue<T> VariableConstructor<T>(T targetValue);
AnimatedBoxConstraintsValue _constraints; typedef AnimatedValue<T> VariableVisitor<T>(AnimatedValue<T> variable, T targetValue, VariableConstructor<T> constructor);
AnimatedDecorationValue _decoration;
AnimatedDecorationValue _foregroundDecoration;
AnimatedEdgeDimsValue _margin;
AnimatedEdgeDimsValue _padding;
AnimatedMatrix4Value _transform;
AnimatedValue<double> _width;
AnimatedValue<double> _height;
abstract class AnimatedWidgetBaseState<T extends AnimatedWidgetBase> extends State<T> {
Performance _performanceController; Performance _performanceController;
PerformanceView _performance; PerformanceView _performance;
...@@ -143,19 +94,14 @@ class _AnimatedContainerState extends State<AnimatedContainer> { ...@@ -143,19 +94,14 @@ class _AnimatedContainerState extends State<AnimatedContainer> {
_configAllVariables(); _configAllVariables();
} }
void didUpdateConfig(AnimatedContainer oldConfig) { void didUpdateConfig(T oldConfig) {
if (config.curve != oldConfig.curve) if (config.curve != oldConfig.curve)
_updateCurve(); _updateCurve();
_performanceController.duration = config.duration; _performanceController.duration = config.duration;
if (_configAllVariables()) { if (_configAllVariables()) {
_updateBeginValue(_constraints); forEachVariable((AnimatedValue variable, dynamic targetValue, VariableConstructor<T> constructor) {
_updateBeginValue(_decoration); _updateBeginValue(variable); return variable;
_updateBeginValue(_foregroundDecoration); });
_updateBeginValue(_margin);
_updateBeginValue(_padding);
_updateBeginValue(_transform);
_updateBeginValue(_width);
_updateBeginValue(_height);
_performanceController.progress = 0.0; _performanceController.progress = 0.0;
_performanceController.play(); _performanceController.play();
} }
...@@ -182,14 +128,9 @@ class _AnimatedContainerState extends State<AnimatedContainer> { ...@@ -182,14 +128,9 @@ class _AnimatedContainerState extends State<AnimatedContainer> {
void _updateAllVariables() { void _updateAllVariables() {
setState(() { setState(() {
_updateVariable(_constraints); forEachVariable((AnimatedValue variable, dynamic targetValue, VariableConstructor<T> constructor) {
_updateVariable(_decoration); _updateVariable(variable); return variable;
_updateVariable(_foregroundDecoration); });
_updateVariable(_margin);
_updateVariable(_padding);
_updateVariable(_transform);
_updateVariable(_width);
_updateVariable(_height);
}); });
} }
...@@ -206,71 +147,113 @@ class _AnimatedContainerState extends State<AnimatedContainer> { ...@@ -206,71 +147,113 @@ class _AnimatedContainerState extends State<AnimatedContainer> {
bool _configAllVariables() { bool _configAllVariables() {
bool startAnimation = false; bool startAnimation = false;
if (config.constraints != null) { forEachVariable((AnimatedValue variable, dynamic targetValue, VariableConstructor<T> constructor) {
_constraints ??= new AnimatedBoxConstraintsValue(config.constraints); if (targetValue != null) {
if (_updateEndValue(_constraints, config.constraints)) variable ??= constructor(targetValue);
startAnimation = true; if (_updateEndValue(variable, targetValue))
} else { startAnimation = true;
_constraints = null; } else {
} variable = null;
}
return variable;
});
return startAnimation;
}
if (config.decoration != null) { /// Subclasses must implement this function by running through the following
_decoration ??= new AnimatedDecorationValue(config.decoration); /// steps for for each animatable facet in the class:
if (_updateEndValue(_decoration, config.decoration)) ///
startAnimation = true; /// 1. Call the visitor callback with three arguments, the first argument
} else { /// being the current value of the AnimatedValue<T> object that represents the
_decoration = null; /// variable (initially null), the second argument, of type T, being the value
} /// on the Widget (config) that represents the current target value of the
/// variable, and the third being a callback that takes a value T (which will
/// be the second argument to the visitor callback), and that returns an
/// AnimatedValue<T> object for the variable, configured with the given value
/// as the begin value.
///
/// 2. Take the value returned from the callback, and store it. This is the
/// value to use as the current value the next time that the forEachVariable()
/// method is called.
void forEachVariable(VariableVisitor visitor);
}
if (config.foregroundDecoration != null) { /// A container that gradually changes its values over a period of time.
_foregroundDecoration ??= new AnimatedDecorationValue(config.foregroundDecoration); ///
if (_updateEndValue(_foregroundDecoration, config.foregroundDecoration)) /// This class is useful for generating simple implicit transitions between
startAnimation = true; /// different parameters to [Container]. For more complex animations, you'll
} else { /// likely want to use a subclass of [Transition] or control a [Performance]
_foregroundDecoration = null; /// yourself.
} class AnimatedContainer extends AnimatedWidgetBase {
AnimatedContainer({
Key key,
this.child,
this.constraints,
this.decoration,
this.foregroundDecoration,
this.margin,
this.padding,
this.transform,
this.width,
this.height,
Curve curve: Curves.linear,
Duration duration
}) : super(key: key, curve: curve, duration: duration) {
assert(decoration == null || decoration.debugAssertValid());
assert(foregroundDecoration == null || foregroundDecoration.debugAssertValid());
assert(margin == null || margin.isNonNegative);
assert(padding == null || padding.isNonNegative);
}
if (config.margin != null) { final Widget child;
_margin ??= new AnimatedEdgeDimsValue(config.margin);
if (_updateEndValue(_margin, config.margin))
startAnimation = true;
} else {
_margin = null;
}
if (config.padding != null) { /// Additional constraints to apply to the child.
_padding ??= new AnimatedEdgeDimsValue(config.padding); final BoxConstraints constraints;
if (_updateEndValue(_padding, config.padding))
startAnimation = true;
} else {
_padding = null;
}
if (config.transform != null) { /// The decoration to paint behind the child.
_transform ??= new AnimatedMatrix4Value(config.transform); final Decoration decoration;
if (_updateEndValue(_transform, config.transform))
startAnimation = true;
} else {
_transform = null;
}
if (config.width != null) { /// The decoration to paint in front of the child.
_width ??= new AnimatedValue<double>(config.width); final Decoration foregroundDecoration;
if (_updateEndValue(_width, config.width))
startAnimation = true;
} else {
_width = null;
}
if (config.height != null) { /// Empty space to surround the decoration.
_height ??= new AnimatedValue<double>(config.height); final EdgeDims margin;
if (_updateEndValue(_height, config.height))
startAnimation = true;
} else {
_height = null;
}
return startAnimation; /// Empty space to inscribe inside the decoration.
final EdgeDims padding;
/// The transformation matrix to apply before painting the container.
final Matrix4 transform;
/// If non-null, requires the decoration to have this width.
final double width;
/// If non-null, requires the decoration to have this height.
final double height;
_AnimatedContainerState createState() => new _AnimatedContainerState();
}
class _AnimatedContainerState extends AnimatedWidgetBaseState<AnimatedContainer> {
AnimatedBoxConstraintsValue _constraints;
AnimatedDecorationValue _decoration;
AnimatedDecorationValue _foregroundDecoration;
AnimatedEdgeDimsValue _margin;
AnimatedEdgeDimsValue _padding;
AnimatedMatrix4Value _transform;
AnimatedValue<double> _width;
AnimatedValue<double> _height;
void forEachVariable(VariableVisitor visitor) {
// TODO(ianh): Use constructor tear-offs when it becomes possible
_constraints = visitor(_constraints, config.constraints, (dynamic value) => new AnimatedBoxConstraintsValue(value));
_decoration = visitor(_decoration, config.decoration, (dynamic value) => new AnimatedDecorationValue(value));
_foregroundDecoration = visitor(_foregroundDecoration, config.foregroundDecoration, (dynamic value) => new AnimatedDecorationValue(value));
_margin = visitor(_margin, config.margin, (dynamic value) => new AnimatedEdgeDimsValue(value));
_padding = visitor(_padding, config.padding, (dynamic value) => new AnimatedEdgeDimsValue(value));
_transform = visitor(_transform, config.transform, (dynamic value) => new AnimatedMatrix4Value(value));
_width = visitor(_width, config.width, (dynamic value) => new AnimatedValue<double>(value));
_height = visitor(_height, config.height, (dynamic value) => new AnimatedValue<double>(value));
} }
Widget build(BuildContext context) { Widget build(BuildContext context) {
......
...@@ -1023,15 +1023,15 @@ class Positioned extends ParentDataWidget<StackRenderObjectWidgetBase> { ...@@ -1023,15 +1023,15 @@ class Positioned extends ParentDataWidget<StackRenderObjectWidgetBase> {
Positioned({ Positioned({
Key key, Key key,
Widget child, Widget child,
this.left,
this.top, this.top,
this.right, this.right,
this.bottom, this.bottom,
this.left,
this.width, this.width,
this.height this.height
}) : super(key: key, child: child) { }) : super(key: key, child: child) {
assert(top == null || bottom == null || height == null);
assert(left == null || right == null || width == null); assert(left == null || right == null || width == null);
assert(top == null || bottom == null || height == null);
} }
Positioned.fromRect({ Positioned.fromRect({
...@@ -1046,6 +1046,9 @@ class Positioned extends ParentDataWidget<StackRenderObjectWidgetBase> { ...@@ -1046,6 +1046,9 @@ class Positioned extends ParentDataWidget<StackRenderObjectWidgetBase> {
bottom = null, bottom = null,
super(key: key, child: child); super(key: key, child: child);
/// The offset of the child's left edge from the left of the stack.
final double left;
/// The offset of the child's top edge from the top of the stack. /// The offset of the child's top edge from the top of the stack.
final double top; final double top;
...@@ -1055,17 +1058,16 @@ class Positioned extends ParentDataWidget<StackRenderObjectWidgetBase> { ...@@ -1055,17 +1058,16 @@ class Positioned extends ParentDataWidget<StackRenderObjectWidgetBase> {
/// The offset of the child's bottom edge from the bottom of the stack. /// The offset of the child's bottom edge from the bottom of the stack.
final double bottom; final double bottom;
/// The offset of the child's left edge from the left of the stack.
final double left;
/// The child's width. /// The child's width.
/// ///
/// Ignored if both left and right are non-null. /// Only two out of the three horizontal values (left, right, width) can be
/// set. The third must be null.
final double width; final double width;
/// The child's height. /// The child's height.
/// ///
/// Ignored if both top and bottom are non-null. /// Only two out of the three vertical values (top, bottom, height) can be
/// set. The third must be null.
final double height; final double height;
void applyParentData(RenderObject renderObject) { void applyParentData(RenderObject renderObject) {
...@@ -1073,6 +1075,11 @@ class Positioned extends ParentDataWidget<StackRenderObjectWidgetBase> { ...@@ -1073,6 +1075,11 @@ class Positioned extends ParentDataWidget<StackRenderObjectWidgetBase> {
final StackParentData parentData = renderObject.parentData; final StackParentData parentData = renderObject.parentData;
bool needsLayout = false; bool needsLayout = false;
if (parentData.left != left) {
parentData.left = left;
needsLayout = true;
}
if (parentData.top != top) { if (parentData.top != top) {
parentData.top = top; parentData.top = top;
needsLayout = true; needsLayout = true;
...@@ -1088,11 +1095,6 @@ class Positioned extends ParentDataWidget<StackRenderObjectWidgetBase> { ...@@ -1088,11 +1095,6 @@ class Positioned extends ParentDataWidget<StackRenderObjectWidgetBase> {
needsLayout = true; needsLayout = true;
} }
if (parentData.left != left) {
parentData.left = left;
needsLayout = true;
}
if (parentData.width != width) { if (parentData.width != width) {
parentData.width = width; parentData.width = width;
needsLayout = true; needsLayout = true;
......
...@@ -226,7 +226,11 @@ class AnimatedRelativeRectValue extends AnimatedValue<RelativeRect> { ...@@ -226,7 +226,11 @@ class AnimatedRelativeRectValue extends AnimatedValue<RelativeRect> {
RelativeRect lerp(double t) => RelativeRect.lerp(begin, end, t); RelativeRect lerp(double t) => RelativeRect.lerp(begin, end, t);
} }
/// Animated version of [Positioned]. /// Animated version of [Positioned] which takes a specific
/// [AnimatedRelativeRectValue] and a [PerformanceView] to transition the
/// child's position from a start position to and end position over the lifetime
/// of the performance.
///
/// Only works if it's the child of a [Stack]. /// Only works if it's the child of a [Stack].
class PositionedTransition extends TransitionWithChild { class PositionedTransition extends TransitionWithChild {
PositionedTransition({ PositionedTransition({
......
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