Add RTL support to ListBody (#12414)
Fixes #11930
diff --git a/packages/flutter/lib/rendering.dart b/packages/flutter/lib/rendering.dart
index 3e3a809..af00b83 100644
--- a/packages/flutter/lib/rendering.dart
+++ b/packages/flutter/lib/rendering.dart
@@ -32,7 +32,6 @@
export 'src/rendering/animated_size.dart';
export 'src/rendering/binding.dart';
-export 'src/rendering/block.dart';
export 'src/rendering/box.dart';
export 'src/rendering/custom_layout.dart';
export 'src/rendering/debug.dart';
@@ -42,6 +41,7 @@
export 'src/rendering/flow.dart';
export 'src/rendering/image.dart';
export 'src/rendering/layer.dart';
+export 'src/rendering/list_body.dart';
export 'src/rendering/node.dart';
export 'src/rendering/object.dart';
export 'src/rendering/paragraph.dart';
diff --git a/packages/flutter/lib/src/material/mergeable_material.dart b/packages/flutter/lib/src/material/mergeable_material.dart
index 3c3fb5a..fa3a4aa 100644
--- a/packages/flutter/lib/src/material/mergeable_material.dart
+++ b/packages/flutter/lib/src/material/mergeable_material.dart
@@ -649,11 +649,15 @@
final List<MergeableMaterialItem> items;
final List<BoxShadow> boxShadows;
+ AxisDirection _getDirection(BuildContext context) {
+ return getAxisDirectionFromAxisReverseAndDirectionality(context, mainAxis, false);
+ }
+
@override
RenderListBody createRenderObject(BuildContext context) {
return new _RenderMergeableMaterialListBody(
- mainAxis: mainAxis,
- boxShadows: boxShadows
+ axisDirection: _getDirection(context),
+ boxShadows: boxShadows,
);
}
@@ -661,7 +665,7 @@
void updateRenderObject(BuildContext context, RenderListBody renderObject) {
final _RenderMergeableMaterialListBody materialRenderListBody = renderObject;
materialRenderListBody
- ..mainAxis = mainAxis
+ ..axisDirection = _getDirection(context)
..boxShadows = boxShadows;
}
}
@@ -669,9 +673,9 @@
class _RenderMergeableMaterialListBody extends RenderListBody {
_RenderMergeableMaterialListBody({
List<RenderBox> children,
- Axis mainAxis: Axis.vertical,
+ AxisDirection axisDirection: AxisDirection.down,
this.boxShadows
- }) : super(children: children, mainAxis: mainAxis);
+ }) : super(children: children, axisDirection: axisDirection);
List<BoxShadow> boxShadows;
diff --git a/packages/flutter/lib/src/painting/basic_types.dart b/packages/flutter/lib/src/painting/basic_types.dart
index aaddbd7..ee157f8 100644
--- a/packages/flutter/lib/src/painting/basic_types.dart
+++ b/packages/flutter/lib/src/painting/basic_types.dart
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'dart:ui' show TextDirection;
+
export 'dart:ui' show
BlendMode,
BlurStyle,
@@ -157,3 +159,108 @@
/// The "start" is at the top, the "end" is at the bottom.
down,
}
+
+/// A direction along either the horizontal or vertical [Axis].
+enum AxisDirection {
+ /// Zero is at the bottom and positive values are above it: ⇈
+ ///
+ /// Alphabetical content with a [GrowthDirection.forward] would have the A at
+ /// the bottom and the Z at the top. This is an unusual configuration.
+ up,
+
+ /// Zero is on the left and positive values are to the right of it: ⇉
+ ///
+ /// Alphabetical content with a [GrowthDirection.forward] would have the A on
+ /// the left and the Z on the right. This is the ordinary reading order for a
+ /// horizontal set of tabs in an English application, for example.
+ right,
+
+ /// Zero is at the top and positive values are below it: ⇊
+ ///
+ /// Alphabetical content with a [GrowthDirection.forward] would have the A at
+ /// the top and the Z at the bottom. This is the ordinary reading order for a
+ /// vertical list.
+ down,
+
+ /// Zero is to the right and positive values are to the left of it: ⇇
+ ///
+ /// Alphabetical content with a [GrowthDirection.forward] would have the A at
+ /// the right and the Z at the left. This is the ordinary reading order for a
+ /// horizontal set of tabs in a Hebrew application, for example.
+ left,
+}
+
+/// Returns the [Axis] that contains the given [AxisDirection].
+///
+/// Specifically, returns [Axis.vertical] for [AxisDirection.up] and
+/// [AxisDirection.down] and returns [Axis.horizontal] for [AxisDirection.left]
+/// and [AxisDirection.right].
+Axis axisDirectionToAxis(AxisDirection axisDirection) {
+ assert(axisDirection != null);
+ switch (axisDirection) {
+ case AxisDirection.up:
+ case AxisDirection.down:
+ return Axis.vertical;
+ case AxisDirection.left:
+ case AxisDirection.right:
+ return Axis.horizontal;
+ }
+ return null;
+}
+
+/// Returns the [AxisDirection] in which reading occurs in the given [TextDirection].
+///
+/// Specifically, returns [AxisDirection.left] for [TextDirection.rtl] and
+/// [AxisDirection.right] for [TextDirection.ltr].
+AxisDirection textDirectionToAxisDirection(TextDirection textDirection) {
+ assert(textDirection != null);
+ switch (textDirection) {
+ case TextDirection.rtl:
+ return AxisDirection.left;
+ case TextDirection.ltr:
+ return AxisDirection.right;
+ }
+ return null;
+}
+
+/// Returns the opposite of the given [AxisDirection].
+///
+/// Specifically, returns [AxisDirection.up] for [AxisDirection.down] (and
+/// vice versa), as well as [AxisDirection.left] for [AxisDirection.right] (and
+/// vice versa).
+///
+/// See also:
+///
+/// * [flipAxis], which does the same thing for [Axis] values.
+AxisDirection flipAxisDirection(AxisDirection axisDirection) {
+ assert(axisDirection != null);
+ switch (axisDirection) {
+ case AxisDirection.up:
+ return AxisDirection.down;
+ case AxisDirection.right:
+ return AxisDirection.left;
+ case AxisDirection.down:
+ return AxisDirection.up;
+ case AxisDirection.left:
+ return AxisDirection.right;
+ }
+ return null;
+}
+
+/// Returns whether travelling along the given axis direction visits coordinates
+/// along that axis in numerically decreasing order.
+///
+/// Specifically, returns true for [AxisDirection.up] and [AxisDirection.left]
+/// and false for [AxisDirection.down] for [AxisDirection.right].
+bool axisDirectionIsReversed(AxisDirection axisDirection) {
+ assert(axisDirection != null);
+ switch (axisDirection) {
+ case AxisDirection.up:
+ case AxisDirection.left:
+ return true;
+ case AxisDirection.down:
+ case AxisDirection.right:
+ return false;
+ }
+ return null;
+}
diff --git a/packages/flutter/lib/src/rendering/block.dart b/packages/flutter/lib/src/rendering/list_body.dart
similarity index 63%
rename from packages/flutter/lib/src/rendering/block.dart
rename to packages/flutter/lib/src/rendering/list_body.dart
index 4e13b94..cfe3cb2 100644
--- a/packages/flutter/lib/src/rendering/block.dart
+++ b/packages/flutter/lib/src/rendering/list_body.dart
@@ -31,8 +31,9 @@
/// By default, children are arranged along the vertical axis.
RenderListBody({
List<RenderBox> children,
- Axis mainAxis: Axis.vertical,
- }) : _mainAxis = mainAxis {
+ AxisDirection axisDirection: AxisDirection.down,
+ }) : assert(axisDirection != null),
+ _axisDirection = axisDirection {
addAll(children);
}
@@ -42,41 +43,17 @@
child.parentData = new ListBodyParentData();
}
- /// The direction to use as the main axis.
- Axis get mainAxis => _mainAxis;
- Axis _mainAxis;
- set mainAxis(Axis value) {
- if (_mainAxis != value) {
- _mainAxis = value;
- markNeedsLayout();
- }
+ AxisDirection get axisDirection => _axisDirection;
+ AxisDirection _axisDirection;
+ set axisDirection(AxisDirection value) {
+ assert(value != null);
+ if (_axisDirection == value)
+ return;
+ _axisDirection = value;
+ markNeedsLayout();
}
- BoxConstraints _getInnerConstraints(BoxConstraints constraints) {
- assert(_mainAxis != null);
- switch (_mainAxis) {
- case Axis.horizontal:
- return new BoxConstraints.tightFor(height: constraints.maxHeight);
- case Axis.vertical:
- return new BoxConstraints.tightFor(width: constraints.maxWidth);
- }
- return null;
- }
-
- double get _mainAxisExtent {
- final RenderBox child = lastChild;
- if (child == null)
- return 0.0;
- final BoxParentData parentData = child.parentData;
- assert(mainAxis != null);
- switch (mainAxis) {
- case Axis.horizontal:
- return parentData.offset.dx + child.size.width;
- case Axis.vertical:
- return parentData.offset.dy + child.size.height;
- }
- return null;
- }
+ Axis get mainAxis => axisDirectionToAxis(axisDirection);
@override
void performLayout() {
@@ -124,41 +101,81 @@
'This is relatively expensive, however.' // (that's why we don't do it automatically)
);
}());
- final BoxConstraints innerConstraints = _getInnerConstraints(constraints);
- double position = 0.0;
+ double mainAxisExtent = 0.0;
RenderBox child = firstChild;
- while (child != null) {
- child.layout(innerConstraints, parentUsesSize: true);
- final ListBodyParentData childParentData = child.parentData;
- switch (mainAxis) {
- case Axis.horizontal:
- childParentData.offset = new Offset(position, 0.0);
- position += child.size.width;
- break;
- case Axis.vertical:
- childParentData.offset = new Offset(0.0, position);
- position += child.size.height;
- break;
+ switch (axisDirection) {
+ case AxisDirection.right:
+ final BoxConstraints innerConstraints = new BoxConstraints.tightFor(height: constraints.maxHeight);
+ while (child != null) {
+ child.layout(innerConstraints, parentUsesSize: true);
+ final ListBodyParentData childParentData = child.parentData;
+ childParentData.offset = new Offset(mainAxisExtent, 0.0);
+ mainAxisExtent += child.size.width;
+ assert(child.parentData == childParentData);
+ child = childParentData.nextSibling;
}
- assert(child.parentData == childParentData);
- child = childParentData.nextSibling;
+ size = constraints.constrain(new Size(mainAxisExtent, constraints.maxHeight));
+ break;
+ case AxisDirection.left:
+ final BoxConstraints innerConstraints = new BoxConstraints.tightFor(height: constraints.maxHeight);
+ while (child != null) {
+ child.layout(innerConstraints, parentUsesSize: true);
+ final ListBodyParentData childParentData = child.parentData;
+ mainAxisExtent += child.size.width;
+ assert(child.parentData == childParentData);
+ child = childParentData.nextSibling;
+ }
+ double position = 0.0;
+ child = firstChild;
+ while (child != null) {
+ final ListBodyParentData childParentData = child.parentData;
+ position += child.size.width;
+ childParentData.offset = new Offset(mainAxisExtent - position, 0.0);
+ assert(child.parentData == childParentData);
+ child = childParentData.nextSibling;
+ }
+ size = constraints.constrain(new Size(mainAxisExtent, constraints.maxHeight));
+ break;
+ case AxisDirection.down:
+ final BoxConstraints innerConstraints = new BoxConstraints.tightFor(width: constraints.maxWidth);
+ while (child != null) {
+ child.layout(innerConstraints, parentUsesSize: true);
+ final ListBodyParentData childParentData = child.parentData;
+ childParentData.offset = new Offset(0.0, mainAxisExtent);
+ mainAxisExtent += child.size.height;
+ assert(child.parentData == childParentData);
+ child = childParentData.nextSibling;
+ }
+ size = constraints.constrain(new Size(constraints.maxWidth, mainAxisExtent));
+ break;
+ case AxisDirection.up:
+ final BoxConstraints innerConstraints = new BoxConstraints.tightFor(width: constraints.maxWidth);
+ while (child != null) {
+ child.layout(innerConstraints, parentUsesSize: true);
+ final ListBodyParentData childParentData = child.parentData;
+ mainAxisExtent += child.size.height;
+ assert(child.parentData == childParentData);
+ child = childParentData.nextSibling;
+ }
+ double position = 0.0;
+ child = firstChild;
+ while (child != null) {
+ final ListBodyParentData childParentData = child.parentData;
+ position += child.size.height;
+ childParentData.offset = new Offset(0.0, mainAxisExtent - position);
+ assert(child.parentData == childParentData);
+ child = childParentData.nextSibling;
+ }
+ size = constraints.constrain(new Size(constraints.maxWidth, mainAxisExtent));
+ break;
}
- switch (mainAxis) {
- case Axis.horizontal:
- size = constraints.constrain(new Size(_mainAxisExtent, constraints.maxHeight));
- break;
- case Axis.vertical:
- size = constraints.constrain(new Size(constraints.maxWidth, _mainAxisExtent));
- break;
- }
-
assert(size.isFinite);
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder description) {
super.debugFillProperties(description);
- description.add(new EnumProperty<Axis>('mainAxis', mainAxis));
+ description.add(new EnumProperty<AxisDirection>('axisDirection', axisDirection));
}
double _getIntrinsicCrossAxis(_ChildSizingFunction childSize) {
diff --git a/packages/flutter/lib/src/rendering/sliver.dart b/packages/flutter/lib/src/rendering/sliver.dart
index ea810c3..7588744 100644
--- a/packages/flutter/lib/src/rendering/sliver.dart
+++ b/packages/flutter/lib/src/rendering/sliver.dart
@@ -41,111 +41,6 @@
reverse,
}
-/// A direction along either the horizontal or vertical [Axis].
-enum AxisDirection {
- /// Zero is at the bottom and positive values are above it: ⇈
- ///
- /// Alphabetical content with a [GrowthDirection.forward] would have the A at
- /// the bottom and the Z at the top. This is an unusual configuration.
- up,
-
- /// Zero is on the left and positive values are to the right of it: ⇉
- ///
- /// Alphabetical content with a [GrowthDirection.forward] would have the A on
- /// the left and the Z on the right. This is the ordinary reading order for a
- /// horizontal set of tabs in an English application, for example.
- right,
-
- /// Zero is at the top and positive values are below it: ⇊
- ///
- /// Alphabetical content with a [GrowthDirection.forward] would have the A at
- /// the top and the Z at the bottom. This is the ordinary reading order for a
- /// vertical list.
- down,
-
- /// Zero is to the right and positive values are to the left of it: ⇇
- ///
- /// Alphabetical content with a [GrowthDirection.forward] would have the A at
- /// the right and the Z at the left. This is the ordinary reading order for a
- /// horizontal set of tabs in a Hebrew application, for example.
- left,
-}
-
-/// Returns the [Axis] that contains the given [AxisDirection].
-///
-/// Specifically, returns [Axis.vertical] for [AxisDirection.up] and
-/// [AxisDirection.down] and returns [Axis.horizontal] for [AxisDirection.left]
-/// and [AxisDirection.right].
-Axis axisDirectionToAxis(AxisDirection axisDirection) {
- assert(axisDirection != null);
- switch (axisDirection) {
- case AxisDirection.up:
- case AxisDirection.down:
- return Axis.vertical;
- case AxisDirection.left:
- case AxisDirection.right:
- return Axis.horizontal;
- }
- return null;
-}
-
-/// Returns the [AxisDirection] in which reading occurs in the given [TextDirection].
-///
-/// Specifically, returns [AxisDirection.left] for [TextDirection.rtl] and
-/// [AxisDirection.right] for [TextDirection.ltr].
-AxisDirection textDirectionToAxisDirection(TextDirection textDirection) {
- assert(textDirection != null);
- switch (textDirection) {
- case TextDirection.rtl:
- return AxisDirection.left;
- case TextDirection.ltr:
- return AxisDirection.right;
- }
- return null;
-}
-
-/// Returns the opposite of the given [AxisDirection].
-///
-/// Specifically, returns [AxisDirection.up] for [AxisDirection.down] (and
-/// vice versa), as well as [AxisDirection.left] for [AxisDirection.right] (and
-/// vice versa).
-///
-/// See also:
-///
-/// * [flipAxis], which does the same thing for [Axis] values.
-AxisDirection flipAxisDirection(AxisDirection axisDirection) {
- assert(axisDirection != null);
- switch (axisDirection) {
- case AxisDirection.up:
- return AxisDirection.down;
- case AxisDirection.right:
- return AxisDirection.left;
- case AxisDirection.down:
- return AxisDirection.up;
- case AxisDirection.left:
- return AxisDirection.right;
- }
- return null;
-}
-
-/// Returns whether travelling along the given axis direction visits coordinates
-/// along that axis in numerically decreasing order.
-///
-/// Specifically, returns true for [AxisDirection.up] and [AxisDirection.left]
-/// and false for [AxisDirection.down] for [AxisDirection.right].
-bool axisDirectionIsReversed(AxisDirection axisDirection) {
- assert(axisDirection != null);
- switch (axisDirection) {
- case AxisDirection.up:
- case AxisDirection.left:
- return true;
- case AxisDirection.down:
- case AxisDirection.right:
- return false;
- }
- return null;
-}
-
/// Flips the [AxisDirection] if the [GrowthDirection] is [GrowthDirection.reverse].
///
/// Specifically, returns `axisDirection` if `growthDirection` is
diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart
index 3a455cf..6e4de19 100644
--- a/packages/flutter/lib/src/widgets/basic.dart
+++ b/packages/flutter/lib/src/widgets/basic.dart
@@ -2116,6 +2116,42 @@
// LAYOUT NODES
+/// Returns the [AxisDirection] in the given [Axis] in the current
+/// [Directionality] (or the reverse if `reverse` is true).
+///
+/// If `axis` is [Axis.vertical], this function returns [AxisDirection.down]
+/// unless `reverse` is true, in which case this function returns
+/// [AxisDirection.up].
+///
+/// If `axis` is [Axis.horizontal], this function checks the current
+/// [Directionality]. If the current [Directionality] is right-to-left, then
+/// this function returns [AxisDirection.left] (unless `reverse` is true, in
+/// which case it returns [AxisDirection.right]). Similarly, if the current
+/// [Directionality] is left-to-right, then this function returns
+/// [AxisDirection.right] (unless `reverse` is true, in which case it returns
+/// [AxisDirection.left]).
+///
+/// This function is used by a number of scrolling widgets (e.g., [ListView],
+/// [GridView], [PageView], and [SingleChildScrollView]) as well as [ListBody]
+/// to translate their [Axis] and `reverse` properties into a concrete
+/// [AxisDirection].
+AxisDirection getAxisDirectionFromAxisReverseAndDirectionality(
+ BuildContext context,
+ Axis axis,
+ bool reverse,
+) {
+ switch (axis) {
+ case Axis.horizontal:
+ assert(debugCheckHasDirectionality(context));
+ final TextDirection textDirection = Directionality.of(context);
+ final AxisDirection axisDirection = textDirectionToAxisDirection(textDirection);
+ return reverse ? flipAxisDirection(axisDirection) : axisDirection;
+ case Axis.vertical:
+ return reverse ? AxisDirection.up : AxisDirection.down;
+ }
+ return null;
+}
+
/// A widget that arranges its children sequentially along a given axis, forcing
/// them to the dimension of the parent in the other axis.
///
@@ -2142,6 +2178,7 @@
ListBody({
Key key,
this.mainAxis: Axis.vertical,
+ this.reverse: false,
List<Widget> children: const <Widget>[],
}) : assert(mainAxis != null),
super(key: key, children: children);
@@ -2149,12 +2186,32 @@
/// The direction to use as the main axis.
final Axis mainAxis;
+ /// Whether the list body positions children in the reading direction.
+ ///
+ /// For example, if the reading direction is left-to-right and
+ /// [mainAxis] is [Axis.horizontal], then the list body positions children
+ /// from left to right when [reverse] is false and from right to left when
+ /// [reverse] is true.
+ ///
+ /// Similarly, if [mainAxis] is [Axis.vertical], then the list body positions
+ /// from top to bottom when [reverse] is false and from bottom to top when
+ /// [reverse] is true.
+ ///
+ /// Defaults to false.
+ final bool reverse;
+
+ AxisDirection _getDirection(BuildContext context) {
+ return getAxisDirectionFromAxisReverseAndDirectionality(context, mainAxis, reverse);
+ }
+
@override
- RenderListBody createRenderObject(BuildContext context) => new RenderListBody(mainAxis: mainAxis);
+ RenderListBody createRenderObject(BuildContext context) {
+ return new RenderListBody(axisDirection: _getDirection(context));
+ }
@override
void updateRenderObject(BuildContext context, RenderListBody renderObject) {
- renderObject.mainAxis = mainAxis;
+ renderObject.axisDirection = _getDirection(context);
}
}
diff --git a/packages/flutter/lib/src/widgets/scroll_view.dart b/packages/flutter/lib/src/widgets/scroll_view.dart
index a827e61..d2b5030 100644
--- a/packages/flutter/lib/src/widgets/scroll_view.dart
+++ b/packages/flutter/lib/src/widgets/scroll_view.dart
@@ -6,7 +6,6 @@
import 'package:flutter/rendering.dart';
import 'basic.dart';
-import 'debug.dart';
import 'framework.dart';
import 'primary_scroll_controller.dart';
import 'scroll_controller.dart';
@@ -179,16 +178,7 @@
/// [AxisDirection.right].
@protected
AxisDirection getDirection(BuildContext context) {
- switch (scrollDirection) {
- case Axis.horizontal:
- assert(debugCheckHasDirectionality(context));
- final TextDirection textDirection = Directionality.of(context);
- final AxisDirection axisDirection = textDirectionToAxisDirection(textDirection);
- return reverse ? flipAxisDirection(axisDirection) : axisDirection;
- case Axis.vertical:
- return reverse ? AxisDirection.up : AxisDirection.down;
- }
- return null;
+ return getAxisDirectionFromAxisReverseAndDirectionality(context, scrollDirection, reverse);
}
/// Subclasses should override this method to build the slivers for the inside
diff --git a/packages/flutter/lib/src/widgets/single_child_scroll_view.dart b/packages/flutter/lib/src/widgets/single_child_scroll_view.dart
index e2bf2bf..a9c513e 100644
--- a/packages/flutter/lib/src/widgets/single_child_scroll_view.dart
+++ b/packages/flutter/lib/src/widgets/single_child_scroll_view.dart
@@ -8,7 +8,6 @@
import 'package:flutter/rendering.dart';
import 'basic.dart';
-import 'debug.dart';
import 'framework.dart';
import 'primary_scroll_controller.dart';
import 'scroll_controller.dart';
@@ -70,7 +69,7 @@
/// left to right when [reverse] is false and from right to left when
/// [reverse] is true.
///
- /// Similarly, if [scrollDirection] is [Axis.vertical], then scroll view
+ /// Similarly, if [scrollDirection] is [Axis.vertical], then the scroll view
/// scrolls from top to bottom when [reverse] is false and from bottom to top
/// when [reverse] is true.
///
@@ -116,16 +115,7 @@
final Widget child;
AxisDirection _getDirection(BuildContext context) {
- switch (scrollDirection) {
- case Axis.horizontal:
- assert(debugCheckHasDirectionality(context));
- final TextDirection textDirection = Directionality.of(context);
- final AxisDirection axisDirection = textDirectionToAxisDirection(textDirection);
- return reverse ? flipAxisDirection(axisDirection) : axisDirection;
- case Axis.vertical:
- return reverse ? AxisDirection.up : AxisDirection.down;
- }
- return null;
+ return getAxisDirectionFromAxisReverseAndDirectionality(context, scrollDirection, reverse);
}
@override
diff --git a/packages/flutter/test/rendering/paragraph_intrinsics_test.dart b/packages/flutter/test/rendering/paragraph_intrinsics_test.dart
index 644d709..75e9db9 100644
--- a/packages/flutter/test/rendering/paragraph_intrinsics_test.dart
+++ b/packages/flutter/test/rendering/paragraph_intrinsics_test.dart
@@ -17,7 +17,7 @@
final RenderListBody testBlock = new RenderListBody(
children: <RenderBox>[
paragraph,
- ]
+ ],
);
final double textWidth = paragraph.getMaxIntrinsicWidth(double.INFINITY);
@@ -52,7 +52,7 @@
expect(testBlock.getMaxIntrinsicHeight(0.0), equals(manyLinesTextHeight));
// horizontal block (same expectations again)
- testBlock.mainAxis = Axis.horizontal;
+ testBlock.axisDirection = AxisDirection.right;
expect(testBlock.getMinIntrinsicWidth(double.INFINITY), equals(wrappedTextWidth));
expect(testBlock.getMaxIntrinsicWidth(double.INFINITY), equals(textWidth));
expect(testBlock.getMinIntrinsicHeight(double.INFINITY), equals(oneLineTextHeight));
diff --git a/packages/flutter/test/widgets/list_body_test.dart b/packages/flutter/test/widgets/list_body_test.dart
new file mode 100644
index 0000000..734d7f1
--- /dev/null
+++ b/packages/flutter/test/widgets/list_body_test.dart
@@ -0,0 +1,109 @@
+// 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:flutter_test/flutter_test.dart';
+import 'package:flutter/widgets.dart';
+
+final List<Widget> children = <Widget>[
+ new Container(width: 200.0, height: 150.0),
+ new Container(width: 200.0, height: 150.0),
+ new Container(width: 200.0, height: 150.0),
+ new Container(width: 200.0, height: 150.0),
+];
+
+void expectRects(WidgetTester tester, List<Rect> expected) {
+ final Finder finder = find.byType(Container);
+ finder.precache();
+ final List<Rect> actual = <Rect>[];
+ for (int i = 0; i < expected.length; ++i) {
+ final Finder current = finder.at(i);
+ expect(current, findsOneWidget);
+ actual.add(tester.getRect(finder.at(i)));
+ }
+ expect(() => finder.at(expected.length), throwsRangeError);
+ expect(actual, equals(expected));
+}
+
+void main() {
+
+ testWidgets('ListBody down', (WidgetTester tester) async {
+ await tester.pumpWidget(new Flex(
+ direction: Axis.vertical,
+ children: <Widget>[ new ListBody(children: children) ],
+ ));
+
+ expectRects(
+ tester,
+ <Rect>[
+ new Rect.fromLTWH(0.0, 0.0, 800.0, 150.0),
+ new Rect.fromLTWH(0.0, 150.0, 800.0, 150.0),
+ new Rect.fromLTWH(0.0, 300.0, 800.0, 150.0),
+ new Rect.fromLTWH(0.0, 450.0, 800.0, 150.0),
+ ],
+ );
+ });
+
+ testWidgets('ListBody up', (WidgetTester tester) async {
+ await tester.pumpWidget(new Flex(
+ direction: Axis.vertical,
+ children: <Widget>[ new ListBody(reverse: true, children: children) ],
+ ));
+
+ expectRects(
+ tester,
+ <Rect>[
+ new Rect.fromLTWH(0.0, 450.0, 800.0, 150.0),
+ new Rect.fromLTWH(0.0, 300.0, 800.0, 150.0),
+ new Rect.fromLTWH(0.0, 150.0, 800.0, 150.0),
+ new Rect.fromLTWH(0.0, 0.0, 800.0, 150.0),
+ ],
+ );
+ });
+
+ testWidgets('ListBody right', (WidgetTester tester) async {
+ await tester.pumpWidget(new Flex(
+ textDirection: TextDirection.ltr,
+ direction: Axis.horizontal,
+ children: <Widget>[
+ new Directionality(
+ textDirection: TextDirection.ltr,
+ child: new ListBody(mainAxis: Axis.horizontal, children: children),
+ ),
+ ],
+ ));
+
+ expectRects(
+ tester,
+ <Rect>[
+ new Rect.fromLTWH(0.0, 0.0, 200.0, 600.0),
+ new Rect.fromLTWH(200.0, 0.0, 200.0, 600.0),
+ new Rect.fromLTWH(400.0, 0.0, 200.0, 600.0),
+ new Rect.fromLTWH(600.0, 0.0, 200.0, 600.0),
+ ],
+ );
+ });
+
+ testWidgets('ListBody left', (WidgetTester tester) async {
+ await tester.pumpWidget(new Flex(
+ textDirection: TextDirection.ltr,
+ direction: Axis.horizontal,
+ children: <Widget>[
+ new Directionality(
+ textDirection: TextDirection.rtl,
+ child: new ListBody(mainAxis: Axis.horizontal, children: children),
+ ),
+ ],
+ ));
+
+ expectRects(
+ tester,
+ <Rect>[
+ new Rect.fromLTWH(600.0, 0.0, 200.0, 600.0),
+ new Rect.fromLTWH(400.0, 0.0, 200.0, 600.0),
+ new Rect.fromLTWH(200.0, 0.0, 200.0, 600.0),
+ new Rect.fromLTWH(0.0, 0.0, 200.0, 600.0),
+ ],
+ );
+ });
+}
diff --git a/packages/flutter_test/lib/src/finders.dart b/packages/flutter_test/lib/src/finders.dart
index e462965..718e3a5 100644
--- a/packages/flutter_test/lib/src/finders.dart
+++ b/packages/flutter_test/lib/src/finders.dart
@@ -285,6 +285,10 @@
/// matched by this finder.
Finder get last => new _LastFinder(this);
+ /// Returns a variant of this finder that only matches the element at the
+ /// given index matched by this finder.
+ Finder at(int index) => new _IndexFinder(this, index);
+
/// Returns a variant of this finder that only matches elements reachable by
/// a hit test.
///
@@ -335,6 +339,22 @@
}
}
+class _IndexFinder extends Finder {
+ _IndexFinder(this.parent, this.index);
+
+ final Finder parent;
+
+ final int index;
+
+ @override
+ String get description => '${parent.description} (ignoring all but index $index)';
+
+ @override
+ Iterable<Element> apply(Iterable<Element> candidates) sync* {
+ yield parent.apply(candidates).elementAt(index);
+ }
+}
+
class _HitTestableFinder extends Finder {
_HitTestableFinder(this.parent, this.alignment);