Commit c88ca5db authored by Adam Barth's avatar Adam Barth

Add AnimatedContainer

This widget is used in Material and Drawer. We don't currently support
animating towards null, but we can add that in a future patch.
parent 6fcdb64a
......@@ -178,6 +178,60 @@ class BoxConstraints extends Constraints {
(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) {
if (identical(this, other))
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
FlexAlignItems,
FlexDirection,
FlexJustifyContent,
Matrix4,
Offset,
Paint,
Path,
......@@ -392,11 +393,11 @@ class Container extends StatelessComponent {
this.constraints,
this.decoration,
this.foregroundDecoration,
this.width,
this.height,
this.margin,
this.padding,
this.transform
this.transform,
this.width,
this.height
}) : super(key: key) {
assert(margin == null || margin.isNonNegative);
assert(padding == null || padding.isNonNegative);
......@@ -940,4 +941,4 @@ class KeyedSubtree extends StatelessComponent {
final Widget child;
Widget build(BuildContext context) => child;
}
\ No newline at end of file
}
......@@ -6,6 +6,7 @@ import 'dart:async';
import 'package:sky/animation.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/basic.dart';
import 'package:sky/src/widgets/gesture_detector.dart';
......@@ -102,9 +103,9 @@ class DrawerState extends State<Drawer> {
Widget content = new SlideTransition(
performance: _performance.view,
position: new AnimatedValue<Point>(_kClosedPosition, end: _kOpenPosition),
// TODO(abarth): Use AnimatedContainer
child: new Container(
// behavior: implicitlyAnimate(const Duration(milliseconds: 200)),
child: new AnimatedContainer(
curve: ease,
duration: const Duration(milliseconds: 200),
decoration: new BoxDecoration(
backgroundColor: Theme.of(context).canvasColor,
boxShadow: shadows[config.level]),
......
......@@ -2,8 +2,10 @@
// 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/painting.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/framework.dart';
import 'package:sky/src/widgets/theme.dart';
......@@ -61,10 +63,11 @@ class Material extends StatelessComponent {
);
}
}
// TODO(abarth): This should use AnimatedContainer.
return new DefaultTextStyle(
style: Theme.of(context).text.body1,
child: new Container(
child: new AnimatedContainer(
curve: ease,
duration: const Duration(milliseconds: 200),
decoration: new BoxDecoration(
backgroundColor: getBackgroundColor(context),
borderRadius: edges[type],
......
......@@ -6,6 +6,7 @@
library widgets;
export 'src/widgets/animated_component.dart';
export 'src/widgets/animated_container.dart';
export 'src/widgets/app.dart';
export 'src/widgets/basic.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