Commit f53a5a52 authored by Adam Barth's avatar Adam Barth

Add Flow layout

A flow layout is optimized for reposition children with transformation
matrices. A flow layout can animate the position of its children very
efficiently.
parent 3ca80d9e
...@@ -30,6 +30,7 @@ export 'src/rendering/debug.dart'; ...@@ -30,6 +30,7 @@ export 'src/rendering/debug.dart';
export 'src/rendering/editable_line.dart'; export 'src/rendering/editable_line.dart';
export 'src/rendering/error.dart'; export 'src/rendering/error.dart';
export 'src/rendering/flex.dart'; export 'src/rendering/flex.dart';
export 'src/rendering/flow.dart';
export 'src/rendering/grid.dart'; export 'src/rendering/grid.dart';
export 'src/rendering/image.dart'; export 'src/rendering/image.dart';
export 'src/rendering/layer.dart'; export 'src/rendering/layer.dart';
......
...@@ -51,7 +51,7 @@ class SnackBarAction extends StatefulWidget { ...@@ -51,7 +51,7 @@ class SnackBarAction extends StatefulWidget {
/// The button label. /// The button label.
final String label; final String label;
/// The callback to be invoked when the button is pressed. Must be non-null. /// The callback to be invoked when the button is pressed. Must not be null.
/// ///
/// This callback will be invoked at most once each time this action is /// This callback will be invoked at most once each time this action is
/// displayed in a [SnackBar]. /// displayed in a [SnackBar].
......
...@@ -7,7 +7,7 @@ import 'dart:math' as math; ...@@ -7,7 +7,7 @@ import 'dart:math' as math;
import 'box.dart'; import 'box.dart';
import 'object.dart'; import 'object.dart';
/// Parent data for use with [RenderFlex] /// Parent data for use with [RenderFlex].
class FlexParentData extends ContainerBoxParentDataMixin<RenderBox> { class FlexParentData extends ContainerBoxParentDataMixin<RenderBox> {
/// The flex factor to use for this child /// The flex factor to use for this child
/// ///
......
This diff is collapsed.
...@@ -806,6 +806,7 @@ abstract class CustomClipper<T> { ...@@ -806,6 +806,7 @@ abstract class CustomClipper<T> {
/// Returns a description of the clip given that the render object being /// Returns a description of the clip given that the render object being
/// clipped is of the given size. /// clipped is of the given size.
T getClip(Size size); T getClip(Size size);
/// Returns an approximation of the clip returned by [getClip], as /// Returns an approximation of the clip returned by [getClip], as
/// an axis-aligned Rect. This is used by the semantics layer to /// an axis-aligned Rect. This is used by the semantics layer to
/// determine whether widgets should be excluded. /// determine whether widgets should be excluded.
...@@ -815,6 +816,7 @@ abstract class CustomClipper<T> { ...@@ -815,6 +816,7 @@ abstract class CustomClipper<T> {
/// same size as the RenderObject (e.g. it's a rounded rectangle /// same size as the RenderObject (e.g. it's a rounded rectangle
/// with very small arcs in the corners), then this may be adequate. /// with very small arcs in the corners), then this may be adequate.
Rect getApproximateClipRect(Size size) => Point.origin & size; Rect getApproximateClipRect(Size size) => Point.origin & size;
/// Returns `true` if the new instance will result in a different clip /// Returns `true` if the new instance will result in a different clip
/// than the oldClipper instance. /// than the oldClipper instance.
bool shouldRepaint(CustomClipper<T> oldClipper); bool shouldRepaint(CustomClipper<T> oldClipper);
...@@ -995,7 +997,10 @@ enum DecorationPosition { ...@@ -995,7 +997,10 @@ enum DecorationPosition {
/// Paints a [Decoration] either before or after its child paints. /// Paints a [Decoration] either before or after its child paints.
class RenderDecoratedBox extends RenderProxyBox { class RenderDecoratedBox extends RenderProxyBox {
/// Creates a decorated box.
///
/// Both the [decoration] and the [position] arguments are required. By
/// default the decoration paints behind the child.
RenderDecoratedBox({ RenderDecoratedBox({
Decoration decoration, Decoration decoration,
DecorationPosition position: DecorationPosition.background, DecorationPosition position: DecorationPosition.background,
...@@ -1010,6 +1015,8 @@ class RenderDecoratedBox extends RenderProxyBox { ...@@ -1010,6 +1015,8 @@ class RenderDecoratedBox extends RenderProxyBox {
BoxPainter _painter; BoxPainter _painter;
/// What decoration to paint. /// What decoration to paint.
///
/// Commonly a [BoxDecoration].
Decoration get decoration => _decoration; Decoration get decoration => _decoration;
Decoration _decoration; Decoration _decoration;
void set decoration (Decoration newDecoration) { void set decoration (Decoration newDecoration) {
...@@ -1023,7 +1030,7 @@ class RenderDecoratedBox extends RenderProxyBox { ...@@ -1023,7 +1030,7 @@ class RenderDecoratedBox extends RenderProxyBox {
markNeedsPaint(); markNeedsPaint();
} }
/// Where to paint the box decoration. /// Whether to paint the box decoration behind or in front of the child.
DecorationPosition get position => _position; DecorationPosition get position => _position;
DecorationPosition _position; DecorationPosition _position;
void set position (DecorationPosition newPosition) { void set position (DecorationPosition newPosition) {
......
...@@ -221,7 +221,7 @@ class RenderPadding extends RenderShiftedBox { ...@@ -221,7 +221,7 @@ class RenderPadding extends RenderShiftedBox {
abstract class RenderAligningShiftedBox extends RenderShiftedBox { abstract class RenderAligningShiftedBox extends RenderShiftedBox {
RenderAligningShiftedBox({ RenderAligningShiftedBox({
RenderBox child, RenderBox child,
FractionalOffset alignment: const FractionalOffset(0.5, 0.5) FractionalOffset alignment: FractionalOffset.center
}) : _alignment = alignment, }) : _alignment = alignment,
super(child) { super(child) {
assert(alignment != null && alignment.dx != null && alignment.dy != null); assert(alignment != null && alignment.dx != null && alignment.dy != null);
...@@ -277,7 +277,7 @@ abstract class RenderAligningShiftedBox extends RenderShiftedBox { ...@@ -277,7 +277,7 @@ abstract class RenderAligningShiftedBox extends RenderShiftedBox {
/// ///
/// For example, to align a box at the bottom right, you would pass this box a /// For example, to align a box at the bottom right, you would pass this box a
/// tight constraint that is bigger than the child's natural size, /// tight constraint that is bigger than the child's natural size,
/// with an alignment of [const FractionalOffset(1.0, 1.0)]. /// with an alignment of [FractionalOffset.bottomRight].
/// ///
/// By default, sizes to be as big as possible in both axes. If either axis is /// By default, sizes to be as big as possible in both axes. If either axis is
/// unconstrained, then in that direction it will be sized to fit the child's /// unconstrained, then in that direction it will be sized to fit the child's
...@@ -288,7 +288,7 @@ class RenderPositionedBox extends RenderAligningShiftedBox { ...@@ -288,7 +288,7 @@ class RenderPositionedBox extends RenderAligningShiftedBox {
RenderBox child, RenderBox child,
double widthFactor, double widthFactor,
double heightFactor, double heightFactor,
FractionalOffset alignment: const FractionalOffset(0.5, 0.5) FractionalOffset alignment: FractionalOffset.center
}) : _widthFactor = widthFactor, }) : _widthFactor = widthFactor,
_heightFactor = heightFactor, _heightFactor = heightFactor,
super(child: child, alignment: alignment) { super(child: child, alignment: alignment) {
...@@ -432,7 +432,7 @@ class RenderConstrainedOverflowBox extends RenderAligningShiftedBox { ...@@ -432,7 +432,7 @@ class RenderConstrainedOverflowBox extends RenderAligningShiftedBox {
double maxWidth, double maxWidth,
double minHeight, double minHeight,
double maxHeight, double maxHeight,
FractionalOffset alignment: const FractionalOffset(0.5, 0.5) FractionalOffset alignment: FractionalOffset.center
}) : _minWidth = minWidth, }) : _minWidth = minWidth,
_maxWidth = maxWidth, _maxWidth = maxWidth,
_minHeight = minHeight, _minHeight = minHeight,
...@@ -548,7 +548,7 @@ class RenderSizedOverflowBox extends RenderAligningShiftedBox { ...@@ -548,7 +548,7 @@ class RenderSizedOverflowBox extends RenderAligningShiftedBox {
RenderSizedOverflowBox({ RenderSizedOverflowBox({
RenderBox child, RenderBox child,
Size requestedSize, Size requestedSize,
FractionalOffset alignment: const FractionalOffset(0.5, 0.5) FractionalOffset alignment: FractionalOffset.center
}) : _requestedSize = requestedSize, }) : _requestedSize = requestedSize,
super(child: child, alignment: alignment) { super(child: child, alignment: alignment) {
assert(requestedSize != null); assert(requestedSize != null);
...@@ -620,7 +620,7 @@ class RenderFractionallySizedOverflowBox extends RenderAligningShiftedBox { ...@@ -620,7 +620,7 @@ class RenderFractionallySizedOverflowBox extends RenderAligningShiftedBox {
RenderBox child, RenderBox child,
double widthFactor, double widthFactor,
double heightFactor, double heightFactor,
FractionalOffset alignment: const FractionalOffset(0.5, 0.5) FractionalOffset alignment: FractionalOffset.center
}) : _widthFactor = widthFactor, }) : _widthFactor = widthFactor,
_heightFactor = heightFactor, _heightFactor = heightFactor,
super(child: child, alignment: alignment) { super(child: child, alignment: alignment) {
......
...@@ -133,7 +133,7 @@ class RelativeRect { ...@@ -133,7 +133,7 @@ class RelativeRect {
String toString() => "RelativeRect.fromLTRB(${left?.toStringAsFixed(1)}, ${top?.toStringAsFixed(1)}, ${right?.toStringAsFixed(1)}, ${bottom?.toStringAsFixed(1)})"; String toString() => "RelativeRect.fromLTRB(${left?.toStringAsFixed(1)}, ${top?.toStringAsFixed(1)}, ${right?.toStringAsFixed(1)}, ${bottom?.toStringAsFixed(1)})";
} }
/// Parent data for use with [RenderStack] /// Parent data for use with [RenderStack].
class StackParentData extends ContainerBoxParentDataMixin<RenderBox> { class StackParentData extends ContainerBoxParentDataMixin<RenderBox> {
/// The distance by which the child's top edge is inset from the top of the stack. /// The distance by which the child's top edge is inset from the top of the stack.
double top; double top;
...@@ -437,6 +437,10 @@ abstract class RenderStackBase extends RenderBox ...@@ -437,6 +437,10 @@ abstract class RenderStackBase extends RenderBox
/// edge of the stack. If the child extends beyond the bounds of the /// edge of the stack. If the child extends beyond the bounds of the
/// stack, the stack will clip the child's painting to the bounds of /// stack, the stack will clip the child's painting to the bounds of
/// the stack. /// the stack.
///
/// See also:
///
/// * [RenderFlow]
class RenderStack extends RenderStackBase { class RenderStack extends RenderStackBase {
RenderStack({ RenderStack({
List<RenderBox> children, List<RenderBox> children,
......
...@@ -14,7 +14,7 @@ import 'package:flutter/foundation.dart'; ...@@ -14,7 +14,7 @@ import 'package:flutter/foundation.dart';
class ImageInfo { class ImageInfo {
/// Creates an [ImageInfo] object for the given image and scale. /// Creates an [ImageInfo] object for the given image and scale.
/// ///
/// Both the image and the scale must be non-null. /// Both the image and the scale must not be null.
ImageInfo({ this.image, this.scale: 1.0 }) { ImageInfo({ this.image, this.scale: 1.0 }) {
assert(image != null); assert(image != null);
assert(scale != null); assert(scale != null);
......
...@@ -21,6 +21,8 @@ export 'package:flutter/rendering.dart' show ...@@ -21,6 +21,8 @@ export 'package:flutter/rendering.dart' show
CustomPainter, CustomPainter,
FixedColumnCountGridDelegate, FixedColumnCountGridDelegate,
FlexDirection, FlexDirection,
FlowDelegate,
FlowPaintingContext,
FractionalOffsetTween, FractionalOffsetTween,
GridDelegate, GridDelegate,
GridDelegateWithInOrderChildPlacement, GridDelegateWithInOrderChildPlacement,
...@@ -165,7 +167,7 @@ class DecoratedBox extends SingleChildRenderObjectWidget { ...@@ -165,7 +167,7 @@ class DecoratedBox extends SingleChildRenderObjectWidget {
/// Commonly a [BoxDecoration]. /// Commonly a [BoxDecoration].
final Decoration decoration; final Decoration decoration;
/// Where to paint the box decoration. /// Whether to paint the box decoration behind or in front of the child.
final DecorationPosition position; final DecorationPosition position;
@override @override
...@@ -431,16 +433,25 @@ class Padding extends SingleChildRenderObjectWidget { ...@@ -431,16 +433,25 @@ class Padding extends SingleChildRenderObjectWidget {
/// ///
/// For example, to align a box at the bottom right, you would pass this box a /// For example, to align a box at the bottom right, you would pass this box a
/// tight constraint that is bigger than the child's natural size, /// tight constraint that is bigger than the child's natural size,
/// with an alignment of [const FractionalOffset(1.0, 1.0)]. /// with an alignment of [FractionalOffset.bottomRight].
/// ///
/// By default, sizes to be as big as possible in both axes. If either axis is /// By default, sizes to be as big as possible in both axes. If either axis is
/// unconstrained, then in that direction it will be sized to fit the child's /// unconstrained, then in that direction it will be sized to fit the child's
/// dimensions. Using widthFactor and heightFactor you can force this latter /// dimensions. Using widthFactor and heightFactor you can force this latter
/// behavior in all cases. /// behavior in all cases.
///
/// See also:
///
/// * [CustomSingleChildLayout]
/// * [Center] (which is the same as [Align] but with the [alignment] always
/// set to [FractionalOffset.center])
class Align extends SingleChildRenderObjectWidget { class Align extends SingleChildRenderObjectWidget {
/// Creates an alignment widget.
///
/// The alignment defaults to [FractionalOffset.center].
Align({ Align({
Key key, Key key,
this.alignment: const FractionalOffset(0.5, 0.5), this.alignment: FractionalOffset.center,
this.widthFactor, this.widthFactor,
this.heightFactor, this.heightFactor,
Widget child Widget child
...@@ -494,6 +505,10 @@ class Align extends SingleChildRenderObjectWidget { ...@@ -494,6 +505,10 @@ class Align extends SingleChildRenderObjectWidget {
} }
/// Centers its child within itself. /// Centers its child within itself.
///
/// See also:
///
/// * [Align]
class Center extends Align { class Center extends Align {
Center({ Key key, double widthFactor, double heightFactor, Widget child }) Center({ Key key, double widthFactor, double heightFactor, Widget child })
: super(key: key, widthFactor: widthFactor, heightFactor: heightFactor, child: child); : super(key: key, widthFactor: widthFactor, heightFactor: heightFactor, child: child);
...@@ -505,7 +520,17 @@ class Center extends Align { ...@@ -505,7 +520,17 @@ class Center extends Align {
/// decide where to position the child. The delegate can also determine the size /// decide where to position the child. The delegate can also determine the size
/// of the parent, but the size of the parent cannot depend on the size of the /// of the parent, but the size of the parent cannot depend on the size of the
/// child. /// child.
///
/// See also:
///
/// * [SingleChildLayoutDelegate]
/// * [Align] (which positions a single child according to a [FractionalOffset])
/// * [CustomMultiChildLayout] (which uses a delegate to position multiple
/// children)
class CustomSingleChildLayout extends SingleChildRenderObjectWidget { class CustomSingleChildLayout extends SingleChildRenderObjectWidget {
/// Creates a custom single child layout.
///
/// The delegate argument must not be null.
CustomSingleChildLayout({ CustomSingleChildLayout({
Key key, Key key,
this.delegate, this.delegate,
...@@ -514,6 +539,7 @@ class CustomSingleChildLayout extends SingleChildRenderObjectWidget { ...@@ -514,6 +539,7 @@ class CustomSingleChildLayout extends SingleChildRenderObjectWidget {
assert(delegate != null); assert(delegate != null);
} }
/// The delegate that controls the layout of the child.
final SingleChildLayoutDelegate delegate; final SingleChildLayoutDelegate delegate;
@override @override
...@@ -527,6 +553,9 @@ class CustomSingleChildLayout extends SingleChildRenderObjectWidget { ...@@ -527,6 +553,9 @@ class CustomSingleChildLayout extends SingleChildRenderObjectWidget {
/// Metadata for identifying children in a [CustomMultiChildLayout]. /// Metadata for identifying children in a [CustomMultiChildLayout].
class LayoutId extends ParentDataWidget<CustomMultiChildLayout> { class LayoutId extends ParentDataWidget<CustomMultiChildLayout> {
/// Marks a child with a layout identifier.
///
/// Both the child and the id arguments must not be null.
LayoutId({ LayoutId({
Key key, Key key,
Widget child, Widget child,
...@@ -566,7 +595,17 @@ const List<Widget> _emptyWidgetList = const <Widget>[]; ...@@ -566,7 +595,17 @@ const List<Widget> _emptyWidgetList = const <Widget>[];
/// decide where to position each child. The delegate can also determine the /// decide where to position each child. The delegate can also determine the
/// size of the parent, but the size of the parent cannot depend on the sizes of /// size of the parent, but the size of the parent cannot depend on the sizes of
/// the children. /// the children.
///
/// See also:
///
/// * [MultiChildLayoutDelegate]
/// * [CustomSingleChildLayout]
/// * [Stack]
/// * [Flow]
class CustomMultiChildLayout extends MultiChildRenderObjectWidget { class CustomMultiChildLayout extends MultiChildRenderObjectWidget {
/// Creates a custom multi-child layout.
///
/// The delegate argument must not be null.
CustomMultiChildLayout({ CustomMultiChildLayout({
Key key, Key key,
List<Widget> children: _emptyWidgetList, List<Widget> children: _emptyWidgetList,
...@@ -667,7 +706,7 @@ class ConstrainedBox extends SingleChildRenderObjectWidget { ...@@ -667,7 +706,7 @@ class ConstrainedBox extends SingleChildRenderObjectWidget {
class FractionallySizedBox extends SingleChildRenderObjectWidget { class FractionallySizedBox extends SingleChildRenderObjectWidget {
FractionallySizedBox({ FractionallySizedBox({
Key key, Key key,
this.alignment: const FractionalOffset(0.5, 0.5), this.alignment: FractionalOffset.center,
this.width, this.width,
this.height, this.height,
Widget child Widget child
...@@ -772,7 +811,7 @@ class LimitedBox extends SingleChildRenderObjectWidget { ...@@ -772,7 +811,7 @@ class LimitedBox extends SingleChildRenderObjectWidget {
class OverflowBox extends SingleChildRenderObjectWidget { class OverflowBox extends SingleChildRenderObjectWidget {
OverflowBox({ OverflowBox({
Key key, Key key,
this.alignment: const FractionalOffset(0.5, 0.5), this.alignment: FractionalOffset.center,
this.minWidth, this.minWidth,
this.maxWidth, this.maxWidth,
this.minHeight, this.minHeight,
...@@ -844,7 +883,7 @@ class OverflowBox extends SingleChildRenderObjectWidget { ...@@ -844,7 +883,7 @@ class OverflowBox extends SingleChildRenderObjectWidget {
class SizedOverflowBox extends SingleChildRenderObjectWidget { class SizedOverflowBox extends SingleChildRenderObjectWidget {
SizedOverflowBox({ SizedOverflowBox({
Key key, Key key,
this.alignment: const FractionalOffset(0.5, 0.5), this.alignment: FractionalOffset.center,
this.size, this.size,
Widget child Widget child
}) : super(key: key, child: child) { }) : super(key: key, child: child) {
...@@ -1265,11 +1304,18 @@ abstract class StackRenderObjectWidgetBase extends MultiChildRenderObjectWidget ...@@ -1265,11 +1304,18 @@ abstract class StackRenderObjectWidgetBase extends MultiChildRenderObjectWidget
/// For more details about the stack layout algorithm, see /// For more details about the stack layout algorithm, see
/// [RenderStack]. To control the position of child widgets, see the /// [RenderStack]. To control the position of child widgets, see the
/// [Positioned] widget. /// [Positioned] widget.
///
/// See also:
///
/// * [Flow]
/// * [Align] (which positions a single child according to a [FractionalOffset])
/// * [CustomSingleChildLayout]
/// * [CustomMultiChildLayout]
class Stack extends StackRenderObjectWidgetBase { class Stack extends StackRenderObjectWidgetBase {
Stack({ Stack({
Key key, Key key,
List<Widget> children: _emptyWidgetList, List<Widget> children: _emptyWidgetList,
this.alignment: const FractionalOffset(0.0, 0.0) this.alignment: FractionalOffset.topLeft
}) : super(key: key, children: children); }) : super(key: key, children: children);
/// How to align the non-positioned children in the stack. /// How to align the non-positioned children in the stack.
...@@ -1289,7 +1335,7 @@ class IndexedStack extends StackRenderObjectWidgetBase { ...@@ -1289,7 +1335,7 @@ class IndexedStack extends StackRenderObjectWidgetBase {
IndexedStack({ IndexedStack({
Key key, Key key,
List<Widget> children: _emptyWidgetList, List<Widget> children: _emptyWidgetList,
this.alignment: const FractionalOffset(0.0, 0.0), this.alignment: FractionalOffset.topLeft,
this.index: 0 this.index: 0
}) : super(key: key, children: children) { }) : super(key: key, children: children) {
assert(index != null); assert(index != null);
...@@ -1718,6 +1764,71 @@ class Flexible extends ParentDataWidget<Flex> { ...@@ -1718,6 +1764,71 @@ class Flexible extends ParentDataWidget<Flex> {
} }
} }
/// Implements the flow layout algorithm.
///
/// Flow layouts are optimized for repositioning children using transformation
/// matrices.
///
/// The flow container is sized independently from the children by the
/// [FlowDelegate.getSize] function of the delegate. The children are then sized
/// independently given the constraints from the
/// [FlowDelegate.getConstraintsForChild] function.
///
/// Rather than positioning the children during layout, the children are
/// positioned using transformation matrices during the paint phase using the
/// matrices from the [FlowDelegate.paintChildren] function. The children can be
/// repositioned efficiently by simply repainting the flow.
///
/// The most efficient way to trigger a repaint of the flow is to supply a
/// repaint argument to the constructor of the [FlowDelegate]. The flow will
/// listen to this animation and repaint whenever the animation ticks, avoiding
/// both the build and layout phases of the pipeline.
///
/// See also:
///
/// * [FlowDelegate]
/// * [Stack]
/// * [CustomSingleChildLayout]
/// * [CustomMultiChildLayout]
class Flow extends MultiChildRenderObjectWidget {
/// Creates a flow layout.
///
/// Wraps each of the given children in a [RepaintBoundary] to avoid
/// repainting the children when the flow repaints.
Flow({
Key key,
List<Widget> children: _emptyWidgetList,
this.delegate
}) : super(key: key, children: RepaintBoundary.wrapAll(children)) {
assert(delegate != null);
}
/// Creates a flow layout.
///
/// Does not wrap the given children in repaint boundaries, unlike the default
/// constructor. Useful when the child is trivial to paint or already contains
/// a repaint boundary.
Flow.unwrapped({
Key key,
List<Widget> children: _emptyWidgetList,
this.delegate
}) : super(key: key, children: children) {
assert(delegate != null);
}
/// The delegate that controls the transformation matrices of the children.
final FlowDelegate delegate;
@override
RenderFlow createRenderObject(BuildContext context) => new RenderFlow(delegate: delegate);
@override
void updateRenderObject(BuildContext context, RenderFlow renderObject) {
renderObject
..delegate = delegate;
}
}
/// A paragraph of rich text. /// A paragraph of rich text.
/// ///
/// The [RichText] widget displays text using a variety of different styles. The /// The [RichText] widget displays text using a variety of different styles. The
......
// 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';
import 'package:test/test.dart';
class TestFlowDelegate extends FlowDelegate {
TestFlowDelegate({
Animation<double> startOffset
}) : startOffset = startOffset, super(repaint: startOffset);
final Animation<double> startOffset;
@override
BoxConstraints getConstraintsForChild(int i, BoxConstraints constraints) {
return constraints.loosen();
}
@override
void paintChildren(FlowPaintingContext context) {
double dy = startOffset.value;
for (int i = 0; i < context.childCount; ++i) {
context.paintChild(i, transform: new Matrix4.translationValues(0.0, dy, 0.0));
dy += 0.75 * context.getChildSize(i).height;
}
}
@override
bool shouldRepaint(TestFlowDelegate oldDelegate) => startOffset == oldDelegate.startOffset;
}
void main() {
test('Flow control test', () {
testWidgets((WidgetTester tester) {
AnimationController startOffset = new AnimationController.unbounded();
List<int> log = <int>[];
Widget buildBox(int i) {
return new GestureDetector(
onTap: () {
log.add(i);
},
child: new Container(
width: 100.0,
height: 100.0,
decoration: const BoxDecoration(
backgroundColor: const Color(0xFF0000FF)
),
child: new Text('$i')
)
);
}
tester.pumpWidget(
new Flow(
delegate: new TestFlowDelegate(startOffset: startOffset),
children: <Widget>[
buildBox(0),
buildBox(1),
buildBox(2),
buildBox(3),
buildBox(4),
buildBox(5),
buildBox(6),
]
)
);
tester.tap(find.text('0'));
expect(log, equals([0]));
tester.tap(find.text('1'));
expect(log, equals([0, 1]));
tester.tap(find.text('2'));
expect(log, equals([0, 1, 2]));
log.clear();
tester.tapAt(new Point(20.0, 90.0));
expect(log, equals([1]));
startOffset.value = 50.0;
tester.pump();
log.clear();
tester.tapAt(new Point(20.0, 90.0));
expect(log, equals([0]));
});
});
}
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