mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Refactor Material animation to use AnimatedContainer
The idea is that AnimatedContainer is a drop-in replacement for Container that provides implicit animations when its properties change. R=mpcomplete@google.com
This commit is contained in:
parent
d6232c3c71
commit
f3d913c514
@ -50,6 +50,7 @@ dart_pkg("sky") {
|
||||
"lib/theme/typography.dart",
|
||||
"lib/theme/view_configuration.dart",
|
||||
"lib/widgets/animated_component.dart",
|
||||
"lib/widgets/animated_container.dart",
|
||||
"lib/widgets/animation_builder.dart",
|
||||
"lib/widgets/basic.dart",
|
||||
"lib/widgets/block_viewport.dart",
|
||||
|
||||
@ -4,9 +4,27 @@
|
||||
|
||||
import 'dart:sky';
|
||||
|
||||
num lerpNum(num a, num b, double t) => a + (b - a) * t;
|
||||
num lerpNum(num a, num b, double t) {
|
||||
if (a == null && b == null)
|
||||
return null;
|
||||
if (a == null)
|
||||
a = 0.0;
|
||||
if (b == null)
|
||||
b = 0.0;
|
||||
return a + (b - a) * t;
|
||||
}
|
||||
|
||||
Color _scaleAlpha(Color a, double factor) {
|
||||
return a.withAlpha((a.alpha * factor).round());
|
||||
}
|
||||
|
||||
Color lerpColor(Color a, Color b, double t) {
|
||||
if (a == null && b == null)
|
||||
return null;
|
||||
if (a == null)
|
||||
return _scaleAlpha(b, t);
|
||||
if (b == null)
|
||||
return _scaleAlpha(b, 1.0 - t);
|
||||
return new Color.fromARGB(
|
||||
lerpNum(a.alpha, b.alpha, t).toInt(),
|
||||
lerpNum(a.red, b.red, t).toInt(),
|
||||
@ -15,5 +33,11 @@ Color lerpColor(Color a, Color b, double t) {
|
||||
}
|
||||
|
||||
Offset lerpOffset(Offset a, Offset 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 Offset(lerpNum(a.dx, b.dx, t), lerpNum(a.dy, b.dy, t));
|
||||
}
|
||||
|
||||
@ -70,16 +70,48 @@ class BoxShadow {
|
||||
final Offset offset;
|
||||
final double blur;
|
||||
|
||||
BoxShadow scale(double factor) {
|
||||
return new BoxShadow(
|
||||
color: color,
|
||||
offset: offset * factor,
|
||||
blur: blur * factor
|
||||
);
|
||||
}
|
||||
|
||||
String toString() => 'BoxShadow($color, $offset, $blur)';
|
||||
}
|
||||
|
||||
BoxShadow lerpBoxShadow(BoxShadow a, BoxShadow b, double t) {
|
||||
if (a == null && b == null)
|
||||
return null;
|
||||
if (a == null)
|
||||
return b.scale(t);
|
||||
if (b == null)
|
||||
return a.scale(1.0 - t);
|
||||
return new BoxShadow(
|
||||
color: lerpColor(a.color, b.color, t),
|
||||
offset: lerpOffset(a.offset, b.offset, t),
|
||||
blur: lerpNum(a.blur, b.blur, t));
|
||||
}
|
||||
|
||||
List<BoxShadow> lerpListBoxShadow(List<BoxShadow> a, List<BoxShadow> b, double t) {
|
||||
if (a == null && b == null)
|
||||
return null;
|
||||
if (a == null)
|
||||
a = new List<BoxShadow>();
|
||||
if (b == null)
|
||||
b = new List<BoxShadow>();
|
||||
List<BoxShadow> result = new List<BoxShadow>();
|
||||
int commonLength = math.min(a.length, b.length);
|
||||
for (int i = 0; i < commonLength; ++i)
|
||||
result.add(lerpBoxShadow(a[i], b[i], t));
|
||||
for (int i = commonLength; i < a.length; ++i)
|
||||
result.add(a[i].scale(1.0 - t));
|
||||
for (int i = commonLength; i < b.length; ++i)
|
||||
result.add(b[i].scale(t));
|
||||
return result;
|
||||
}
|
||||
|
||||
abstract class Gradient {
|
||||
sky.Shader createShader();
|
||||
}
|
||||
@ -198,6 +230,19 @@ class BoxDecoration {
|
||||
final Gradient gradient;
|
||||
final Shape shape;
|
||||
|
||||
BoxDecoration scale(double factor) {
|
||||
// TODO(abarth): Scale ALL the things.
|
||||
return new BoxDecoration(
|
||||
backgroundColor: lerpColor(null, backgroundColor, factor),
|
||||
backgroundImage: backgroundImage,
|
||||
border: border,
|
||||
borderRadius: lerpNum(null, borderRadius, factor),
|
||||
boxShadow: lerpListBoxShadow(null, boxShadow, factor),
|
||||
gradient: gradient,
|
||||
shape: shape
|
||||
);
|
||||
}
|
||||
|
||||
String toString([String prefix = '']) {
|
||||
List<String> result = [];
|
||||
if (backgroundColor != null)
|
||||
@ -220,6 +265,25 @@ class BoxDecoration {
|
||||
}
|
||||
}
|
||||
|
||||
BoxDecoration lerpBoxDecoration(BoxDecoration a, BoxDecoration b, double t) {
|
||||
if (a == null && b == null)
|
||||
return null;
|
||||
if (a == null)
|
||||
return b.scale(t);
|
||||
if (b == null)
|
||||
return a.scale(1.0 - t);
|
||||
// TODO(abarth): lerp ALL the fields.
|
||||
return new BoxDecoration(
|
||||
backgroundColor: lerpColor(a.backgroundColor, b.backgroundColor, t),
|
||||
backgroundImage: b.backgroundImage,
|
||||
border: b.border,
|
||||
borderRadius: lerpNum(a.borderRadius, b.borderRadius, t),
|
||||
boxShadow: lerpListBoxShadow(a.boxShadow, b.boxShadow, t),
|
||||
gradient: b.gradient,
|
||||
shape: b.shape
|
||||
);
|
||||
}
|
||||
|
||||
class BoxPainter {
|
||||
BoxPainter(BoxDecoration decoration) : _decoration = decoration {
|
||||
assert(decoration != null);
|
||||
|
||||
206
packages/flutter/lib/widgets/animated_container.dart
Normal file
206
packages/flutter/lib/widgets/animated_container.dart
Normal file
@ -0,0 +1,206 @@
|
||||
// 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:vector_math/vector_math.dart';
|
||||
|
||||
import 'package:sky/animation/animation_performance.dart';
|
||||
import 'package:sky/animation/curves.dart';
|
||||
import 'package:sky/base/lerp.dart';
|
||||
import 'package:sky/painting/box_painter.dart';
|
||||
import 'package:sky/widgets/basic.dart';
|
||||
import 'package:sky/widgets/animated_component.dart';
|
||||
|
||||
class AnimatedBoxConstraintsValue extends AnimatedType<BoxConstraints> {
|
||||
AnimatedBoxConstraintsValue(BoxConstraints begin, { BoxConstraints end, Curve curve: linear })
|
||||
: super(begin, end: end, curve: curve);
|
||||
|
||||
void setFraction(double t) {
|
||||
// TODO(abarth): We should lerp the BoxConstraints.
|
||||
value = end;
|
||||
}
|
||||
}
|
||||
|
||||
class AnimatedBoxDecorationValue extends AnimatedType<BoxDecoration> {
|
||||
AnimatedBoxDecorationValue(BoxDecoration begin, { BoxDecoration end, Curve curve: linear })
|
||||
: super(begin, end: end, curve: curve);
|
||||
|
||||
void setFraction(double t) {
|
||||
if (t == 1.0) {
|
||||
value = end;
|
||||
return;
|
||||
}
|
||||
value = lerpBoxDecoration(begin, end, t);
|
||||
}
|
||||
}
|
||||
|
||||
class AnimatedEdgeDimsValue extends AnimatedType<EdgeDims> {
|
||||
AnimatedEdgeDimsValue(EdgeDims begin, { EdgeDims end, Curve curve: linear })
|
||||
: super(begin, end: end, curve: curve);
|
||||
|
||||
void setFraction(double t) {
|
||||
if (t == 1.0) {
|
||||
value = end;
|
||||
return;
|
||||
}
|
||||
value = new EdgeDims(
|
||||
lerpNum(begin.top, end.top, t),
|
||||
lerpNum(begin.right, end.right, t),
|
||||
lerpNum(begin.bottom, end.bottom, t),
|
||||
lerpNum(begin.bottom, end.left, t)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ImplicitlyAnimatedValue<T> {
|
||||
final AnimationPerformance performance = new AnimationPerformance();
|
||||
final AnimatedType<T> _variable;
|
||||
|
||||
ImplicitlyAnimatedValue(this._variable, Duration duration) {
|
||||
performance
|
||||
..variable = _variable
|
||||
..duration = duration;
|
||||
}
|
||||
|
||||
T get value => _variable.value;
|
||||
void set value(T newValue) {
|
||||
_variable.begin = _variable.value;
|
||||
_variable.end = newValue;
|
||||
if (_variable.value != _variable.end) {
|
||||
performance
|
||||
..progress = 0.0
|
||||
..play();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AnimatedContainer extends AnimatedComponent {
|
||||
AnimatedContainer({
|
||||
String key,
|
||||
this.child,
|
||||
this.duration,
|
||||
this.constraints,
|
||||
this.decoration,
|
||||
this.width,
|
||||
this.height,
|
||||
this.margin,
|
||||
this.padding,
|
||||
this.transform
|
||||
}) : super(key: key);
|
||||
|
||||
Widget child;
|
||||
Duration duration; // TODO(abarth): Support separate durations for each value.
|
||||
BoxConstraints constraints;
|
||||
BoxDecoration decoration;
|
||||
EdgeDims margin;
|
||||
EdgeDims padding;
|
||||
Matrix4 transform;
|
||||
double width;
|
||||
double height;
|
||||
|
||||
ImplicitlyAnimatedValue<BoxConstraints> _constraints;
|
||||
ImplicitlyAnimatedValue<BoxDecoration> _decoration;
|
||||
ImplicitlyAnimatedValue<EdgeDims> _margin;
|
||||
ImplicitlyAnimatedValue<EdgeDims> _padding;
|
||||
ImplicitlyAnimatedValue<Matrix4> _transform;
|
||||
ImplicitlyAnimatedValue<double> _width;
|
||||
ImplicitlyAnimatedValue<double> _height;
|
||||
|
||||
void initState() {
|
||||
_updateFields();
|
||||
}
|
||||
|
||||
void syncFields(AnimatedContainer source) {
|
||||
child = source.child;
|
||||
constraints = source.constraints;
|
||||
decoration = source.decoration;
|
||||
margin = source.margin;
|
||||
padding = source.padding;
|
||||
width = source.width;
|
||||
height = source.height;
|
||||
_updateFields();
|
||||
}
|
||||
|
||||
void _updateFields() {
|
||||
_updateConstraints();
|
||||
_updateDecoration();
|
||||
_updateMargin();
|
||||
_updatePadding();
|
||||
_updateTransform();
|
||||
_updateWidth();
|
||||
_updateHeight();
|
||||
}
|
||||
|
||||
void _updateField(dynamic value, ImplicitlyAnimatedValue animatedValue, Function initField) {
|
||||
if (animatedValue != null)
|
||||
animatedValue.value = value;
|
||||
else if (value != null)
|
||||
initField();
|
||||
}
|
||||
|
||||
void _updateConstraints() {
|
||||
_updateField(constraints, _constraints, () {
|
||||
_constraints = new ImplicitlyAnimatedValue<BoxConstraints>(new AnimatedBoxConstraintsValue(constraints), duration);
|
||||
watch(_constraints.performance);
|
||||
});
|
||||
}
|
||||
|
||||
void _updateDecoration() {
|
||||
_updateField(decoration, _decoration, () {
|
||||
_decoration = new ImplicitlyAnimatedValue<BoxDecoration>(new AnimatedBoxDecorationValue(decoration), duration);
|
||||
watch(_decoration.performance);
|
||||
});
|
||||
}
|
||||
|
||||
void _updateMargin() {
|
||||
_updateField(margin, _margin, () {
|
||||
_margin = new ImplicitlyAnimatedValue<EdgeDims>(new AnimatedEdgeDimsValue(margin), duration);
|
||||
watch(_margin.performance);
|
||||
});
|
||||
}
|
||||
|
||||
void _updatePadding() {
|
||||
_updateField(padding, _padding, () {
|
||||
_padding = new ImplicitlyAnimatedValue<EdgeDims>(new AnimatedEdgeDimsValue(padding), duration);
|
||||
watch(_padding.performance);
|
||||
});
|
||||
}
|
||||
|
||||
void _updateTransform() {
|
||||
_updateField(transform, _transform, () {
|
||||
_transform = new ImplicitlyAnimatedValue<Matrix4>(new AnimatedType<Matrix4>(transform), duration);
|
||||
watch(_transform.performance);
|
||||
});
|
||||
}
|
||||
|
||||
void _updateWidth() {
|
||||
_updateField(width, _width, () {
|
||||
_width = new ImplicitlyAnimatedValue<double>(new AnimatedType<double>(width), duration);
|
||||
watch(_width.performance);
|
||||
});
|
||||
}
|
||||
|
||||
void _updateHeight() {
|
||||
_updateField(height, _height, () {
|
||||
_height = new ImplicitlyAnimatedValue<double>( new AnimatedType<double>(height), duration);
|
||||
watch(_height.performance);
|
||||
});
|
||||
}
|
||||
|
||||
dynamic _getValue(dynamic value, ImplicitlyAnimatedValue animatedValue) {
|
||||
return animatedValue == null ? value : animatedValue.value;
|
||||
}
|
||||
|
||||
Widget build() {
|
||||
return new Container(
|
||||
child: child,
|
||||
constraints: _getValue(constraints, _constraints),
|
||||
decoration: _getValue(decoration, _decoration),
|
||||
margin: _getValue(margin, _margin),
|
||||
padding: _getValue(padding, _padding),
|
||||
transform: _getValue(transform, _transform),
|
||||
width: _getValue(width, _width),
|
||||
height: _getValue(height, _height)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -2,10 +2,9 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:sky/animation/animation_performance.dart';
|
||||
import 'package:sky/painting/box_painter.dart';
|
||||
import 'package:sky/widgets/animated_component.dart';
|
||||
import 'package:sky/widgets/animation_builder.dart';
|
||||
import 'package:sky/theme/shadows.dart';
|
||||
import 'package:sky/widgets/animated_container.dart';
|
||||
import 'package:sky/widgets/basic.dart';
|
||||
import 'package:sky/widgets/default_text_style.dart';
|
||||
import 'package:sky/widgets/theme.dart';
|
||||
@ -19,11 +18,7 @@ const Map<MaterialType, double> edges = const {
|
||||
MaterialType.button: 2.0,
|
||||
};
|
||||
|
||||
const Duration _kAnimateShadowDuration = const Duration(milliseconds: 100);
|
||||
const Duration _kAnimateColorDuration = const Duration(milliseconds: 100);
|
||||
|
||||
class Material extends AnimatedComponent {
|
||||
|
||||
class Material extends Component {
|
||||
Material({
|
||||
String key,
|
||||
this.child,
|
||||
@ -34,64 +29,37 @@ class Material extends AnimatedComponent {
|
||||
assert(level != null);
|
||||
}
|
||||
|
||||
Widget child;
|
||||
MaterialType type;
|
||||
int level;
|
||||
Color color;
|
||||
final Widget child;
|
||||
final MaterialType type;
|
||||
final int level;
|
||||
final Color color;
|
||||
|
||||
final AnimationBuilder _builder = new AnimationBuilder();
|
||||
|
||||
void initState() {
|
||||
_builder
|
||||
..shadow = new AnimatedType<double>(level.toDouble())
|
||||
..backgroundColor = _getBackgroundColor(type, color)
|
||||
..borderRadius = edges[type]
|
||||
..shape = type == MaterialType.circle ? Shape.circle : Shape.rectangle;
|
||||
watch(_builder.createPerformance(
|
||||
[_builder.shadow],
|
||||
duration: _kAnimateShadowDuration
|
||||
));
|
||||
watch(_builder.createPerformance(
|
||||
[_builder.backgroundColor],
|
||||
duration: _kAnimateColorDuration
|
||||
));
|
||||
super.initState();
|
||||
}
|
||||
|
||||
void syncFields(Material source) {
|
||||
child = source.child;
|
||||
type = source.type;
|
||||
level = source.level;
|
||||
color = source.color;
|
||||
_builder.updateFields(
|
||||
shadow: new AnimatedType<double>(level.toDouble()),
|
||||
backgroundColor: _getBackgroundColor(type, color),
|
||||
borderRadius: edges[type],
|
||||
shape: type == MaterialType.circle ? Shape.circle : Shape.rectangle
|
||||
);
|
||||
super.syncFields(source);
|
||||
}
|
||||
|
||||
AnimatedColor _getBackgroundColor(MaterialType type, Color color) {
|
||||
if (color == null) {
|
||||
switch (type) {
|
||||
case MaterialType.canvas:
|
||||
color = Theme.of(this).canvasColor;
|
||||
break;
|
||||
case MaterialType.card:
|
||||
color = Theme.of(this).cardColor;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
Color get _backgroundColor {
|
||||
if (color != null)
|
||||
return color;
|
||||
switch (type) {
|
||||
case MaterialType.canvas:
|
||||
return Theme.of(this).canvasColor;
|
||||
case MaterialType.card:
|
||||
return Theme.of(this).cardColor;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
return color == null ? null : new AnimatedColor(color);
|
||||
}
|
||||
|
||||
Widget build() {
|
||||
return _builder.build(
|
||||
new DefaultTextStyle(style: Theme.of(this).text.body1, child: child)
|
||||
return new AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 1000),
|
||||
decoration: new BoxDecoration(
|
||||
backgroundColor: _backgroundColor,
|
||||
borderRadius: edges[type],
|
||||
boxShadow: level == 0 ? null : shadows[level],
|
||||
shape: type == MaterialType.circle ? Shape.circle : Shape.rectangle
|
||||
),
|
||||
child: new DefaultTextStyle(
|
||||
style: Theme.of(this).text.body1,
|
||||
child: child
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user