Merge pull request #1463 from abarth/animated_container
Add AnimatedContainer
This commit is contained in:
@@ -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;
|
||||
|
||||
226
packages/flutter/lib/src/widgets/animated_container.dart
Normal file
226
packages/flutter/lib/src/widgets/animated_container.dart
Normal file
@@ -0,0 +1,226 @@
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
48
packages/unit/test/widget/animated_container_test.dart
Normal file
48
packages/unit/test/widget/animated_container_test.dart
Normal file
@@ -0,0 +1,48 @@
|
||||
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));
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user