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

Add RTL support to ListBody (#12414)

Fixes #11930
parent cd3715a8
...@@ -32,7 +32,6 @@ export 'package:vector_math/vector_math_64.dart' show Matrix4; ...@@ -32,7 +32,6 @@ export 'package:vector_math/vector_math_64.dart' show Matrix4;
export 'src/rendering/animated_size.dart'; export 'src/rendering/animated_size.dart';
export 'src/rendering/binding.dart'; export 'src/rendering/binding.dart';
export 'src/rendering/block.dart';
export 'src/rendering/box.dart'; export 'src/rendering/box.dart';
export 'src/rendering/custom_layout.dart'; export 'src/rendering/custom_layout.dart';
export 'src/rendering/debug.dart'; export 'src/rendering/debug.dart';
...@@ -42,6 +41,7 @@ export 'src/rendering/flex.dart'; ...@@ -42,6 +41,7 @@ export 'src/rendering/flex.dart';
export 'src/rendering/flow.dart'; export 'src/rendering/flow.dart';
export 'src/rendering/image.dart'; export 'src/rendering/image.dart';
export 'src/rendering/layer.dart'; export 'src/rendering/layer.dart';
export 'src/rendering/list_body.dart';
export 'src/rendering/node.dart'; export 'src/rendering/node.dart';
export 'src/rendering/object.dart'; export 'src/rendering/object.dart';
export 'src/rendering/paragraph.dart'; export 'src/rendering/paragraph.dart';
......
...@@ -649,11 +649,15 @@ class _MergeableMaterialListBody extends ListBody { ...@@ -649,11 +649,15 @@ class _MergeableMaterialListBody extends ListBody {
final List<MergeableMaterialItem> items; final List<MergeableMaterialItem> items;
final List<BoxShadow> boxShadows; final List<BoxShadow> boxShadows;
AxisDirection _getDirection(BuildContext context) {
return getAxisDirectionFromAxisReverseAndDirectionality(context, mainAxis, false);
}
@override @override
RenderListBody createRenderObject(BuildContext context) { RenderListBody createRenderObject(BuildContext context) {
return new _RenderMergeableMaterialListBody( return new _RenderMergeableMaterialListBody(
mainAxis: mainAxis, axisDirection: _getDirection(context),
boxShadows: boxShadows boxShadows: boxShadows,
); );
} }
...@@ -661,7 +665,7 @@ class _MergeableMaterialListBody extends ListBody { ...@@ -661,7 +665,7 @@ class _MergeableMaterialListBody extends ListBody {
void updateRenderObject(BuildContext context, RenderListBody renderObject) { void updateRenderObject(BuildContext context, RenderListBody renderObject) {
final _RenderMergeableMaterialListBody materialRenderListBody = renderObject; final _RenderMergeableMaterialListBody materialRenderListBody = renderObject;
materialRenderListBody materialRenderListBody
..mainAxis = mainAxis ..axisDirection = _getDirection(context)
..boxShadows = boxShadows; ..boxShadows = boxShadows;
} }
} }
...@@ -669,9 +673,9 @@ class _MergeableMaterialListBody extends ListBody { ...@@ -669,9 +673,9 @@ class _MergeableMaterialListBody extends ListBody {
class _RenderMergeableMaterialListBody extends RenderListBody { class _RenderMergeableMaterialListBody extends RenderListBody {
_RenderMergeableMaterialListBody({ _RenderMergeableMaterialListBody({
List<RenderBox> children, List<RenderBox> children,
Axis mainAxis: Axis.vertical, AxisDirection axisDirection: AxisDirection.down,
this.boxShadows this.boxShadows
}) : super(children: children, mainAxis: mainAxis); }) : super(children: children, axisDirection: axisDirection);
List<BoxShadow> boxShadows; List<BoxShadow> boxShadows;
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:ui' show TextDirection;
export 'dart:ui' show export 'dart:ui' show
BlendMode, BlendMode,
BlurStyle, BlurStyle,
...@@ -157,3 +159,108 @@ enum VerticalDirection { ...@@ -157,3 +159,108 @@ enum VerticalDirection {
/// The "start" is at the top, the "end" is at the bottom. /// The "start" is at the top, the "end" is at the bottom.
down, 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;
}
...@@ -31,8 +31,9 @@ class RenderListBody extends RenderBox ...@@ -31,8 +31,9 @@ class RenderListBody extends RenderBox
/// By default, children are arranged along the vertical axis. /// By default, children are arranged along the vertical axis.
RenderListBody({ RenderListBody({
List<RenderBox> children, List<RenderBox> children,
Axis mainAxis: Axis.vertical, AxisDirection axisDirection: AxisDirection.down,
}) : _mainAxis = mainAxis { }) : assert(axisDirection != null),
_axisDirection = axisDirection {
addAll(children); addAll(children);
} }
...@@ -42,41 +43,17 @@ class RenderListBody extends RenderBox ...@@ -42,41 +43,17 @@ class RenderListBody extends RenderBox
child.parentData = new ListBodyParentData(); child.parentData = new ListBodyParentData();
} }
/// The direction to use as the main axis. AxisDirection get axisDirection => _axisDirection;
Axis get mainAxis => _mainAxis; AxisDirection _axisDirection;
Axis _mainAxis; set axisDirection(AxisDirection value) {
set mainAxis(Axis value) { assert(value != null);
if (_mainAxis != value) { if (_axisDirection == value)
_mainAxis = value; return;
markNeedsLayout(); _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 { Axis get mainAxis => axisDirectionToAxis(axisDirection);
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;
}
@override @override
void performLayout() { void performLayout() {
...@@ -124,41 +101,81 @@ class RenderListBody extends RenderBox ...@@ -124,41 +101,81 @@ class RenderListBody extends RenderBox
'This is relatively expensive, however.' // (that's why we don't do it automatically) 'This is relatively expensive, however.' // (that's why we don't do it automatically)
); );
}()); }());
final BoxConstraints innerConstraints = _getInnerConstraints(constraints); double mainAxisExtent = 0.0;
double position = 0.0;
RenderBox child = firstChild; RenderBox child = firstChild;
while (child != null) { switch (axisDirection) {
child.layout(innerConstraints, parentUsesSize: true); case AxisDirection.right:
final ListBodyParentData childParentData = child.parentData; final BoxConstraints innerConstraints = new BoxConstraints.tightFor(height: constraints.maxHeight);
switch (mainAxis) { while (child != null) {
case Axis.horizontal: child.layout(innerConstraints, parentUsesSize: true);
childParentData.offset = new Offset(position, 0.0); final ListBodyParentData childParentData = child.parentData;
position += child.size.width; childParentData.offset = new Offset(mainAxisExtent, 0.0);
break; mainAxisExtent += child.size.width;
case Axis.vertical: assert(child.parentData == childParentData);
childParentData.offset = new Offset(0.0, position); child = childParentData.nextSibling;
position += child.size.height;
break;
} }
assert(child.parentData == childParentData); size = constraints.constrain(new Size(mainAxisExtent, constraints.maxHeight));
child = childParentData.nextSibling; break;
} case AxisDirection.left:
switch (mainAxis) { final BoxConstraints innerConstraints = new BoxConstraints.tightFor(height: constraints.maxHeight);
case Axis.horizontal: while (child != null) {
size = constraints.constrain(new Size(_mainAxisExtent, constraints.maxHeight)); child.layout(innerConstraints, parentUsesSize: true);
break; final ListBodyParentData childParentData = child.parentData;
case Axis.vertical: mainAxisExtent += child.size.width;
size = constraints.constrain(new Size(constraints.maxWidth, _mainAxisExtent)); assert(child.parentData == childParentData);
break; 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;
} }
assert(size.isFinite); assert(size.isFinite);
} }
@override @override
void debugFillProperties(DiagnosticPropertiesBuilder description) { void debugFillProperties(DiagnosticPropertiesBuilder description) {
super.debugFillProperties(description); super.debugFillProperties(description);
description.add(new EnumProperty<Axis>('mainAxis', mainAxis)); description.add(new EnumProperty<AxisDirection>('axisDirection', axisDirection));
} }
double _getIntrinsicCrossAxis(_ChildSizingFunction childSize) { double _getIntrinsicCrossAxis(_ChildSizingFunction childSize) {
......
...@@ -41,111 +41,6 @@ enum GrowthDirection { ...@@ -41,111 +41,6 @@ enum GrowthDirection {
reverse, 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]. /// Flips the [AxisDirection] if the [GrowthDirection] is [GrowthDirection.reverse].
/// ///
/// Specifically, returns `axisDirection` if `growthDirection` is /// Specifically, returns `axisDirection` if `growthDirection` is
......
...@@ -2116,6 +2116,42 @@ class SliverPadding extends SingleChildRenderObjectWidget { ...@@ -2116,6 +2116,42 @@ class SliverPadding extends SingleChildRenderObjectWidget {
// LAYOUT NODES // 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 /// A widget that arranges its children sequentially along a given axis, forcing
/// them to the dimension of the parent in the other axis. /// them to the dimension of the parent in the other axis.
/// ///
...@@ -2142,6 +2178,7 @@ class ListBody extends MultiChildRenderObjectWidget { ...@@ -2142,6 +2178,7 @@ class ListBody extends MultiChildRenderObjectWidget {
ListBody({ ListBody({
Key key, Key key,
this.mainAxis: Axis.vertical, this.mainAxis: Axis.vertical,
this.reverse: false,
List<Widget> children: const <Widget>[], List<Widget> children: const <Widget>[],
}) : assert(mainAxis != null), }) : assert(mainAxis != null),
super(key: key, children: children); super(key: key, children: children);
...@@ -2149,12 +2186,32 @@ class ListBody extends MultiChildRenderObjectWidget { ...@@ -2149,12 +2186,32 @@ class ListBody extends MultiChildRenderObjectWidget {
/// The direction to use as the main axis. /// The direction to use as the main axis.
final Axis mainAxis; 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 @override
RenderListBody createRenderObject(BuildContext context) => new RenderListBody(mainAxis: mainAxis); RenderListBody createRenderObject(BuildContext context) {
return new RenderListBody(axisDirection: _getDirection(context));
}
@override @override
void updateRenderObject(BuildContext context, RenderListBody renderObject) { void updateRenderObject(BuildContext context, RenderListBody renderObject) {
renderObject.mainAxis = mainAxis; renderObject.axisDirection = _getDirection(context);
} }
} }
......
...@@ -6,7 +6,6 @@ import 'package:flutter/foundation.dart'; ...@@ -6,7 +6,6 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'basic.dart'; import 'basic.dart';
import 'debug.dart';
import 'framework.dart'; import 'framework.dart';
import 'primary_scroll_controller.dart'; import 'primary_scroll_controller.dart';
import 'scroll_controller.dart'; import 'scroll_controller.dart';
...@@ -179,16 +178,7 @@ abstract class ScrollView extends StatelessWidget { ...@@ -179,16 +178,7 @@ abstract class ScrollView extends StatelessWidget {
/// [AxisDirection.right]. /// [AxisDirection.right].
@protected @protected
AxisDirection getDirection(BuildContext context) { AxisDirection getDirection(BuildContext context) {
switch (scrollDirection) { return getAxisDirectionFromAxisReverseAndDirectionality(context, scrollDirection, reverse);
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;
} }
/// Subclasses should override this method to build the slivers for the inside /// Subclasses should override this method to build the slivers for the inside
......
...@@ -8,7 +8,6 @@ import 'package:flutter/foundation.dart'; ...@@ -8,7 +8,6 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'basic.dart'; import 'basic.dart';
import 'debug.dart';
import 'framework.dart'; import 'framework.dart';
import 'primary_scroll_controller.dart'; import 'primary_scroll_controller.dart';
import 'scroll_controller.dart'; import 'scroll_controller.dart';
...@@ -70,7 +69,7 @@ class SingleChildScrollView extends StatelessWidget { ...@@ -70,7 +69,7 @@ class SingleChildScrollView extends StatelessWidget {
/// left to right when [reverse] is false and from right to left when /// left to right when [reverse] is false and from right to left when
/// [reverse] is true. /// [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 /// scrolls from top to bottom when [reverse] is false and from bottom to top
/// when [reverse] is true. /// when [reverse] is true.
/// ///
...@@ -116,16 +115,7 @@ class SingleChildScrollView extends StatelessWidget { ...@@ -116,16 +115,7 @@ class SingleChildScrollView extends StatelessWidget {
final Widget child; final Widget child;
AxisDirection _getDirection(BuildContext context) { AxisDirection _getDirection(BuildContext context) {
switch (scrollDirection) { return getAxisDirectionFromAxisReverseAndDirectionality(context, scrollDirection, reverse);
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;
} }
@override @override
......
...@@ -17,7 +17,7 @@ void main() { ...@@ -17,7 +17,7 @@ void main() {
final RenderListBody testBlock = new RenderListBody( final RenderListBody testBlock = new RenderListBody(
children: <RenderBox>[ children: <RenderBox>[
paragraph, paragraph,
] ],
); );
final double textWidth = paragraph.getMaxIntrinsicWidth(double.INFINITY); final double textWidth = paragraph.getMaxIntrinsicWidth(double.INFINITY);
...@@ -52,7 +52,7 @@ void main() { ...@@ -52,7 +52,7 @@ void main() {
expect(testBlock.getMaxIntrinsicHeight(0.0), equals(manyLinesTextHeight)); expect(testBlock.getMaxIntrinsicHeight(0.0), equals(manyLinesTextHeight));
// horizontal block (same expectations again) // horizontal block (same expectations again)
testBlock.mainAxis = Axis.horizontal; testBlock.axisDirection = AxisDirection.right;
expect(testBlock.getMinIntrinsicWidth(double.INFINITY), equals(wrappedTextWidth)); expect(testBlock.getMinIntrinsicWidth(double.INFINITY), equals(wrappedTextWidth));
expect(testBlock.getMaxIntrinsicWidth(double.INFINITY), equals(textWidth)); expect(testBlock.getMaxIntrinsicWidth(double.INFINITY), equals(textWidth));
expect(testBlock.getMinIntrinsicHeight(double.INFINITY), equals(oneLineTextHeight)); expect(testBlock.getMinIntrinsicHeight(double.INFINITY), equals(oneLineTextHeight));
......
// 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),
],
);
});
}
...@@ -285,6 +285,10 @@ abstract class Finder { ...@@ -285,6 +285,10 @@ abstract class Finder {
/// matched by this finder. /// matched by this finder.
Finder get last => new _LastFinder(this); 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 /// Returns a variant of this finder that only matches elements reachable by
/// a hit test. /// a hit test.
/// ///
...@@ -335,6 +339,22 @@ class _LastFinder extends Finder { ...@@ -335,6 +339,22 @@ class _LastFinder extends Finder {
} }
} }
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 { class _HitTestableFinder extends Finder {
_HitTestableFinder(this.parent, this.alignment); _HitTestableFinder(this.parent, this.alignment);
......
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