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
This commit is contained in:
Adam Barth 2017-09-02 22:27:17 -07:00 committed by GitHub
parent 74b0bf6480
commit baf3b45e0d
7 changed files with 310 additions and 150 deletions

View file

@ -752,10 +752,10 @@ class EdgeInsetsDirectional extends EdgeInsetsGeometry {
EdgeInsets resolve(TextDirection direction) {
assert(direction != null);
switch (direction) {
case TextDirection.ltr:
return new EdgeInsets.fromLTRB(start, top, end, bottom);
case TextDirection.rtl:
return new EdgeInsets.fromLTRB(end, top, start, bottom);
case TextDirection.ltr:
return new EdgeInsets.fromLTRB(start, top, end, bottom);
}
return null;
}
@ -856,10 +856,10 @@ class _MixedEdgeInsets extends EdgeInsetsGeometry {
EdgeInsets resolve(TextDirection direction) {
assert(direction != null);
switch (direction) {
case TextDirection.rtl:
return new EdgeInsets.fromLTRB(_end + _left, _top, _start + _right, _bottom);
case TextDirection.ltr:
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;
}

View file

@ -21,8 +21,12 @@ abstract class FractionalOffsetGeometry {
/// const constructors so that they can be used in const expressions.
const FractionalOffsetGeometry();
double get _dx;
double get _start;
/// The [FractionalOffset.dx] to which this object will [resolve] in [TextDirection.ltr].
double get _dxForRTL;
/// The [FractionalOffset.dx] to which this object will [resolve] in [TextDirection.ltr].
double get _dxForLTR;
double get _dy;
/// Returns the difference between two [FractionalOffsetGeometry] objects.
@ -42,9 +46,9 @@ abstract class FractionalOffsetGeometry {
/// negating the argument (using the prefix unary `-` operator or multiplying
/// the argument by -1.0 using the `*` operator).
FractionalOffsetGeometry subtract(FractionalOffsetGeometry other) {
return new _MixedFractionalOffset(
_dx - other._dx,
_start - other._start,
return new _SchrodingersFractionalOffset(
_dxForRTL - other._dxForRTL,
_dxForLTR - other._dxForLTR,
_dy - other._dy,
);
}
@ -61,9 +65,9 @@ abstract class FractionalOffsetGeometry {
/// representing a combination of both is returned. That object can be turned
/// into a concrete [FractionalOffset] using [resolve].
FractionalOffsetGeometry add(FractionalOffsetGeometry other) {
return new _MixedFractionalOffset(
_dx + other._dx,
_start + other._start,
return new _SchrodingersFractionalOffset(
_dxForRTL + other._dxForRTL,
_dxForLTR + other._dxForLTR,
_dy + other._dy,
);
}
@ -113,22 +117,22 @@ abstract class FractionalOffsetGeometry {
if ((a == null || a is FractionalOffsetDirectional) && (b == null || b is FractionalOffsetDirectional))
return FractionalOffsetDirectional.lerp(a, b, t);
if (a == null) {
return new _MixedFractionalOffset(
ui.lerpDouble(0.5, b._dx, t),
ui.lerpDouble(0.0, b._start, t),
return new _SchrodingersFractionalOffset(
ui.lerpDouble(0.5, b._dxForRTL, t),
ui.lerpDouble(0.5, b._dxForLTR, t),
ui.lerpDouble(0.5, b._dy, t),
);
}
if (b == null) {
return new _MixedFractionalOffset(
ui.lerpDouble(a._dx, 0.5, t),
ui.lerpDouble(a._start, 0.0, t),
return new _SchrodingersFractionalOffset(
ui.lerpDouble(a._dxForRTL, 0.5, t),
ui.lerpDouble(a._dxForLTR, 0.5, t),
ui.lerpDouble(a._dy, 0.5, t),
);
}
return new _MixedFractionalOffset(
ui.lerpDouble(a._dx, b._dx, t),
ui.lerpDouble(a._start, b._start, t),
return new _SchrodingersFractionalOffset(
ui.lerpDouble(a._dxForRTL, b._dxForRTL, t),
ui.lerpDouble(a._dxForLTR, b._dxForLTR, t),
ui.lerpDouble(a._dy, b._dy, t),
);
}
@ -142,62 +146,15 @@ abstract class FractionalOffsetGeometry {
/// * [FractionalOffset], for which this is a no-op (returns itself).
/// * [FractionalOffsetDirectional], which flips the horizontal direction
/// based on the `direction` argument.
FractionalOffset resolve(TextDirection direction);
@override
String toString() {
double x = _dx;
double start = _start;
if (this is FractionalOffset) {
assert(start == 0.0);
start = null;
} else if (start == 0.5) {
x += 0.5;
start = null;
FractionalOffset resolve(TextDirection direction) {
assert(direction != null);
switch (direction) {
case TextDirection.rtl:
return new FractionalOffset(_dxForRTL, _dy);
case TextDirection.ltr:
return new FractionalOffset(_dxForLTR, _dy);
}
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)}, '
'${_dy.toStringAsFixed(1)})'
' + '
'FractionalOffsetDirectional(${_start.toStringAsFixed(1)}, '
'0.0)';
return null;
}
@override
@ -205,13 +162,13 @@ abstract class FractionalOffsetGeometry {
if (other is! FractionalOffsetGeometry)
return false;
final FractionalOffsetGeometry typedOther = other;
return _dx == typedOther._dx &&
_start == typedOther._start &&
return _dxForRTL == typedOther._dxForRTL &&
_dxForLTR == typedOther._dxForLTR &&
_dy == typedOther._dy;
}
@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].
@ -279,7 +236,10 @@ class FractionalOffset extends FractionalOffsetGeometry {
final double dx;
@override
double get _dx => dx;
double get _dxForRTL => dx;
@override
double get _dxForLTR => dx;
/// The distance fraction in the vertical direction.
///
@ -292,9 +252,6 @@ class FractionalOffset extends FractionalOffsetGeometry {
@override
double get _dy => dy;
@override
double get _start => 0.0;
/// The top left corner.
static const FractionalOffset topLeft = const FractionalOffset(0.0, 0.0);
@ -420,6 +377,32 @@ class FractionalOffset extends FractionalOffsetGeometry {
@override
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
@ -455,7 +438,10 @@ class FractionalOffsetDirectional extends FractionalOffsetGeometry {
final double start;
@override
double get _start => start;
double get _dxForRTL => 1.0 - start;
@override
double get _dxForLTR => start;
/// The distance fraction in the vertical direction.
///
@ -471,9 +457,6 @@ class FractionalOffsetDirectional extends FractionalOffsetGeometry {
@override
double get _dy => dy;
@override
double get _dx => 0.0;
/// The top corner on the "start" side.
static const FractionalOffsetDirectional topStart = const FractionalOffsetDirectional(0.0, 0.0);
@ -578,84 +561,89 @@ class FractionalOffsetDirectional extends FractionalOffsetGeometry {
}
@override
FractionalOffset resolve(TextDirection direction) {
assert(direction != null);
switch (direction) {
case TextDirection.ltr:
return new FractionalOffset(start, dy);
case TextDirection.rtl:
return new FractionalOffset(1.0 - start, dy);
}
return null;
String toString() {
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)})';
}
}
class _MixedFractionalOffset extends FractionalOffsetGeometry {
const _MixedFractionalOffset(this._dx, this._start, this._dy);
class _SchrodingersFractionalOffset extends FractionalOffsetGeometry {
const _SchrodingersFractionalOffset(this._dxForRTL, this._dxForLTR, this._dy);
@override
final double _dx;
final double _dxForRTL;
@override
final double _start;
final double _dxForLTR;
@override
final double _dy;
@override
_MixedFractionalOffset operator -() {
return new _MixedFractionalOffset(
-_dx,
-_start,
_SchrodingersFractionalOffset operator -() {
return new _SchrodingersFractionalOffset(
-_dxForRTL,
-_dxForLTR,
-_dy,
);
}
@override
_MixedFractionalOffset operator *(double other) {
return new _MixedFractionalOffset(
_dx * other,
_start * other,
_SchrodingersFractionalOffset operator *(double other) {
return new _SchrodingersFractionalOffset(
_dxForRTL * other,
_dxForLTR * other,
_dy * other,
);
}
@override
_MixedFractionalOffset operator /(double other) {
return new _MixedFractionalOffset(
_dx / other,
_start / other,
_SchrodingersFractionalOffset operator /(double other) {
return new _SchrodingersFractionalOffset(
_dxForRTL / other,
_dxForLTR / other,
_dy / other,
);
}
@override
_MixedFractionalOffset operator ~/(double other) {
return new _MixedFractionalOffset(
(_dx ~/ other).toDouble(),
(_start ~/ other).toDouble(),
_SchrodingersFractionalOffset operator ~/(double other) {
return new _SchrodingersFractionalOffset(
(_dxForRTL ~/ other).toDouble(),
(_dxForLTR ~/ other).toDouble(),
(_dy ~/ other).toDouble(),
);
}
@override
_MixedFractionalOffset operator %(double other) {
return new _MixedFractionalOffset(
_dx % other,
_start % other,
_SchrodingersFractionalOffset operator %(double other) {
return new _SchrodingersFractionalOffset(
_dxForRTL % other,
_dxForLTR % other,
_dy % other,
);
}
@override
FractionalOffset resolve(TextDirection direction) {
assert(direction != null);
switch (direction) {
case TextDirection.ltr:
return new FractionalOffset(_start + _dx, _dy);
case TextDirection.rtl:
return new FractionalOffset((1.0 - _start) + _dx, _dy);
}
return null;
String toString() {
if (_dxForRTL == _dxForLTR)
return FractionalOffset._stringify(_dxForRTL, _dy);
return '${FractionalOffset._stringify(_dxForRTL, _dy)} in RTL'
' or '
'${FractionalOffset._stringify(_dxForLTR, _dy)} in LTR';
}
}

View file

@ -11,11 +11,16 @@ import 'package:flutter/painting.dart';
/// appropriate for rectangles.
///
/// 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> {
/// Creates a fractional offset tween.
///
/// 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 })
: super(begin: begin, end: end);
@ -23,3 +28,29 @@ class FractionalOffsetTween extends Tween<FractionalOffset> {
@override
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);
}

View file

@ -29,6 +29,7 @@ export 'package:flutter/rendering.dart' show
FlowDelegate,
FlowPaintingContext,
FractionalOffsetTween,
FractionalOffsetGeometryTween,
HitTestBehavior,
LayerLink,
MainAxisAlignment,

View file

@ -61,6 +61,11 @@ class DecorationTween extends Tween<Decoration> {
/// [EdgeInsets.lerp].
///
/// 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> {
/// Creates an [EdgeInsets] tween.
///
@ -73,6 +78,28 @@ class EdgeInsetsTween extends Tween<EdgeInsets> {
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.
///
/// This class specializes the interpolation of [Tween<BorderRadius>] to use
@ -358,11 +385,11 @@ class AnimatedContainer extends ImplicitlyAnimatedWidget {
/// constraints are unbounded, then the child will be shrink-wrapped instead.
///
/// Ignored if [child] is null.
final FractionalOffset alignment;
final FractionalOffsetGeometry alignment;
/// Empty space to inscribe inside the [decoration]. The [child], if any, is
/// placed inside this padding.
final EdgeInsets padding;
final EdgeInsetsGeometry padding;
/// The decoration to paint behind the [child].
///
@ -383,7 +410,7 @@ class AnimatedContainer extends ImplicitlyAnimatedWidget {
final BoxConstraints constraints;
/// Empty space to surround the [decoration] and [child].
final EdgeInsets margin;
final EdgeInsetsGeometry margin;
/// The transformation matrix to apply before painting the container.
final Matrix4 transform;
@ -394,33 +421,33 @@ class AnimatedContainer extends ImplicitlyAnimatedWidget {
@override
void debugFillProperties(DiagnosticPropertiesBuilder description) {
super.debugFillProperties(description);
description.add(new DiagnosticsProperty<FractionalOffset>('alignment', alignment, showName: false, defaultValue: null));
description.add(new DiagnosticsProperty<EdgeInsets>('padding', padding, defaultValue: null));
description.add(new DiagnosticsProperty<FractionalOffsetGeometry>('alignment', alignment, showName: false, 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>('fg', foregroundDecoration, defaultValue: null));
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));
}
}
class _AnimatedContainerState extends AnimatedWidgetBaseState<AnimatedContainer> {
FractionalOffsetTween _alignment;
EdgeInsetsTween _padding;
FractionalOffsetGeometryTween _alignment;
EdgeInsetsGeometryTween _padding;
DecorationTween _decoration;
DecorationTween _foregroundDecoration;
BoxConstraintsTween _constraints;
EdgeInsetsTween _margin;
EdgeInsetsGeometryTween _margin;
Matrix4Tween _transform;
@override
void forEachTween(TweenVisitor<dynamic> visitor) {
_alignment = visitor(_alignment, widget.alignment, (dynamic value) => new FractionalOffsetTween(begin: value));
_padding = visitor(_padding, widget.padding, (dynamic value) => new EdgeInsetsTween(begin: value));
_alignment = visitor(_alignment, widget.alignment, (dynamic value) => new FractionalOffsetGeometryTween(begin: value));
_padding = visitor(_padding, widget.padding, (dynamic value) => new EdgeInsetsGeometryTween(begin: value));
_decoration = visitor(_decoration, widget.decoration, (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));
_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));
}
@ -441,12 +468,12 @@ class _AnimatedContainerState extends AnimatedWidgetBaseState<AnimatedContainer>
@override
void debugFillProperties(DiagnosticPropertiesBuilder description) {
super.debugFillProperties(description);
description.add(new DiagnosticsProperty<FractionalOffsetTween>('alignment', _alignment, showName: false, defaultValue: null));
description.add(new DiagnosticsProperty<EdgeInsetsTween>('padding', _padding, defaultValue: null));
description.add(new DiagnosticsProperty<FractionalOffsetGeometryTween>('alignment', _alignment, showName: false, 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>('fg', _foregroundDecoration, 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));
}
}

View file

@ -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(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.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.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), 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.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.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, 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(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);
@ -98,6 +98,35 @@ void main() {
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', () {
final FractionalOffsetGeometry directional = const FractionalOffsetDirectional(1.0, 2.0);
final FractionalOffsetGeometry normal = const FractionalOffset(3.0, 5.0);
@ -136,10 +165,10 @@ void main() {
test('FractionalOffsetGeometry toString', () {
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, 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.0, 0.0).toString(), 'FractionalOffset.topLeft');
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');
});
}

View file

@ -139,6 +139,90 @@ void main() {
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 {
await tester.pumpWidget(
new Center(