Commit 274d2986 authored by Adam Barth's avatar Adam Barth

Merge pull request #1463 from abarth/animated_container

Add AnimatedContainer
parents 8faa7789 c88ca5db
...@@ -178,6 +178,60 @@ class BoxConstraints extends Constraints { ...@@ -178,6 +178,60 @@ class BoxConstraints extends Constraints {
(minHeight <= size.height) && (size.height <= math.max(minHeight, maxHeight)); (minHeight <= size.height) && (size.height <= math.max(minHeight, maxHeight));
} }
BoxConstraints operator*(double other) {
return new BoxConstraints(
minWidth: minWidth * other,
maxWidth: maxWidth * other,
minHeight: minHeight * other,
maxHeight: maxHeight * other
);
}
BoxConstraints operator/(double other) {
return new BoxConstraints(
minWidth: minWidth / other,
maxWidth: maxWidth / other,
minHeight: minHeight / other,
maxHeight: maxHeight / other
);
}
BoxConstraints operator~/(double other) {
return new BoxConstraints(
minWidth: (minWidth ~/ other).toDouble(),
maxWidth: (maxWidth ~/ other).toDouble(),
minHeight: (minHeight ~/ other).toDouble(),
maxHeight: (maxHeight ~/ other).toDouble()
);
}
BoxConstraints operator%(double other) {
return new BoxConstraints(
minWidth: minWidth % other,
maxWidth: maxWidth % other,
minHeight: minHeight % other,
maxHeight: maxHeight % other
);
}
/// Linearly interpolate between two BoxConstraints
///
/// If either is null, this function interpolates from [BoxConstraints.zero].
static BoxConstraints lerp(BoxConstraints a, BoxConstraints b, double t) {
if (a == null && b == null)
return null;
if (a == null)
return b * t;
if (b == null)
return a * (1.0 - t);
return new BoxConstraints(
minWidth: sky.lerpDouble(a.minWidth, b.minWidth, t),
maxWidth: sky.lerpDouble(a.maxWidth, b.maxWidth, t),
minHeight: sky.lerpDouble(a.minHeight, b.minHeight, t),
maxHeight: sky.lerpDouble(a.maxHeight, b.maxHeight, t)
);
}
bool operator ==(other) { bool operator ==(other) {
if (identical(this, other)) if (identical(this, other))
return true; return true;
......
// 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.
import 'package:sky/animation.dart';
import 'package:sky/src/widgets/basic.dart';
import 'package:sky/src/widgets/framework.dart';
import 'package:vector_math/vector_math_64.dart';
class AnimatedBoxConstraintsValue extends AnimatedValue<BoxConstraints> {
AnimatedBoxConstraintsValue(BoxConstraints begin, { BoxConstraints end, Curve curve: linear })
: super(begin, end: end, curve: curve);
BoxConstraints lerp(double t) => BoxConstraints.lerp(begin, end, t);
}
class AnimatedBoxDecorationValue extends AnimatedValue<BoxDecoration> {
AnimatedBoxDecorationValue(BoxDecoration begin, { BoxDecoration end, Curve curve: linear })
: super(begin, end: end, curve: curve);
BoxDecoration lerp(double t) => BoxDecoration.lerp(begin, end, t);
}
class AnimatedEdgeDimsValue extends AnimatedValue<EdgeDims> {
AnimatedEdgeDimsValue(EdgeDims begin, { EdgeDims end, Curve curve: linear })
: super(begin, end: end, curve: curve);
EdgeDims lerp(double t) => EdgeDims.lerp(begin, end, t);
}
class AnimatedMatrix4Value extends AnimatedValue<Matrix4> {
AnimatedMatrix4Value(Matrix4 begin, { Matrix4 end, Curve curve: linear })
: super(begin, end: end, curve: curve);
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;
return new Matrix4.identity()..translate(lerpT);
}
}
class AnimatedContainer extends StatefulComponent {
AnimatedContainer({
Key key,
this.child,
this.constraints,
this.decoration,
this.foregroundDecoration,
this.margin,
this.padding,
this.transform,
this.width,
this.height,
this.curve: linear,
this.duration
}) : super(key: key) {
assert(margin == null || margin.isNonNegative);
assert(padding == null || padding.isNonNegative);
assert(curve != null);
assert(duration != null);
}
final Widget child;
final BoxConstraints constraints;
final BoxDecoration decoration;
final BoxDecoration foregroundDecoration;
final EdgeDims margin;
final EdgeDims padding;
final Matrix4 transform;
final double width;
final double height;
final Curve curve;
final Duration duration;
AnimatedContainerState createState() => new AnimatedContainerState();
}
class AnimatedContainerState extends State<AnimatedContainer> {
AnimatedBoxConstraintsValue _constraints;
AnimatedBoxDecorationValue _decoration;
AnimatedBoxDecorationValue _foregroundDecoration;
AnimatedEdgeDimsValue _margin;
AnimatedEdgeDimsValue _padding;
AnimatedMatrix4Value _transform;
AnimatedValue<double> _width;
AnimatedValue<double> _height;
AnimationPerformance _performance;
void initState() {
super.initState();
_performance = new AnimationPerformance(duration: config.duration)
..timing = new AnimationTiming(curve: config.curve)
..addListener(_updateAllVariables);
_configAllVariables();
}
void didUpdateConfig(AnimatedContainer oldConfig) {
_performance
..duration = config.duration
..timing.curve = config.curve;
if (_configAllVariables()) {
_performance.progress = 0.0;
_performance.play();
}
}
void dispose() {
_performance.stop();
super.dispose();
}
void _updateVariable(AnimatedVariable variable) {
if (variable != null)
_performance.updateVariable(variable);
}
void _updateAllVariables() {
setState(() {
_updateVariable(_constraints);
_updateVariable(_constraints);
_updateVariable(_decoration);
_updateVariable(_foregroundDecoration);
_updateVariable(_margin);
_updateVariable(_padding);
_updateVariable(_transform);
_updateVariable(_width);
_updateVariable(_height);
});
}
bool _configVariable(AnimatedValue variable, dynamic targetValue) {
dynamic currentValue = variable.value;
variable.end = targetValue;
variable.begin = currentValue;
return currentValue != targetValue;
}
bool _configAllVariables() {
bool needsAnimation = false;
if (config.constraints != null) {
_constraints ??= new AnimatedBoxConstraintsValue(config.constraints);
if (_configVariable(_constraints, config.constraints))
needsAnimation = true;
} else {
_constraints = null;
}
if (config.decoration != null) {
_decoration ??= new AnimatedBoxDecorationValue(config.decoration);
if (_configVariable(_decoration, config.decoration))
needsAnimation = true;
} else {
_decoration = null;
}
if (config.foregroundDecoration != null) {
_foregroundDecoration ??= new AnimatedBoxDecorationValue(config.foregroundDecoration);
if (_configVariable(_foregroundDecoration, config.foregroundDecoration))
needsAnimation = true;
} else {
_foregroundDecoration = null;
}
if (config.margin != null) {
_margin ??= new AnimatedEdgeDimsValue(config.margin);
if (_configVariable(_margin, config.margin))
needsAnimation = true;
} else {
_margin = null;
}
if (config.padding != null) {
_padding ??= new AnimatedEdgeDimsValue(config.padding);
if (_configVariable(_padding, config.padding))
needsAnimation = true;
} else {
_padding = null;
}
if (config.transform != null) {
_transform ??= new AnimatedMatrix4Value(config.transform);
if (_configVariable(_transform, config.transform))
needsAnimation = true;
} else {
_transform = null;
}
if (config.width != null) {
_width ??= new AnimatedValue<double>(config.width);
if (_configVariable(_width, config.width))
needsAnimation = true;
} else {
_width = null;
}
if (config.height != null) {
_height ??= new AnimatedValue<double>(config.height);
if (_configVariable(_height, config.height))
needsAnimation = true;
} else {
_height = null;
}
return needsAnimation;
}
Widget build(BuildContext context) {
return new Container(
child: config.child,
constraints: _constraints?.value,
decoration: _decoration?.value,
foregroundDecoration: _foregroundDecoration?.value,
margin: _margin?.value,
padding: _padding?.value,
transform: _transform?.value,
width: _width?.value,
height: _height?.value
);
}
}
...@@ -22,6 +22,7 @@ export 'package:sky/rendering.dart' show ...@@ -22,6 +22,7 @@ export 'package:sky/rendering.dart' show
FlexAlignItems, FlexAlignItems,
FlexDirection, FlexDirection,
FlexJustifyContent, FlexJustifyContent,
Matrix4,
Offset, Offset,
Paint, Paint,
Path, Path,
...@@ -392,11 +393,11 @@ class Container extends StatelessComponent { ...@@ -392,11 +393,11 @@ class Container extends StatelessComponent {
this.constraints, this.constraints,
this.decoration, this.decoration,
this.foregroundDecoration, this.foregroundDecoration,
this.width,
this.height,
this.margin, this.margin,
this.padding, this.padding,
this.transform this.transform,
this.width,
this.height
}) : super(key: key) { }) : super(key: key) {
assert(margin == null || margin.isNonNegative); assert(margin == null || margin.isNonNegative);
assert(padding == null || padding.isNonNegative); assert(padding == null || padding.isNonNegative);
......
...@@ -6,6 +6,7 @@ import 'dart:async'; ...@@ -6,6 +6,7 @@ import 'dart:async';
import 'package:sky/animation.dart'; import 'package:sky/animation.dart';
import 'package:sky/material.dart'; import 'package:sky/material.dart';
import 'package:sky/src/widgets/animated_container.dart';
import 'package:sky/src/widgets/framework.dart'; import 'package:sky/src/widgets/framework.dart';
import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/basic.dart';
import 'package:sky/src/widgets/gesture_detector.dart'; import 'package:sky/src/widgets/gesture_detector.dart';
...@@ -102,9 +103,9 @@ class DrawerState extends State<Drawer> { ...@@ -102,9 +103,9 @@ class DrawerState extends State<Drawer> {
Widget content = new SlideTransition( Widget content = new SlideTransition(
performance: _performance.view, performance: _performance.view,
position: new AnimatedValue<Point>(_kClosedPosition, end: _kOpenPosition), position: new AnimatedValue<Point>(_kClosedPosition, end: _kOpenPosition),
// TODO(abarth): Use AnimatedContainer child: new AnimatedContainer(
child: new Container( curve: ease,
// behavior: implicitlyAnimate(const Duration(milliseconds: 200)), duration: const Duration(milliseconds: 200),
decoration: new BoxDecoration( decoration: new BoxDecoration(
backgroundColor: Theme.of(context).canvasColor, backgroundColor: Theme.of(context).canvasColor,
boxShadow: shadows[config.level]), boxShadow: shadows[config.level]),
......
...@@ -2,8 +2,10 @@ ...@@ -2,8 +2,10 @@
// 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 'package:sky/animation.dart';
import 'package:sky/painting.dart'; import 'package:sky/painting.dart';
import 'package:sky/material.dart'; import 'package:sky/material.dart';
import 'package:sky/src/widgets/animated_container.dart';
import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/basic.dart';
import 'package:sky/src/widgets/framework.dart'; import 'package:sky/src/widgets/framework.dart';
import 'package:sky/src/widgets/theme.dart'; import 'package:sky/src/widgets/theme.dart';
...@@ -61,10 +63,11 @@ class Material extends StatelessComponent { ...@@ -61,10 +63,11 @@ class Material extends StatelessComponent {
); );
} }
} }
// TODO(abarth): This should use AnimatedContainer.
return new DefaultTextStyle( return new DefaultTextStyle(
style: Theme.of(context).text.body1, style: Theme.of(context).text.body1,
child: new Container( child: new AnimatedContainer(
curve: ease,
duration: const Duration(milliseconds: 200),
decoration: new BoxDecoration( decoration: new BoxDecoration(
backgroundColor: getBackgroundColor(context), backgroundColor: getBackgroundColor(context),
borderRadius: edges[type], borderRadius: edges[type],
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
library widgets; library widgets;
export 'src/widgets/animated_component.dart'; export 'src/widgets/animated_component.dart';
export 'src/widgets/animated_container.dart';
export 'src/widgets/app.dart'; export 'src/widgets/app.dart';
export 'src/widgets/basic.dart'; export 'src/widgets/basic.dart';
export 'src/widgets/binding.dart'; export 'src/widgets/binding.dart';
......
import 'package:sky/rendering.dart';
import 'package:sky/widgets.dart';
import 'package:test/test.dart';
import 'widget_tester.dart';
void main() {
test('AnimatedContainer control test', () {
testWidgets((WidgetTester tester) {
GlobalKey key = new GlobalKey();
BoxDecoration decorationA = new BoxDecoration(
backgroundColor: new Color(0xFF00FF00)
);
BoxDecoration decorationB = new BoxDecoration(
backgroundColor: new Color(0xFF0000FF)
);
tester.pumpWidget(
new AnimatedContainer(
key: key,
duration: const Duration(milliseconds: 200),
decoration: decorationA
)
);
RenderDecoratedBox box = key.currentState.context.findRenderObject();
expect(box.decoration.backgroundColor, equals(decorationA.backgroundColor));
tester.pumpWidget(
new AnimatedContainer(
key: key,
duration: const Duration(milliseconds: 200),
decoration: decorationB
)
);
expect(key.currentState.context.findRenderObject(), equals(box));
expect(box.decoration.backgroundColor, equals(decorationA.backgroundColor));
tester.pump(const Duration(seconds: 1));
expect(box.decoration.backgroundColor, equals(decorationB.backgroundColor));
});
});
}
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