Commit baf3b45e authored by Adam Barth's avatar Adam Barth Committed by GitHub

Add RTL support to AnimatedContainer (#11910)

Also, fix the interpolation between visual and directional fractional
offsets. The interpolation now works visually in whatever direction the
result eventually gets resolved into.

Fixes #11847
Fixes #11357
parent 74b0bf64
...@@ -752,10 +752,10 @@ class EdgeInsetsDirectional extends EdgeInsetsGeometry { ...@@ -752,10 +752,10 @@ class EdgeInsetsDirectional extends EdgeInsetsGeometry {
EdgeInsets resolve(TextDirection direction) { EdgeInsets resolve(TextDirection direction) {
assert(direction != null); assert(direction != null);
switch (direction) { switch (direction) {
case TextDirection.ltr:
return new EdgeInsets.fromLTRB(start, top, end, bottom);
case TextDirection.rtl: case TextDirection.rtl:
return new EdgeInsets.fromLTRB(end, top, start, bottom); return new EdgeInsets.fromLTRB(end, top, start, bottom);
case TextDirection.ltr:
return new EdgeInsets.fromLTRB(start, top, end, bottom);
} }
return null; return null;
} }
...@@ -856,10 +856,10 @@ class _MixedEdgeInsets extends EdgeInsetsGeometry { ...@@ -856,10 +856,10 @@ class _MixedEdgeInsets extends EdgeInsetsGeometry {
EdgeInsets resolve(TextDirection direction) { EdgeInsets resolve(TextDirection direction) {
assert(direction != null); assert(direction != null);
switch (direction) { switch (direction) {
case TextDirection.rtl:
return new EdgeInsets.fromLTRB(_end + _left, _top, _start + _right, _bottom);
case TextDirection.ltr: case TextDirection.ltr:
return new EdgeInsets.fromLTRB(_start + _left, _top, _end + _right, _bottom); return new EdgeInsets.fromLTRB(_start + _left, _top, _end + _right, _bottom);
case TextDirection.rtl:
return new EdgeInsets.fromLTRB(_end + _left, _top, _start + _left, _bottom);
} }
return null; return null;
} }
......
...@@ -21,8 +21,12 @@ abstract class FractionalOffsetGeometry { ...@@ -21,8 +21,12 @@ abstract class FractionalOffsetGeometry {
/// const constructors so that they can be used in const expressions. /// const constructors so that they can be used in const expressions.
const FractionalOffsetGeometry(); const FractionalOffsetGeometry();
double get _dx; /// The [FractionalOffset.dx] to which this object will [resolve] in [TextDirection.ltr].
double get _start; double get _dxForRTL;
/// The [FractionalOffset.dx] to which this object will [resolve] in [TextDirection.ltr].
double get _dxForLTR;
double get _dy; double get _dy;
/// Returns the difference between two [FractionalOffsetGeometry] objects. /// Returns the difference between two [FractionalOffsetGeometry] objects.
...@@ -42,9 +46,9 @@ abstract class FractionalOffsetGeometry { ...@@ -42,9 +46,9 @@ abstract class FractionalOffsetGeometry {
/// negating the argument (using the prefix unary `-` operator or multiplying /// negating the argument (using the prefix unary `-` operator or multiplying
/// the argument by -1.0 using the `*` operator). /// the argument by -1.0 using the `*` operator).
FractionalOffsetGeometry subtract(FractionalOffsetGeometry other) { FractionalOffsetGeometry subtract(FractionalOffsetGeometry other) {
return new _MixedFractionalOffset( return new _SchrodingersFractionalOffset(
_dx - other._dx, _dxForRTL - other._dxForRTL,
_start - other._start, _dxForLTR - other._dxForLTR,
_dy - other._dy, _dy - other._dy,
); );
} }
...@@ -61,9 +65,9 @@ abstract class FractionalOffsetGeometry { ...@@ -61,9 +65,9 @@ abstract class FractionalOffsetGeometry {
/// representing a combination of both is returned. That object can be turned /// representing a combination of both is returned. That object can be turned
/// into a concrete [FractionalOffset] using [resolve]. /// into a concrete [FractionalOffset] using [resolve].
FractionalOffsetGeometry add(FractionalOffsetGeometry other) { FractionalOffsetGeometry add(FractionalOffsetGeometry other) {
return new _MixedFractionalOffset( return new _SchrodingersFractionalOffset(
_dx + other._dx, _dxForRTL + other._dxForRTL,
_start + other._start, _dxForLTR + other._dxForLTR,
_dy + other._dy, _dy + other._dy,
); );
} }
...@@ -113,22 +117,22 @@ abstract class FractionalOffsetGeometry { ...@@ -113,22 +117,22 @@ abstract class FractionalOffsetGeometry {
if ((a == null || a is FractionalOffsetDirectional) && (b == null || b is FractionalOffsetDirectional)) if ((a == null || a is FractionalOffsetDirectional) && (b == null || b is FractionalOffsetDirectional))
return FractionalOffsetDirectional.lerp(a, b, t); return FractionalOffsetDirectional.lerp(a, b, t);
if (a == null) { if (a == null) {
return new _MixedFractionalOffset( return new _SchrodingersFractionalOffset(
ui.lerpDouble(0.5, b._dx, t), ui.lerpDouble(0.5, b._dxForRTL, t),
ui.lerpDouble(0.0, b._start, t), ui.lerpDouble(0.5, b._dxForLTR, t),
ui.lerpDouble(0.5, b._dy, t), ui.lerpDouble(0.5, b._dy, t),
); );
} }
if (b == null) { if (b == null) {
return new _MixedFractionalOffset( return new _SchrodingersFractionalOffset(
ui.lerpDouble(a._dx, 0.5, t), ui.lerpDouble(a._dxForRTL, 0.5, t),
ui.lerpDouble(a._start, 0.0, t), ui.lerpDouble(a._dxForLTR, 0.5, t),
ui.lerpDouble(a._dy, 0.5, t), ui.lerpDouble(a._dy, 0.5, t),
); );
} }
return new _MixedFractionalOffset( return new _SchrodingersFractionalOffset(
ui.lerpDouble(a._dx, b._dx, t), ui.lerpDouble(a._dxForRTL, b._dxForRTL, t),
ui.lerpDouble(a._start, b._start, t), ui.lerpDouble(a._dxForLTR, b._dxForLTR, t),
ui.lerpDouble(a._dy, b._dy, t), ui.lerpDouble(a._dy, b._dy, t),
); );
} }
...@@ -142,62 +146,15 @@ abstract class FractionalOffsetGeometry { ...@@ -142,62 +146,15 @@ abstract class FractionalOffsetGeometry {
/// * [FractionalOffset], for which this is a no-op (returns itself). /// * [FractionalOffset], for which this is a no-op (returns itself).
/// * [FractionalOffsetDirectional], which flips the horizontal direction /// * [FractionalOffsetDirectional], which flips the horizontal direction
/// based on the `direction` argument. /// based on the `direction` argument.
FractionalOffset resolve(TextDirection direction); FractionalOffset resolve(TextDirection direction) {
assert(direction != null);
@override switch (direction) {
String toString() { case TextDirection.rtl:
double x = _dx; return new FractionalOffset(_dxForRTL, _dy);
double start = _start; case TextDirection.ltr:
if (this is FractionalOffset) { return new FractionalOffset(_dxForLTR, _dy);
assert(start == 0.0);
start = null;
} else if (start == 0.5) {
x += 0.5;
start = null;
}
if (start == null) {
if (x == 0.0 && _dy == 0.0)
return 'FractionalOffset.topLeft';
if (x == 0.5 && _dy == 0.0)
return 'FractionalOffset.topCenter';
if (x == 1.0 && _dy == 0.0)
return 'FractionalOffset.topRight';
if (x == 0.0 && _dy == 0.5)
return 'FractionalOffset.centerLeft';
if (x == 0.5 && _dy == 0.5)
return 'FractionalOffset.center';
if (x == 1.0 && _dy == 0.5)
return 'FractionalOffset.centerRight';
if (x == 0.0 && _dy == 1.0)
return 'FractionalOffset.bottomLeft';
if (x == 0.5 && _dy == 1.0)
return 'FractionalOffset.bottomCenter';
if (x == 1.0 && _dy == 1.0)
return 'FractionalOffset.bottomRight';
return 'FractionalOffset(${x.toStringAsFixed(1)}, '
'${_dy.toStringAsFixed(1)})';
} else if (x == 0.0) {
assert(start != 0.5);
if (start == 0.0 && _dy == 0.0)
return 'FractionalOffsetDirectional.topStart';
if (start == 1.0 && _dy == 0.0)
return 'FractionalOffsetDirectional.topEnd';
if (start == 0.0 && _dy == 0.5)
return 'FractionalOffsetDirectional.centerStart';
if (start == 1.0 && _dy == 0.5)
return 'FractionalOffsetDirectional.centerEnd';
if (start == 0.0 && _dy == 1.0)
return 'FractionalOffsetDirectional.bottomStart';
if (start == 1.0 && _dy == 1.0)
return 'FractionalOffsetDirectional.bottomEnd';
return 'FractionalOffsetDirectional(${start.toStringAsFixed(1)}, '
'${_dy.toStringAsFixed(1)})';
} }
return 'FractionalOffset(${_dx.toStringAsFixed(1)}, ' return null;
'${_dy.toStringAsFixed(1)})'
' + '
'FractionalOffsetDirectional(${_start.toStringAsFixed(1)}, '
'0.0)';
} }
@override @override
...@@ -205,13 +162,13 @@ abstract class FractionalOffsetGeometry { ...@@ -205,13 +162,13 @@ abstract class FractionalOffsetGeometry {
if (other is! FractionalOffsetGeometry) if (other is! FractionalOffsetGeometry)
return false; return false;
final FractionalOffsetGeometry typedOther = other; final FractionalOffsetGeometry typedOther = other;
return _dx == typedOther._dx && return _dxForRTL == typedOther._dxForRTL &&
_start == typedOther._start && _dxForLTR == typedOther._dxForLTR &&
_dy == typedOther._dy; _dy == typedOther._dy;
} }
@override @override
int get hashCode => hashValues(_dx, _start, _dy); int get hashCode => hashValues(_dxForRTL, _dxForLTR, _dy);
} }
/// An offset that's expressed as a fraction of a [Size]. /// An offset that's expressed as a fraction of a [Size].
...@@ -279,7 +236,10 @@ class FractionalOffset extends FractionalOffsetGeometry { ...@@ -279,7 +236,10 @@ class FractionalOffset extends FractionalOffsetGeometry {
final double dx; final double dx;
@override @override
double get _dx => dx; double get _dxForRTL => dx;
@override
double get _dxForLTR => dx;
/// The distance fraction in the vertical direction. /// The distance fraction in the vertical direction.
/// ///
...@@ -292,9 +252,6 @@ class FractionalOffset extends FractionalOffsetGeometry { ...@@ -292,9 +252,6 @@ class FractionalOffset extends FractionalOffsetGeometry {
@override @override
double get _dy => dy; double get _dy => dy;
@override
double get _start => 0.0;
/// The top left corner. /// The top left corner.
static const FractionalOffset topLeft = const FractionalOffset(0.0, 0.0); static const FractionalOffset topLeft = const FractionalOffset(0.0, 0.0);
...@@ -420,6 +377,32 @@ class FractionalOffset extends FractionalOffsetGeometry { ...@@ -420,6 +377,32 @@ class FractionalOffset extends FractionalOffsetGeometry {
@override @override
FractionalOffset resolve(TextDirection direction) => this; FractionalOffset resolve(TextDirection direction) => this;
static String _stringify(double dx, double dy) {
if (dx == 0.0 && dy == 0.0)
return 'FractionalOffset.topLeft';
if (dx == 0.5 && dy == 0.0)
return 'FractionalOffset.topCenter';
if (dx == 1.0 && dy == 0.0)
return 'FractionalOffset.topRight';
if (dx == 0.0 && dy == 0.5)
return 'FractionalOffset.centerLeft';
if (dx == 0.5 && dy == 0.5)
return 'FractionalOffset.center';
if (dx == 1.0 && dy == 0.5)
return 'FractionalOffset.centerRight';
if (dx == 0.0 && dy == 1.0)
return 'FractionalOffset.bottomLeft';
if (dx == 0.5 && dy == 1.0)
return 'FractionalOffset.bottomCenter';
if (dx == 1.0 && dy == 1.0)
return 'FractionalOffset.bottomRight';
return 'FractionalOffset(${dx.toStringAsFixed(1)}, '
'${dy.toStringAsFixed(1)})';
}
@override
String toString() => _stringify(dx, dy);
} }
/// An offset that's expressed as a fraction of a [Size], but whose horizontal /// An offset that's expressed as a fraction of a [Size], but whose horizontal
...@@ -455,7 +438,10 @@ class FractionalOffsetDirectional extends FractionalOffsetGeometry { ...@@ -455,7 +438,10 @@ class FractionalOffsetDirectional extends FractionalOffsetGeometry {
final double start; final double start;
@override @override
double get _start => start; double get _dxForRTL => 1.0 - start;
@override
double get _dxForLTR => start;
/// The distance fraction in the vertical direction. /// The distance fraction in the vertical direction.
/// ///
...@@ -471,9 +457,6 @@ class FractionalOffsetDirectional extends FractionalOffsetGeometry { ...@@ -471,9 +457,6 @@ class FractionalOffsetDirectional extends FractionalOffsetGeometry {
@override @override
double get _dy => dy; double get _dy => dy;
@override
double get _dx => 0.0;
/// The top corner on the "start" side. /// The top corner on the "start" side.
static const FractionalOffsetDirectional topStart = const FractionalOffsetDirectional(0.0, 0.0); static const FractionalOffsetDirectional topStart = const FractionalOffsetDirectional(0.0, 0.0);
...@@ -578,84 +561,89 @@ class FractionalOffsetDirectional extends FractionalOffsetGeometry { ...@@ -578,84 +561,89 @@ class FractionalOffsetDirectional extends FractionalOffsetGeometry {
} }
@override @override
FractionalOffset resolve(TextDirection direction) { String toString() {
assert(direction != null); assert(start != 0.5);
switch (direction) { if (start == 0.0 && dy == 0.0)
case TextDirection.ltr: return 'FractionalOffsetDirectional.topStart';
return new FractionalOffset(start, dy); if (start == 1.0 && dy == 0.0)
case TextDirection.rtl: return 'FractionalOffsetDirectional.topEnd';
return new FractionalOffset(1.0 - start, dy); if (start == 0.0 && dy == 0.5)
} return 'FractionalOffsetDirectional.centerStart';
return null; if (start == 1.0 && dy == 0.5)
return 'FractionalOffsetDirectional.centerEnd';
if (start == 0.0 && dy == 1.0)
return 'FractionalOffsetDirectional.bottomStart';
if (start == 1.0 && dy == 1.0)
return 'FractionalOffsetDirectional.bottomEnd';
return 'FractionalOffsetDirectional(${start.toStringAsFixed(1)}, '
'${dy.toStringAsFixed(1)})';
} }
} }
class _MixedFractionalOffset extends FractionalOffsetGeometry { class _SchrodingersFractionalOffset extends FractionalOffsetGeometry {
const _MixedFractionalOffset(this._dx, this._start, this._dy); const _SchrodingersFractionalOffset(this._dxForRTL, this._dxForLTR, this._dy);
@override @override
final double _dx; final double _dxForRTL;
@override @override
final double _start; final double _dxForLTR;
@override @override
final double _dy; final double _dy;
@override @override
_MixedFractionalOffset operator -() { _SchrodingersFractionalOffset operator -() {
return new _MixedFractionalOffset( return new _SchrodingersFractionalOffset(
-_dx, -_dxForRTL,
-_start, -_dxForLTR,
-_dy, -_dy,
); );
} }
@override @override
_MixedFractionalOffset operator *(double other) { _SchrodingersFractionalOffset operator *(double other) {
return new _MixedFractionalOffset( return new _SchrodingersFractionalOffset(
_dx * other, _dxForRTL * other,
_start * other, _dxForLTR * other,
_dy * other, _dy * other,
); );
} }
@override @override
_MixedFractionalOffset operator /(double other) { _SchrodingersFractionalOffset operator /(double other) {
return new _MixedFractionalOffset( return new _SchrodingersFractionalOffset(
_dx / other, _dxForRTL / other,
_start / other, _dxForLTR / other,
_dy / other, _dy / other,
); );
} }
@override @override
_MixedFractionalOffset operator ~/(double other) { _SchrodingersFractionalOffset operator ~/(double other) {
return new _MixedFractionalOffset( return new _SchrodingersFractionalOffset(
(_dx ~/ other).toDouble(), (_dxForRTL ~/ other).toDouble(),
(_start ~/ other).toDouble(), (_dxForLTR ~/ other).toDouble(),
(_dy ~/ other).toDouble(), (_dy ~/ other).toDouble(),
); );
} }
@override @override
_MixedFractionalOffset operator %(double other) { _SchrodingersFractionalOffset operator %(double other) {
return new _MixedFractionalOffset( return new _SchrodingersFractionalOffset(
_dx % other, _dxForRTL % other,
_start % other, _dxForLTR % other,
_dy % other, _dy % other,
); );
} }
@override @override
FractionalOffset resolve(TextDirection direction) { String toString() {
assert(direction != null); if (_dxForRTL == _dxForLTR)
switch (direction) { return FractionalOffset._stringify(_dxForRTL, _dy);
case TextDirection.ltr:
return new FractionalOffset(_start + _dx, _dy); return '${FractionalOffset._stringify(_dxForRTL, _dy)} in RTL'
case TextDirection.rtl: ' or '
return new FractionalOffset((1.0 - _start) + _dx, _dy); '${FractionalOffset._stringify(_dxForLTR, _dy)} in LTR';
}
return null;
} }
} }
...@@ -11,11 +11,16 @@ import 'package:flutter/painting.dart'; ...@@ -11,11 +11,16 @@ import 'package:flutter/painting.dart';
/// appropriate for rectangles. /// appropriate for rectangles.
/// ///
/// See [Tween] for a discussion on how to use interpolation objects. /// See [Tween] for a discussion on how to use interpolation objects.
///
/// See also:
///
/// * [FractionalOffsetGeometryTween], which interpolates between two
/// [FractionalOffsetGeometry] objects.
class FractionalOffsetTween extends Tween<FractionalOffset> { class FractionalOffsetTween extends Tween<FractionalOffset> {
/// Creates a fractional offset tween. /// Creates a fractional offset tween.
/// ///
/// The [begin] and [end] properties may be null; the null value /// The [begin] and [end] properties may be null; the null value
/// is treated as meaning the top left corner. /// is treated as meaning the center.
FractionalOffsetTween({ FractionalOffset begin, FractionalOffset end }) FractionalOffsetTween({ FractionalOffset begin, FractionalOffset end })
: super(begin: begin, end: end); : super(begin: begin, end: end);
...@@ -23,3 +28,29 @@ class FractionalOffsetTween extends Tween<FractionalOffset> { ...@@ -23,3 +28,29 @@ class FractionalOffsetTween extends Tween<FractionalOffset> {
@override @override
FractionalOffset lerp(double t) => FractionalOffset.lerp(begin, end, t); FractionalOffset lerp(double t) => FractionalOffset.lerp(begin, end, t);
} }
/// An interpolation between two [FractionalOffsetGeometry].
///
/// This class specializes the interpolation of [Tween<FractionalOffsetGeometry>]
/// to be appropriate for rectangles.
///
/// See [Tween] for a discussion on how to use interpolation objects.
///
/// See also:
///
/// * [FractionalOffsetTween], which interpolates between two
/// [FractionalOffset] objects.
class FractionalOffsetGeometryTween extends Tween<FractionalOffsetGeometry> {
/// Creates a fractional offset geometry tween.
///
/// The [begin] and [end] properties may be null; the null value
/// is treated as meaning the center.
FractionalOffsetGeometryTween({
FractionalOffsetGeometry begin,
FractionalOffsetGeometry end,
}) : super(begin: begin, end: end);
/// Returns the value this variable has at the given animation clock value.
@override
FractionalOffsetGeometry lerp(double t) => FractionalOffsetGeometry.lerp(begin, end, t);
}
...@@ -29,6 +29,7 @@ export 'package:flutter/rendering.dart' show ...@@ -29,6 +29,7 @@ export 'package:flutter/rendering.dart' show
FlowDelegate, FlowDelegate,
FlowPaintingContext, FlowPaintingContext,
FractionalOffsetTween, FractionalOffsetTween,
FractionalOffsetGeometryTween,
HitTestBehavior, HitTestBehavior,
LayerLink, LayerLink,
MainAxisAlignment, MainAxisAlignment,
......
...@@ -61,6 +61,11 @@ class DecorationTween extends Tween<Decoration> { ...@@ -61,6 +61,11 @@ class DecorationTween extends Tween<Decoration> {
/// [EdgeInsets.lerp]. /// [EdgeInsets.lerp].
/// ///
/// See [Tween] for a discussion on how to use interpolation objects. /// See [Tween] for a discussion on how to use interpolation objects.
///
/// See also:
///
/// * [EdgeInsetsGeometryTween], which interpolates between two
/// [EdgeInsetsGeometry] objects.
class EdgeInsetsTween extends Tween<EdgeInsets> { class EdgeInsetsTween extends Tween<EdgeInsets> {
/// Creates an [EdgeInsets] tween. /// Creates an [EdgeInsets] tween.
/// ///
...@@ -73,6 +78,28 @@ class EdgeInsetsTween extends Tween<EdgeInsets> { ...@@ -73,6 +78,28 @@ class EdgeInsetsTween extends Tween<EdgeInsets> {
EdgeInsets lerp(double t) => EdgeInsets.lerp(begin, end, t); EdgeInsets lerp(double t) => EdgeInsets.lerp(begin, end, t);
} }
/// An interpolation between two [EdgeInsetsGeometry]s.
///
/// This class specializes the interpolation of [Tween<EdgeInsetsGeometry>] to
/// use [EdgeInsetsGeometry.lerp].
///
/// See [Tween] for a discussion on how to use interpolation objects.
///
/// See also:
///
/// * [EdgeInsetsTween], which interpolates between two [EdgeInsets] objects.
class EdgeInsetsGeometryTween extends Tween<EdgeInsetsGeometry> {
/// Creates an [EdgeInsetsGeometry] tween.
///
/// The [begin] and [end] properties may be null; the null value
/// is treated as an [EdgeInsetsGeometry] with no inset.
EdgeInsetsGeometryTween({ EdgeInsetsGeometry begin, EdgeInsetsGeometry end }) : super(begin: begin, end: end);
/// Returns the value this variable has at the given animation clock value.
@override
EdgeInsetsGeometry lerp(double t) => EdgeInsetsGeometry.lerp(begin, end, t);
}
/// An interpolation between two [BorderRadius]s. /// An interpolation between two [BorderRadius]s.
/// ///
/// This class specializes the interpolation of [Tween<BorderRadius>] to use /// This class specializes the interpolation of [Tween<BorderRadius>] to use
...@@ -358,11 +385,11 @@ class AnimatedContainer extends ImplicitlyAnimatedWidget { ...@@ -358,11 +385,11 @@ class AnimatedContainer extends ImplicitlyAnimatedWidget {
/// constraints are unbounded, then the child will be shrink-wrapped instead. /// constraints are unbounded, then the child will be shrink-wrapped instead.
/// ///
/// Ignored if [child] is null. /// Ignored if [child] is null.
final FractionalOffset alignment; final FractionalOffsetGeometry alignment;
/// Empty space to inscribe inside the [decoration]. The [child], if any, is /// Empty space to inscribe inside the [decoration]. The [child], if any, is
/// placed inside this padding. /// placed inside this padding.
final EdgeInsets padding; final EdgeInsetsGeometry padding;
/// The decoration to paint behind the [child]. /// The decoration to paint behind the [child].
/// ///
...@@ -383,7 +410,7 @@ class AnimatedContainer extends ImplicitlyAnimatedWidget { ...@@ -383,7 +410,7 @@ class AnimatedContainer extends ImplicitlyAnimatedWidget {
final BoxConstraints constraints; final BoxConstraints constraints;
/// Empty space to surround the [decoration] and [child]. /// Empty space to surround the [decoration] and [child].
final EdgeInsets margin; final EdgeInsetsGeometry margin;
/// The transformation matrix to apply before painting the container. /// The transformation matrix to apply before painting the container.
final Matrix4 transform; final Matrix4 transform;
...@@ -394,33 +421,33 @@ class AnimatedContainer extends ImplicitlyAnimatedWidget { ...@@ -394,33 +421,33 @@ class AnimatedContainer extends ImplicitlyAnimatedWidget {
@override @override
void debugFillProperties(DiagnosticPropertiesBuilder description) { void debugFillProperties(DiagnosticPropertiesBuilder description) {
super.debugFillProperties(description); super.debugFillProperties(description);
description.add(new DiagnosticsProperty<FractionalOffset>('alignment', alignment, showName: false, defaultValue: null)); description.add(new DiagnosticsProperty<FractionalOffsetGeometry>('alignment', alignment, showName: false, defaultValue: null));
description.add(new DiagnosticsProperty<EdgeInsets>('padding', padding, defaultValue: null)); description.add(new DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
description.add(new DiagnosticsProperty<Decoration>('bg', decoration, defaultValue: null)); description.add(new DiagnosticsProperty<Decoration>('bg', decoration, defaultValue: null));
description.add(new DiagnosticsProperty<Decoration>('fg', foregroundDecoration, defaultValue: null)); description.add(new DiagnosticsProperty<Decoration>('fg', foregroundDecoration, defaultValue: null));
description.add(new DiagnosticsProperty<BoxConstraints>('constraints', constraints, defaultValue: null, showName: false)); description.add(new DiagnosticsProperty<BoxConstraints>('constraints', constraints, defaultValue: null, showName: false));
description.add(new DiagnosticsProperty<EdgeInsets>('margin', margin, defaultValue: null)); description.add(new DiagnosticsProperty<EdgeInsetsGeometry>('margin', margin, defaultValue: null));
description.add(new ObjectFlagProperty<Matrix4>.has('transform', transform)); description.add(new ObjectFlagProperty<Matrix4>.has('transform', transform));
} }
} }
class _AnimatedContainerState extends AnimatedWidgetBaseState<AnimatedContainer> { class _AnimatedContainerState extends AnimatedWidgetBaseState<AnimatedContainer> {
FractionalOffsetTween _alignment; FractionalOffsetGeometryTween _alignment;
EdgeInsetsTween _padding; EdgeInsetsGeometryTween _padding;
DecorationTween _decoration; DecorationTween _decoration;
DecorationTween _foregroundDecoration; DecorationTween _foregroundDecoration;
BoxConstraintsTween _constraints; BoxConstraintsTween _constraints;
EdgeInsetsTween _margin; EdgeInsetsGeometryTween _margin;
Matrix4Tween _transform; Matrix4Tween _transform;
@override @override
void forEachTween(TweenVisitor<dynamic> visitor) { void forEachTween(TweenVisitor<dynamic> visitor) {
_alignment = visitor(_alignment, widget.alignment, (dynamic value) => new FractionalOffsetTween(begin: value)); _alignment = visitor(_alignment, widget.alignment, (dynamic value) => new FractionalOffsetGeometryTween(begin: value));
_padding = visitor(_padding, widget.padding, (dynamic value) => new EdgeInsetsTween(begin: value)); _padding = visitor(_padding, widget.padding, (dynamic value) => new EdgeInsetsGeometryTween(begin: value));
_decoration = visitor(_decoration, widget.decoration, (dynamic value) => new DecorationTween(begin: value)); _decoration = visitor(_decoration, widget.decoration, (dynamic value) => new DecorationTween(begin: value));
_foregroundDecoration = visitor(_foregroundDecoration, widget.foregroundDecoration, (dynamic value) => new DecorationTween(begin: value)); _foregroundDecoration = visitor(_foregroundDecoration, widget.foregroundDecoration, (dynamic value) => new DecorationTween(begin: value));
_constraints = visitor(_constraints, widget.constraints, (dynamic value) => new BoxConstraintsTween(begin: value)); _constraints = visitor(_constraints, widget.constraints, (dynamic value) => new BoxConstraintsTween(begin: value));
_margin = visitor(_margin, widget.margin, (dynamic value) => new EdgeInsetsTween(begin: value)); _margin = visitor(_margin, widget.margin, (dynamic value) => new EdgeInsetsGeometryTween(begin: value));
_transform = visitor(_transform, widget.transform, (dynamic value) => new Matrix4Tween(begin: value)); _transform = visitor(_transform, widget.transform, (dynamic value) => new Matrix4Tween(begin: value));
} }
...@@ -441,12 +468,12 @@ class _AnimatedContainerState extends AnimatedWidgetBaseState<AnimatedContainer> ...@@ -441,12 +468,12 @@ class _AnimatedContainerState extends AnimatedWidgetBaseState<AnimatedContainer>
@override @override
void debugFillProperties(DiagnosticPropertiesBuilder description) { void debugFillProperties(DiagnosticPropertiesBuilder description) {
super.debugFillProperties(description); super.debugFillProperties(description);
description.add(new DiagnosticsProperty<FractionalOffsetTween>('alignment', _alignment, showName: false, defaultValue: null)); description.add(new DiagnosticsProperty<FractionalOffsetGeometryTween>('alignment', _alignment, showName: false, defaultValue: null));
description.add(new DiagnosticsProperty<EdgeInsetsTween>('padding', _padding, defaultValue: null)); description.add(new DiagnosticsProperty<EdgeInsetsGeometryTween>('padding', _padding, defaultValue: null));
description.add(new DiagnosticsProperty<DecorationTween>('bg', _decoration, defaultValue: null)); description.add(new DiagnosticsProperty<DecorationTween>('bg', _decoration, defaultValue: null));
description.add(new DiagnosticsProperty<DecorationTween>('fg', _foregroundDecoration, defaultValue: null)); description.add(new DiagnosticsProperty<DecorationTween>('fg', _foregroundDecoration, defaultValue: null));
description.add(new DiagnosticsProperty<BoxConstraintsTween>('constraints', _constraints, showName: false, defaultValue: null)); description.add(new DiagnosticsProperty<BoxConstraintsTween>('constraints', _constraints, showName: false, defaultValue: null));
description.add(new DiagnosticsProperty<EdgeInsetsTween>('margin', _margin, defaultValue: null)); description.add(new DiagnosticsProperty<EdgeInsetsGeometryTween>('margin', _margin, defaultValue: null));
description.add(new ObjectFlagProperty<Matrix4Tween>.has('transform', _transform)); description.add(new ObjectFlagProperty<Matrix4Tween>.has('transform', _transform));
} }
} }
......
...@@ -73,15 +73,15 @@ void main() { ...@@ -73,15 +73,15 @@ void main() {
expect(FractionalOffsetGeometry.lerp(directional1, directional2, 0.5), const FractionalOffsetDirectional(0.125 + (2.0 - 0.125) / 2.0, 0.625 + (3.0 - 0.625) / 2.0)); expect(FractionalOffsetGeometry.lerp(directional1, directional2, 0.5), const FractionalOffsetDirectional(0.125 + (2.0 - 0.125) / 2.0, 0.625 + (3.0 - 0.625) / 2.0));
expect(FractionalOffsetGeometry.lerp(directional2, directional2, 0.5), directional2); expect(FractionalOffsetGeometry.lerp(directional2, directional2, 0.5), directional2);
expect(FractionalOffsetGeometry.lerp(directional1, normal2, 0.5).resolve(TextDirection.ltr), const FractionalOffset(1.0 + 1.0 / 16.0, 0.625 + (3.0 - 0.625) / 2.0)); expect(FractionalOffsetGeometry.lerp(directional1, normal2, 0.5).resolve(TextDirection.ltr), const FractionalOffset(1.0 + 1.0 / 16.0, 0.625 + (3.0 - 0.625) / 2.0));
expect(FractionalOffsetGeometry.lerp(directional1, normal2, 0.5).resolve(TextDirection.rtl), const FractionalOffset(1.0 + 15.0 / 16.0, 0.625 + (3.0 - 0.625) / 2.0)); expect(FractionalOffsetGeometry.lerp(directional1, normal2, 0.5).resolve(TextDirection.rtl), new FractionalOffset(lerpDouble(0.875, 2.0, 0.5), 0.625 + (3.0 - 0.625) / 2.0));
expect(FractionalOffsetGeometry.lerp(directional1, mixed1, 0.5).resolve(TextDirection.ltr), new FractionalOffset(1.0 / 32.0 + 2.5 / 16.0, lerpDouble(0.625, 0.5625 + 0.6875, 0.5))); expect(FractionalOffsetGeometry.lerp(directional1, mixed1, 0.5).resolve(TextDirection.ltr), new FractionalOffset(1.0 / 32.0 + 2.5 / 16.0, lerpDouble(0.625, 0.5625 + 0.6875, 0.5)));
expect(FractionalOffsetGeometry.lerp(directional1, mixed1, 0.5).resolve(TextDirection.rtl), new FractionalOffset(1.0 / 32.0 + 1.0 - 2.5 / 16.0, lerpDouble(0.625, 0.5625 + 0.6875, 0.5))); expect(FractionalOffsetGeometry.lerp(directional1, mixed1, 0.5).resolve(TextDirection.rtl), new FractionalOffset(1.0 / 32.0 + 1.0 - 2.5 / 16.0, lerpDouble(0.625, 0.5625 + 0.6875, 0.5)));
expect(FractionalOffsetGeometry.lerp(mixed1, mixed2, 0.5).resolve(TextDirection.ltr), new FractionalOffset(3.0 + 5.0 / 8.0, lerpDouble(0.5625 + 0.6875, 6.0, 0.5))); expect(FractionalOffsetGeometry.lerp(mixed1, mixed2, 0.5).resolve(TextDirection.ltr), new FractionalOffset(3.0 + 5.0 / 8.0, lerpDouble(0.5625 + 0.6875, 6.0, 0.5)));
expect(FractionalOffsetGeometry.lerp(mixed1, mixed2, 0.5).resolve(TextDirection.rtl), new FractionalOffset(2.0 - 41.0 / 16.0, lerpDouble(0.5625 + 0.6875, 6.0, 0.5))); expect(FractionalOffsetGeometry.lerp(mixed1, mixed2, 0.5).resolve(TextDirection.rtl), new FractionalOffset(2.0 - 41.0 / 16.0, lerpDouble(0.5625 + 0.6875, 6.0, 0.5)));
expect(FractionalOffsetGeometry.lerp(normal1, normal2, 0.5), const FractionalOffset(0.25 + (2.0 - 0.25) / 2.0, 0.875 + (3.0 - 0.875) / 2.0)); expect(FractionalOffsetGeometry.lerp(normal1, normal2, 0.5), const FractionalOffset(0.25 + (2.0 - 0.25) / 2.0, 0.875 + (3.0 - 0.875) / 2.0));
expect(FractionalOffsetGeometry.lerp(normal1, mixed1, 0.5).resolve(TextDirection.ltr), new FractionalOffset(lerpDouble(0.25, 0.0625, 0.5) + lerpDouble(0.0, 0.1875, 0.5), lerpDouble(0.875, 0.5625 + 0.6875, 0.5))); expect(FractionalOffsetGeometry.lerp(normal1, mixed1, 0.5).resolve(TextDirection.ltr), new FractionalOffset(lerpDouble(0.25, 0.0625, 0.5) + lerpDouble(0.0, 0.1875, 0.5), lerpDouble(0.875, 0.5625 + 0.6875, 0.5)));
expect(FractionalOffsetGeometry.lerp(normal1, mixed1, 0.5).resolve(TextDirection.rtl), new FractionalOffset(lerpDouble(0.25, 0.0625, 0.5) + 1.0 - lerpDouble(0.0, 0.1875, 0.5), lerpDouble(0.875, 0.5625 + 0.6875, 0.5))); expect(FractionalOffsetGeometry.lerp(normal1, mixed1, 0.5).resolve(TextDirection.rtl), new FractionalOffset(lerpDouble(0.25, 0.0625 + 0.8125, 0.5), lerpDouble(0.875, 0.5625 + 0.6875, 0.5)));
expect(FractionalOffsetGeometry.lerp(null, mixed1, 0.5).resolve(TextDirection.ltr), FractionalOffsetGeometry.lerp(FractionalOffset.center, mixed1, 0.5).resolve(TextDirection.ltr)); expect(FractionalOffsetGeometry.lerp(null, mixed1, 0.5).resolve(TextDirection.ltr), FractionalOffsetGeometry.lerp(FractionalOffset.center, mixed1, 0.5).resolve(TextDirection.ltr));
expect(FractionalOffsetGeometry.lerp(mixed2, null, 0.25).resolve(TextDirection.ltr), FractionalOffsetGeometry.lerp(FractionalOffset.center, mixed2, 0.75).resolve(TextDirection.ltr)); expect(FractionalOffsetGeometry.lerp(mixed2, null, 0.25).resolve(TextDirection.ltr), FractionalOffsetGeometry.lerp(FractionalOffset.center, mixed2, 0.75).resolve(TextDirection.ltr));
expect(FractionalOffsetGeometry.lerp(directional1, null, 1.0), FractionalOffsetDirectional.center); expect(FractionalOffsetGeometry.lerp(directional1, null, 1.0), FractionalOffsetDirectional.center);
...@@ -98,6 +98,35 @@ void main() { ...@@ -98,6 +98,35 @@ void main() {
expect(FractionalOffsetGeometry.lerp(mixed1, mixed2, 0.25), mixed3); expect(FractionalOffsetGeometry.lerp(mixed1, mixed2, 0.25), mixed3);
}); });
test('lerp commutes with resolve', () {
final List<FractionalOffsetGeometry> offsets = <FractionalOffsetGeometry>[
const FractionalOffset(-1.0, 0.65),
const FractionalOffsetDirectional(-1.0, 0.45),
const FractionalOffsetDirectional(0.125, 0.625),
const FractionalOffset(0.25, 0.875),
const FractionalOffset(0.0625, 0.5625).add(const FractionalOffsetDirectional(0.1875, 0.6875)),
const FractionalOffsetDirectional(2.0, 3.0),
const FractionalOffset(2.0, 3.0),
const FractionalOffset(2.0, 3.0).add(const FractionalOffsetDirectional(5.0, 3.0)),
const FractionalOffset(10.0, 20.0).add(const FractionalOffsetDirectional(30.0, 50.0)),
const FractionalOffset(70.0, 110.0).add(const FractionalOffsetDirectional(130.0, 170.0)),
const FractionalOffset(25.0, 42.5).add(const FractionalOffsetDirectional(55.0, 80.0)),
];
final List<double> times = <double>[ 0.0, 0.25, 0.5, 0.75, 1.0 ];
for (TextDirection direction in TextDirection.values) {
for (FractionalOffsetGeometry a in offsets) {
for (FractionalOffsetGeometry b in offsets) {
for (double t in times) {
expect(FractionalOffsetGeometry.lerp(a, b, t).resolve(direction),
FractionalOffset.lerp(a.resolve(direction), b.resolve(direction), t));
}
}
}
}
});
test('FractionalOffsetGeometry add/subtract', () { test('FractionalOffsetGeometry add/subtract', () {
final FractionalOffsetGeometry directional = const FractionalOffsetDirectional(1.0, 2.0); final FractionalOffsetGeometry directional = const FractionalOffsetDirectional(1.0, 2.0);
final FractionalOffsetGeometry normal = const FractionalOffset(3.0, 5.0); final FractionalOffsetGeometry normal = const FractionalOffset(3.0, 5.0);
...@@ -136,10 +165,10 @@ void main() { ...@@ -136,10 +165,10 @@ void main() {
test('FractionalOffsetGeometry toString', () { test('FractionalOffsetGeometry toString', () {
expect(const FractionalOffset(1.0001, 2.0001).toString(), 'FractionalOffset(1.0, 2.0)'); expect(const FractionalOffset(1.0001, 2.0001).toString(), 'FractionalOffset(1.0, 2.0)');
expect(const FractionalOffset(0.0, 0.0).toString(), 'FractionalOffset.topLeft'); expect(const FractionalOffset(0.0, 0.0).toString(), 'FractionalOffset.topLeft');
expect(const FractionalOffset(0.0, 1.0).add(const FractionalOffsetDirectional(1.0, 0.0)).toString(), 'FractionalOffsetDirectional.bottomEnd'); expect(const FractionalOffset(0.0, 1.0).add(const FractionalOffsetDirectional(1.0, 0.0)).toString(), 'FractionalOffset.bottomLeft in RTL or FractionalOffset.bottomRight in LTR');
expect(const FractionalOffset(0.0001, 0.0001).toString(), 'FractionalOffset(0.0, 0.0)'); expect(const FractionalOffset(0.0001, 0.0001).toString(), 'FractionalOffset(0.0, 0.0)');
expect(const FractionalOffset(0.0, 0.0).toString(), 'FractionalOffset.topLeft'); expect(const FractionalOffset(0.0, 0.0).toString(), 'FractionalOffset.topLeft');
expect(const FractionalOffsetDirectional(0.0, 0.0).toString(), 'FractionalOffsetDirectional.topStart'); expect(const FractionalOffsetDirectional(0.0, 0.0).toString(), 'FractionalOffsetDirectional.topStart');
expect(const FractionalOffset(1.0, 1.0).add(const FractionalOffsetDirectional(1.0, 1.0)).toString(), 'FractionalOffset(1.0, 2.0) + FractionalOffsetDirectional(1.0, 0.0)'); expect(const FractionalOffset(1.0, 1.0).add(const FractionalOffsetDirectional(1.0, 1.0)).toString(), 'FractionalOffset(1.0, 2.0) in RTL or FractionalOffset(2.0, 2.0) in LTR');
}); });
} }
...@@ -139,6 +139,90 @@ void main() { ...@@ -139,6 +139,90 @@ void main() {
expect(tester.binding.transientCallbackCount, 0); expect(tester.binding.transientCallbackCount, 0);
}); });
testWidgets('AnimatedContainer padding visual-to-directional animation', (WidgetTester tester) async {
final Key target = new UniqueKey();
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.rtl,
child: new AnimatedContainer(
duration: const Duration(milliseconds: 200),
padding: const EdgeInsets.only(right: 50.0),
child: new SizedBox.expand(key: target),
),
),
);
expect(tester.getSize(find.byKey(target)), const Size(750.0, 600.0));
expect(tester.getTopRight(find.byKey(target)), const Offset(750.0, 0.0));
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.rtl,
child: new AnimatedContainer(
duration: const Duration(milliseconds: 200),
padding: const EdgeInsetsDirectional.only(start: 100.0),
child: new SizedBox.expand(key: target),
),
),
);
expect(tester.getSize(find.byKey(target)), const Size(750.0, 600.0));
expect(tester.getTopRight(find.byKey(target)), const Offset(750.0, 0.0));
await tester.pump(const Duration(milliseconds: 100));
expect(tester.getSize(find.byKey(target)), const Size(725.0, 600.0));
expect(tester.getTopRight(find.byKey(target)), const Offset(725.0, 0.0));
await tester.pump(const Duration(milliseconds: 500));
expect(tester.getSize(find.byKey(target)), const Size(700.0, 600.0));
expect(tester.getTopRight(find.byKey(target)), const Offset(700.0, 0.0));
});
testWidgets('AnimatedContainer alignment visual-to-directional animation', (WidgetTester tester) async {
final Key target = new UniqueKey();
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.rtl,
child: new AnimatedContainer(
duration: const Duration(milliseconds: 200),
alignment: FractionalOffset.topRight,
child: new SizedBox(key: target, width: 100.0, height: 200.0),
),
),
);
expect(tester.getSize(find.byKey(target)), const Size(100.0, 200.0));
expect(tester.getTopRight(find.byKey(target)), const Offset(800.0, 0.0));
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.rtl,
child: new AnimatedContainer(
duration: const Duration(milliseconds: 200),
alignment: FractionalOffsetDirectional.bottomStart,
child: new SizedBox(key: target, width: 100.0, height: 200.0),
),
),
);
expect(tester.getSize(find.byKey(target)), const Size(100.0, 200.0));
expect(tester.getTopRight(find.byKey(target)), const Offset(800.0, 0.0));
await tester.pump(const Duration(milliseconds: 100));
expect(tester.getSize(find.byKey(target)), const Size(100.0, 200.0));
expect(tester.getTopRight(find.byKey(target)), const Offset(800.0, 200.0));
await tester.pump(const Duration(milliseconds: 500));
expect(tester.getSize(find.byKey(target)), const Size(100.0, 200.0));
expect(tester.getTopRight(find.byKey(target)), const Offset(800.0, 400.0));
});
testWidgets('Animation rerun', (WidgetTester tester) async { testWidgets('Animation rerun', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
new Center( new Center(
......
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