Commit 552f26d7 authored by Hixie's avatar Hixie

Stop AnimatedContainer from animating every build

It turns out that an AnimatedContainer with a BoxDecoration would
start animating every single time it was built, whether or not the
container's decoration had changed.
parent 6b2d121f
...@@ -30,6 +30,8 @@ class Scheduler { ...@@ -30,6 +30,8 @@ class Scheduler {
Map<int, SchedulerCallback> _transientCallbacks = new LinkedHashMap<int, SchedulerCallback>(); Map<int, SchedulerCallback> _transientCallbacks = new LinkedHashMap<int, SchedulerCallback>();
final Set<int> _removedIds = new Set<int>(); final Set<int> _removedIds = new Set<int>();
int get transientCallbackCount => _transientCallbacks.length;
/// Called by the engine to produce a new frame. /// Called by the engine to produce a new frame.
/// ///
/// This function first calls all the callbacks registered by /// This function first calls all the callbacks registered by
......
...@@ -51,7 +51,7 @@ class EdgeDims { ...@@ -51,7 +51,7 @@ class EdgeDims {
/// The size that this edge dims would occupy with an empty interior. /// The size that this edge dims would occupy with an empty interior.
Size get collapsedSize => new Size(left + right, top + bottom); Size get collapsedSize => new Size(left + right, top + bottom);
EdgeDims operator-(EdgeDims other) { EdgeDims operator -(EdgeDims other) {
return new EdgeDims.TRBL( return new EdgeDims.TRBL(
top - other.top, top - other.top,
right - other.right, right - other.right,
...@@ -60,7 +60,7 @@ class EdgeDims { ...@@ -60,7 +60,7 @@ class EdgeDims {
); );
} }
EdgeDims operator+(EdgeDims other) { EdgeDims operator +(EdgeDims other) {
return new EdgeDims.TRBL( return new EdgeDims.TRBL(
top + other.top, top + other.top,
right + other.right, right + other.right,
...@@ -69,7 +69,7 @@ class EdgeDims { ...@@ -69,7 +69,7 @@ class EdgeDims {
); );
} }
EdgeDims operator*(double other) { EdgeDims operator *(double other) {
return new EdgeDims.TRBL( return new EdgeDims.TRBL(
top * other, top * other,
right * other, right * other,
...@@ -78,7 +78,7 @@ class EdgeDims { ...@@ -78,7 +78,7 @@ class EdgeDims {
); );
} }
EdgeDims operator/(double other) { EdgeDims operator /(double other) {
return new EdgeDims.TRBL( return new EdgeDims.TRBL(
top / other, top / other,
right / other, right / other,
...@@ -87,7 +87,7 @@ class EdgeDims { ...@@ -87,7 +87,7 @@ class EdgeDims {
); );
} }
EdgeDims operator~/(double other) { EdgeDims operator ~/(double other) {
return new EdgeDims.TRBL( return new EdgeDims.TRBL(
(top ~/ other).toDouble(), (top ~/ other).toDouble(),
(right ~/ other).toDouble(), (right ~/ other).toDouble(),
...@@ -96,7 +96,7 @@ class EdgeDims { ...@@ -96,7 +96,7 @@ class EdgeDims {
); );
} }
EdgeDims operator%(double other) { EdgeDims operator %(double other) {
return new EdgeDims.TRBL( return new EdgeDims.TRBL(
top % other, top % other,
right % other, right % other,
...@@ -166,12 +166,23 @@ class BorderSide { ...@@ -166,12 +166,23 @@ class BorderSide {
/// A black border side of zero width /// A black border side of zero width
static const none = const BorderSide(width: 0.0); static const none = const BorderSide(width: 0.0);
bool operator ==(dynamic other) {
if (identical(this, other))
return true;
if (other is! BorderSide)
return false;
final BorderSide typedOther = other;
return color == typedOther.color &&
width == typedOther.width;
}
int get hashCode { int get hashCode {
int value = 373; int value = 373;
value = 37 * value * color.hashCode; value = 37 * value + color.hashCode;
value = 37 * value * width.hashCode; value = 37 * value + width.hashCode;
return value; return value;
} }
String toString() => 'BorderSide($color, $width)'; String toString() => 'BorderSide($color, $width)';
} }
...@@ -210,14 +221,27 @@ class Border { ...@@ -210,14 +221,27 @@ class Border {
return new EdgeDims.TRBL(top.width, right.width, bottom.width, left.width); return new EdgeDims.TRBL(top.width, right.width, bottom.width, left.width);
} }
bool operator ==(dynamic other) {
if (identical(this, other))
return true;
if (other is! Border)
return false;
final Border typedOther = other;
return top == typedOther.top &&
right == typedOther.right &&
bottom == typedOther.bottom &&
left == typedOther.left;
}
int get hashCode { int get hashCode {
int value = 373; int value = 373;
value = 37 * value * top.hashCode; value = 37 * value + top.hashCode;
value = 37 * value * right.hashCode; value = 37 * value + right.hashCode;
value = 37 * value * bottom.hashCode; value = 37 * value + bottom.hashCode;
value = 37 * value * left.hashCode; value = 37 * value + left.hashCode;
return value; return value;
} }
String toString() => 'Border($top, $right, $bottom, $left)'; String toString() => 'Border($top, $right, $bottom, $left)';
} }
...@@ -290,17 +314,37 @@ class BoxShadow { ...@@ -290,17 +314,37 @@ class BoxShadow {
return result; return result;
} }
bool operator ==(dynamic other) {
if (identical(this, other))
return true;
if (other is! BoxShadow)
return false;
final BoxShadow typedOther = other;
return color == typedOther.color &&
offset == typedOther.offset &&
blur == typedOther.blur;
}
int get hashCode {
int value = 373;
value = 37 * value + color.hashCode;
value = 37 * value + offset.hashCode;
value = 37 * value + blur.hashCode;
return value;
}
String toString() => 'BoxShadow($color, $offset, $blur)'; String toString() => 'BoxShadow($color, $offset, $blur)';
} }
/// A 2D gradient /// A 2D gradient
abstract class Gradient { abstract class Gradient {
const Gradient();
ui.Shader createShader(); ui.Shader createShader();
} }
/// A 2D linear gradient /// A 2D linear gradient
class LinearGradient extends Gradient { class LinearGradient extends Gradient {
LinearGradient({ const LinearGradient({
this.begin, this.begin,
this.end, this.end,
this.colors, this.colors,
...@@ -332,6 +376,57 @@ class LinearGradient extends Gradient { ...@@ -332,6 +376,57 @@ class LinearGradient extends Gradient {
return new ui.Gradient.linear(<Point>[begin, end], this.colors, this.stops, this.tileMode); return new ui.Gradient.linear(<Point>[begin, end], this.colors, this.stops, this.tileMode);
} }
bool operator ==(dynamic other) {
if (identical(this, other))
return true;
if (other is! LinearGradient)
return false;
final LinearGradient typedOther = other;
if (begin != typedOther.begin ||
end != typedOther.end ||
tileMode != typedOther.tileMode ||
colors?.length != typedOther.colors?.length ||
stops?.length != typedOther.stops?.length)
return false;
if (colors != null) {
assert(typedOther.colors != null);
assert(colors.length == typedOther.colors.length);
for (int i = 0; i < colors.length; i += 1) {
if (colors[i] != typedOther.colors[i])
return false;
}
}
if (stops != null) {
assert(typedOther.stops != null);
assert(stops.length == typedOther.stops.length);
for (int i = 0; i < stops.length; i += 1) {
if (stops[i] != typedOther.stops[i])
return false;
}
}
return true;
}
int get hashCode {
int value = 373;
value = 37 * value + begin.hashCode;
value = 37 * value + end.hashCode;
value = 37 * value + tileMode.hashCode;
if (colors != null) {
for (int i = 0; i < colors.length; i += 1)
value = 37 * value + colors[i].hashCode;
} else {
value = 37 * value + null.hashCode;
}
if (stops != null) {
for (int i = 0; i < stops.length; i += 1)
value = 37 * value + stops[i].hashCode;
} else {
value = 37 * value + null.hashCode;
}
return value;
}
String toString() { String toString() {
return 'LinearGradient($begin, $end, $colors, $stops, $tileMode)'; return 'LinearGradient($begin, $end, $colors, $stops, $tileMode)';
} }
...@@ -339,7 +434,7 @@ class LinearGradient extends Gradient { ...@@ -339,7 +434,7 @@ class LinearGradient extends Gradient {
/// A 2D radial gradient /// A 2D radial gradient
class RadialGradient extends Gradient { class RadialGradient extends Gradient {
RadialGradient({ const RadialGradient({
this.center, this.center,
this.radius, this.radius,
this.colors, this.colors,
...@@ -373,6 +468,57 @@ class RadialGradient extends Gradient { ...@@ -373,6 +468,57 @@ class RadialGradient extends Gradient {
return new ui.Gradient.radial(center, radius, colors, stops, tileMode); return new ui.Gradient.radial(center, radius, colors, stops, tileMode);
} }
bool operator ==(dynamic other) {
if (identical(this, other))
return true;
if (other is! RadialGradient)
return false;
final RadialGradient typedOther = other;
if (center != typedOther.center ||
radius != typedOther.radius ||
tileMode != typedOther.tileMode ||
colors?.length != typedOther.colors?.length ||
stops?.length != typedOther.stops?.length)
return false;
if (colors != null) {
assert(typedOther.colors != null);
assert(colors.length == typedOther.colors.length);
for (int i = 0; i < colors.length; i += 1) {
if (colors[i] != typedOther.colors[i])
return false;
}
}
if (stops != null) {
assert(typedOther.stops != null);
assert(stops.length == typedOther.stops.length);
for (int i = 0; i < stops.length; i += 1) {
if (stops[i] != typedOther.stops[i])
return false;
}
}
return true;
}
int get hashCode {
int value = 373;
value = 37 * value + center.hashCode;
value = 37 * value + radius.hashCode;
value = 37 * value + tileMode.hashCode;
if (colors != null) {
for (int i = 0; i < colors.length; i += 1)
value = 37 * value + colors[i].hashCode;
} else {
value = 37 * value + null.hashCode;
}
if (stops != null) {
for (int i = 0; i < stops.length; i += 1)
value = 37 * value + stops[i].hashCode;
} else {
value = 37 * value + null.hashCode;
}
return value;
}
String toString() { String toString() {
return 'RadialGradient($center, $radius, $colors, $stops, $tileMode)'; return 'RadialGradient($center, $radius, $colors, $stops, $tileMode)';
} }
...@@ -498,6 +644,14 @@ typedef void BackgroundImageChangeListener(); ...@@ -498,6 +644,14 @@ typedef void BackgroundImageChangeListener();
/// A background image for a box. /// A background image for a box.
class BackgroundImage { class BackgroundImage {
BackgroundImage({
ImageResource image,
this.fit,
this.repeat: ImageRepeat.noRepeat,
this.centerSlice,
this.colorFilter
}) : _imageResource = image;
/// How the background image should be inscribed into the box. /// How the background image should be inscribed into the box.
final ImageFit fit; final ImageFit fit;
...@@ -516,22 +670,14 @@ class BackgroundImage { ...@@ -516,22 +670,14 @@ class BackgroundImage {
/// A color filter to apply to the background image before painting it. /// A color filter to apply to the background image before painting it.
final ui.ColorFilter colorFilter; final ui.ColorFilter colorFilter;
BackgroundImage({
ImageResource image,
this.fit,
this.repeat: ImageRepeat.noRepeat,
this.centerSlice,
this.colorFilter
}) : _imageResource = image;
/// The image to be painted into the background. /// The image to be painted into the background.
ui.Image get image => _image; ui.Image get image => _image;
ui.Image _image; ui.Image _image;
ImageResource _imageResource; final ImageResource _imageResource;
final List<BackgroundImageChangeListener> _listeners = final List<BackgroundImageChangeListener> _listeners =
new List<BackgroundImageChangeListener>(); new List<BackgroundImageChangeListener>();
/// Call listener when the background images changes (e.g., arrives from the network). /// Call listener when the background images changes (e.g., arrives from the network).
void addChangeListener(BackgroundImageChangeListener listener) { void addChangeListener(BackgroundImageChangeListener listener) {
...@@ -557,10 +703,32 @@ class BackgroundImage { ...@@ -557,10 +703,32 @@ class BackgroundImage {
return; return;
_image = resolvedImage; _image = resolvedImage;
final List<BackgroundImageChangeListener> localListeners = final List<BackgroundImageChangeListener> localListeners =
new List<BackgroundImageChangeListener>.from(_listeners); new List<BackgroundImageChangeListener>.from(_listeners);
for (BackgroundImageChangeListener listener in localListeners) { for (BackgroundImageChangeListener listener in localListeners)
listener(); listener();
} }
bool operator ==(dynamic other) {
if (identical(this, other))
return true;
if (other is! BackgroundImage)
return false;
final BackgroundImage typedOther = other;
return fit == typedOther.fit &&
repeat == typedOther.repeat &&
centerSlice == typedOther.centerSlice &&
colorFilter == typedOther.colorFilter &&
_imageResource == typedOther._imageResource;
}
int get hashCode {
int value = 373;
value = 37 * value + fit.hashCode;
value = 37 * value + repeat.hashCode;
value = 37 * value + centerSlice.hashCode;
value = 37 * value + colorFilter.hashCode;
value = 37 * value + _imageResource.hashCode;
return value;
} }
String toString() => 'BackgroundImage($fit, $repeat)'; String toString() => 'BackgroundImage($fit, $repeat)';
...@@ -650,6 +818,33 @@ class BoxDecoration { ...@@ -650,6 +818,33 @@ class BoxDecoration {
); );
} }
bool operator ==(dynamic other) {
if (identical(this, other))
return true;
if (other is! BoxDecoration)
return false;
final BoxDecoration typedOther = other;
return backgroundColor == typedOther.backgroundColor &&
backgroundImage == typedOther.backgroundImage &&
border == typedOther.border &&
borderRadius == typedOther.borderRadius &&
boxShadow == typedOther.boxShadow &&
gradient == typedOther.gradient &&
shape == typedOther.shape;
}
int get hashCode {
int value = 373;
value = 37 * value + backgroundColor.hashCode;
value = 37 * value + backgroundImage.hashCode;
value = 37 * value + border.hashCode;
value = 37 * value + borderRadius.hashCode;
value = 37 * value + boxShadow.hashCode;
value = 37 * value + gradient.hashCode;
value = 37 * value + shape.hashCode;
return value;
}
String toString([String prefix = '']) { String toString([String prefix = '']) {
List<String> result = <String>[]; List<String> result = <String>[];
if (backgroundColor != null) if (backgroundColor != null)
......
...@@ -66,6 +66,7 @@ class AnimatedContainer extends StatefulComponent { ...@@ -66,6 +66,7 @@ class AnimatedContainer extends StatefulComponent {
} }
final Widget child; final Widget child;
final BoxConstraints constraints; final BoxConstraints constraints;
final BoxDecoration decoration; final BoxDecoration decoration;
final BoxDecoration foregroundDecoration; final BoxDecoration foregroundDecoration;
...@@ -95,7 +96,7 @@ class _AnimatedContainerState extends State<AnimatedContainer> { ...@@ -95,7 +96,7 @@ class _AnimatedContainerState extends State<AnimatedContainer> {
void initState() { void initState() {
super.initState(); super.initState();
_performance = new Performance(duration: config.duration) _performance = new Performance(duration: config.duration, debugLabel: '${config.toStringShort()}')
..timing = new AnimationTiming(curve: config.curve) ..timing = new AnimationTiming(curve: config.curve)
..addListener(_updateAllVariables); ..addListener(_updateAllVariables);
_configAllVariables(); _configAllVariables();
......
...@@ -202,8 +202,12 @@ abstract class Widget { ...@@ -202,8 +202,12 @@ abstract class Widget {
/// Inflates this configuration to a concrete instance. /// Inflates this configuration to a concrete instance.
Element createElement(); Element createElement();
String toStringShort() {
return key == null ? '$runtimeType' : '$runtimeType-$key';
}
String toString() { String toString() {
final String name = key == null ? '$runtimeType' : '$runtimeType-$key'; final String name = toStringShort();
final List<String> data = <String>[]; final List<String> data = <String>[];
debugFillDescription(data); debugFillDescription(data);
if (data.isEmpty) if (data.isEmpty)
......
import 'package:flutter/animation.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
...@@ -45,4 +46,51 @@ void main() { ...@@ -45,4 +46,51 @@ void main() {
}); });
}); });
test('AnimatedContainer overanimate test', () {
testWidgets((WidgetTester tester) {
tester.pumpWidget(
new AnimatedContainer(
duration: const Duration(milliseconds: 200),
decoration: new BoxDecoration(
backgroundColor: new Color(0xFF00FF00)
)
)
);
expect(scheduler.transientCallbackCount, 0);
tester.pump(new Duration(seconds: 1));
expect(scheduler.transientCallbackCount, 0);
tester.pumpWidget(
new AnimatedContainer(
duration: const Duration(milliseconds: 200),
decoration: new BoxDecoration(
backgroundColor: new Color(0xFF00FF00)
)
)
);
expect(scheduler.transientCallbackCount, 0);
tester.pump(new Duration(seconds: 1));
expect(scheduler.transientCallbackCount, 0);
tester.pumpWidget(
new AnimatedContainer(
duration: const Duration(milliseconds: 200),
decoration: new BoxDecoration(
backgroundColor: new Color(0xFF0000FF)
)
)
);
expect(scheduler.transientCallbackCount, 1); // this is the only time an animation should have started!
tester.pump(new Duration(seconds: 1));
expect(scheduler.transientCallbackCount, 0);
tester.pumpWidget(
new AnimatedContainer(
duration: const Duration(milliseconds: 200),
decoration: new BoxDecoration(
backgroundColor: new Color(0xFF0000FF)
)
)
);
expect(scheduler.transientCallbackCount, 0);
});
});
} }
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