Commit 8ce2f859 authored by Adam Barth's avatar Adam Barth

Merge pull request #3516 from abarth/tweaks

Add Flow layout
parents 9d900ea7 f53a5a52
......@@ -30,6 +30,7 @@ export 'src/rendering/debug.dart';
export 'src/rendering/editable_line.dart';
export 'src/rendering/error.dart';
export 'src/rendering/flex.dart';
export 'src/rendering/flow.dart';
export 'src/rendering/grid.dart';
export 'src/rendering/image.dart';
export 'src/rendering/layer.dart';
......
......@@ -51,7 +51,7 @@ class SnackBarAction extends StatefulWidget {
/// The button 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
/// displayed in a [SnackBar].
......
......@@ -1049,7 +1049,7 @@ abstract class RenderBoxContainerDefaultsMixin<ChildType extends RenderBox, Pare
/// appear in the child list.
double defaultComputeDistanceToFirstActualBaseline(TextBaseline baseline) {
assert(!needsLayout);
RenderBox child = firstChild;
ChildType child = firstChild;
while (child != null) {
final ParentDataType childParentData = child.parentData;
double result = child.getDistanceToActualBaseline(baseline);
......@@ -1067,7 +1067,7 @@ abstract class RenderBoxContainerDefaultsMixin<ChildType extends RenderBox, Pare
double defaultComputeDistanceToHighestActualBaseline(TextBaseline baseline) {
assert(!needsLayout);
double result;
RenderBox child = firstChild;
ChildType child = firstChild;
while (child != null) {
final ParentDataType childParentData = child.parentData;
double candidate = child.getDistanceToActualBaseline(baseline);
......@@ -1103,13 +1103,24 @@ abstract class RenderBoxContainerDefaultsMixin<ChildType extends RenderBox, Pare
/// Paints each child by walking the child list forwards.
void defaultPaint(PaintingContext context, Offset offset) {
RenderBox child = firstChild;
ChildType child = firstChild;
while (child != null) {
final ParentDataType childParentData = child.parentData;
context.paintChild(child, childParentData.offset + offset);
child = childParentData.nextSibling;
}
}
List<ChildType> getChildrenAsList() {
List<ChildType> result = <ChildType>[];
RenderBox child = firstChild;
while (child != null) {
final ParentDataType childParentData = child.parentData;
result.add(child);
child = childParentData.nextSibling;
}
return result;
}
}
class FractionalOffsetTween extends Tween<FractionalOffset> {
......
......@@ -7,7 +7,7 @@ import 'dart:math' as math;
import 'box.dart';
import 'object.dart';
/// Parent data for use with [RenderFlex]
/// Parent data for use with [RenderFlex].
class FlexParentData extends ContainerBoxParentDataMixin<RenderBox> {
/// The flex factor to use for this child
///
......
// Copyright 2016 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/animation.dart';
import 'package:vector_math/vector_math_64.dart';
import 'box.dart';
import 'object.dart';
/// A context in which a [FlowDelegate] paints.
///
/// Provides information about the current size of the container and the
/// children and a mechanism for painting children.
///
/// See also:
///
/// * [FlowDelegate]
/// * [Flow]
/// * [RenderFlow]
abstract class FlowPaintingContext {
/// The size of the container in which the children can be painted.
Size get size;
/// The number of children available to paint.
int get childCount;
/// The size of the [i]th child.
///
/// If [i] is negative or exceeds [childCount], returns null.
Size getChildSize(int i);
/// Paint the [i]th child using the given transform.
///
/// The child will be painted in a coordinate system that concatenates the
/// container's coordinate system with the given transform. The origin of the
/// parent's coordinate system is the upper left corner of the parent, with
/// x increasing rightward and y increasing downward.
///
/// The container will clip the children to its bounds.
void paintChild(int i, { Matrix4 transform, double opacity: 1.0 });
}
/// A delegate that controls the appearance of a flow layout.
///
/// Flow layouts are optimized for moving children around the screen using
/// transformation matrices. For optimal performance, construct the
/// [FlowDelegate] with an [Animation] that ticks whenever the delegate wishes
/// to change the transformation matrices for the children and avoid rebuilding
/// the [Flow] widget itself every animation frame.
///
/// See also:
///
/// * [Flow]
/// * [RenderFlow]
abstract class FlowDelegate {
/// The flow will repaint whenever the [repaint] animation ticks.
const FlowDelegate({ Animation<dynamic> repaint }) : _repaint = repaint;
final Animation<dynamic> _repaint;
/// Override to control the size of the container for the children.
///
/// By default, the flow will be as large as possible. If this function
/// returns a size that does not respect the given constraints, the size will
/// be adjusted to be as close to the returned size as possible while still
/// respecting the constraints.
///
/// If this function depends on information other than the given constraints,
/// override [shouldRelayout] to indicate when when the container should
/// relayout.
Size getSize(BoxConstraints constraints) => constraints.biggest;
/// Override to control the layout constraints given to each child.
///
/// By default, the children will receive the given constraints, which are the
/// constrains the constraints used to size the container. The children need
/// not respect the given constraints, but they are required to respect the
/// returned constraints. For example, the incoming constraings might require
/// the container to have a width of exactly 100.0 and a height of exactly
/// 100.0, but this function might give the children looser constraints that
/// let them be larger or smaller than 100.0 by 100.0.
///
/// If this function depends on information other than the given constraints,
/// override [shouldRelayout] to indicate when when the container should
/// relayout.
BoxConstraints getConstraintsForChild(int i, BoxConstraints constraints) => constraints;
/// Override to paint the children of the flow.
///
/// Children can be painted in any order, but each child can be painted at
/// most once. Although the container clips the children to its own bounds, it
/// is more efficient to skip painting a child altogether rather than having
/// it paint entirely outside the container's clip.
///
/// To paint a child, call [FlowPaintingContext.paintChild] on the given
/// [context]. The given context is valid only within the scope of this
/// function call and contains information (such as the size of the container)
/// that is useful for picking transformation matrices for the children.
///
/// If this function depends on information other than the given context,
/// override [shouldRepaint] to indicate when when the container should
/// relayout.
void paintChildren(FlowPaintingContext context);
/// Override this method to return true when the children need to be laid out.
/// This should compare the fields of the current delegate and the given
/// oldDelegate and return true if the fields are such that the layout would
/// be different.
bool shouldRelayout(FlowDelegate oldDelegate) => false;
/// Override this method to return true when the children need to be
/// repainted. This should compare the fields of the current delegate and the
/// given oldDelegate and return true if the fields are such that
/// paintChildren would act differently.
///
/// The delegate can also trigger a repaint if the delegate provides the
/// repaint animation argument to this object's constructor and that animation
/// ticks. Triggering a repaint using this animation-based mechanism is more
/// efficient than rebuilding the [Flow] widget to change its delegate.
///
/// The flow container might repaint even if this function returns false, for
/// example if layout triggers painting (e.g., if [shouldRelayout] returns
/// true).
bool shouldRepaint(FlowDelegate oldDelegate);
/// Override this method to include additional information in the
/// debugging data printed by [debugDumpRenderTree] and friends.
///
/// By default, returns the [runtimeType] of the class.
@override
String toString() => '$runtimeType';
}
int _getAlphaFromOpacity(double opacity) => (opacity * 255).round();
/// Parent data for use with [RenderFlow].
///
/// The [offset] property is ignored by [RenderFlow] and is always set to
/// [Offset.zero]. Children of a [RenderFlow] are positioned using a
/// transformation matrix, which is private to the [RenderFlow]. To set the
/// matrix, use the [FlowPaintingContext.paintChild] function from an override
/// of the [FlowDelegate.paintChildren] function.
class FlowParentData extends ContainerBoxParentDataMixin<RenderBox> {
Matrix4 _transform;
}
/// 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]
/// * [RenderStack]
class RenderFlow extends RenderBox
with ContainerRenderObjectMixin<RenderBox, FlowParentData>,
RenderBoxContainerDefaultsMixin<RenderBox, FlowParentData>
implements FlowPaintingContext {
/// Creates a render object for a flow layout.
///
/// For optimal performance, consider using children that return true from
/// [isRepaintBounday].
RenderFlow({
List<RenderBox> children,
FlowDelegate delegate
}) : _delegate = delegate {
assert(delegate != null);
addAll(children);
}
@override
void setupParentData(RenderBox child) {
final ParentData childParentData = child.parentData;
if (childParentData is FlowParentData)
childParentData._transform = null;
else
child.parentData = new FlowParentData();
}
/// The delegate that controls the transformation matrices of the children.
FlowDelegate get delegate => _delegate;
FlowDelegate _delegate;
/// When the delegate is changed to a new delegate with the same runtimeType
/// as the old delegate, this object will call the delegate's
/// [FlowDelegate.shouldRelayout] and [FlowDelegate.shouldRepaint] functions
/// to determine whether the new delegate requires this object to update its
/// layout or painting.
void set delegate (FlowDelegate newDelegate) {
assert(newDelegate != null);
if (_delegate == newDelegate)
return;
final FlowDelegate oldDelegate = _delegate;
_delegate = newDelegate;
if (newDelegate.runtimeType != oldDelegate.runtimeType || newDelegate.shouldRelayout(oldDelegate))
markNeedsLayout();
else if (newDelegate.shouldRepaint(oldDelegate))
markNeedsPaint();
if (attached) {
oldDelegate._repaint?.removeListener(markNeedsPaint);
newDelegate._repaint?.addListener(markNeedsPaint);
}
}
@override
void attach(PipelineOwner owner) {
super.attach(owner);
_delegate._repaint?.addListener(markNeedsPaint);
}
@override
void detach() {
_delegate._repaint?.removeListener(markNeedsPaint);
super.detach();
}
Size _getSize(BoxConstraints constraints) {
assert(constraints.debugAssertIsValid());
return constraints.constrain(_delegate.getSize(constraints));
}
@override
bool get isRepaintBoundary => true;
@override
double getMinIntrinsicWidth(BoxConstraints constraints) {
return _getSize(constraints).width;
}
@override
double getMaxIntrinsicWidth(BoxConstraints constraints) {
return _getSize(constraints).width;
}
@override
double getMinIntrinsicHeight(BoxConstraints constraints) {
return _getSize(constraints).height;
}
@override
double getMaxIntrinsicHeight(BoxConstraints constraints) {
return _getSize(constraints).height;
}
@override
void performLayout() {
size = _getSize(constraints);
int i = 0;
_randomAccessChildren.clear();
RenderBox child = firstChild;
while (child != null) {
_randomAccessChildren.add(child);
BoxConstraints innerConstraints = _delegate.getConstraintsForChild(i, constraints);
child.layout(innerConstraints, parentUsesSize: true);
final FlowParentData childParentData = child.parentData;
childParentData.offset = Offset.zero;
child = childParentData.nextSibling;
i += 1;
}
}
// Updated during layout. Only valid if layout is not dirty.
final List<RenderBox> _randomAccessChildren = <RenderBox>[];
// Updated during paint.
final List<int> _lastPaintOrder = <int>[];
// Only valid during paint.
PaintingContext _paintingContext;
Offset _paintingOffset;
@override
Size getChildSize(int i) {
if (i < 0 || i >= _randomAccessChildren.length)
return null;
return _randomAccessChildren[i].size;
}
@override
void paintChild(int i, { Matrix4 transform, double opacity: 1.0 }) {
transform ??= new Matrix4.identity();
RenderBox child = _randomAccessChildren[i];
final FlowParentData childParentData = child.parentData;
assert(() {
if (childParentData._transform != null) {
throw new FlutterError(
'Cannot call paintChild twice for the same child.\n'
'The flow delegate of type ${_delegate.runtimeType} attempted to '
'paint child $i multiple times, which is not permitted.'
);
}
return true;
});
_lastPaintOrder.add(i);
childParentData._transform = transform;
// We return after assigning _transform so that the transparent child can
// still be hit tested at the correct location.
if (opacity == 0.0)
return;
void painter(PaintingContext context, Offset offset) {
context.paintChild(child, offset);
};
if (opacity == 1.0) {
_paintingContext.pushTransform(needsCompositing, _paintingOffset, transform, painter);
} else {
_paintingContext.pushOpacity(_paintingOffset, _getAlphaFromOpacity(opacity), (PaintingContext context, Offset offset) {
_paintingContext.pushTransform(needsCompositing, offset, transform, painter);
});
}
}
void _paintWithDelegate(PaintingContext context, Offset offset) {
_lastPaintOrder.clear();
_paintingContext = context;
_paintingOffset = offset;
for (RenderBox child in _randomAccessChildren) {
final FlowParentData childParentData = child.parentData;
childParentData._transform = null;
}
try {
_delegate.paintChildren(this);
} finally {
_paintingContext = null;
_paintingOffset = null;
}
}
@override
void paint(PaintingContext context, Offset offset) {
context.pushClipRect(needsCompositing, offset, Point.origin & size, _paintWithDelegate);
}
@override
bool hitTestChildren(HitTestResult result, { Point position }) {
final List<RenderBox> children = getChildrenAsList();
for (int i = _lastPaintOrder.length - 1; i >= 0; --i) {
final int childIndex = _lastPaintOrder[i];
if (childIndex >= children.length)
continue;
final RenderBox child = children[childIndex];
final FlowParentData childParentData = child.parentData;
final Matrix4 transform = childParentData._transform;
if (transform == null)
continue;
Matrix4 inverse = new Matrix4.zero();
double determinate = inverse.copyInverse(transform);
if (determinate == 0.0) {
// We cannot invert the transform. That means the child doesn't appear
// on screen and cannot be hit.
continue;
}
final Vector3 position3 = new Vector3(position.x, position.y, 0.0);
final Vector3 transformed3 = inverse.transform3(position3);
Point childPosition = new Point(transformed3.x, transformed3.y);
if (child.hitTest(result, position: childPosition))
return true;
}
return false;
}
@override
void applyPaintTransform(RenderBox child, Matrix4 transform) {
final FlowParentData childParentData = child.parentData;
if (childParentData._transform != null)
transform.multiply(childParentData._transform);
super.applyPaintTransform(child, transform);
}
}
......@@ -806,6 +806,7 @@ abstract class CustomClipper<T> {
/// Returns a description of the clip given that the render object being
/// clipped is of the given size.
T getClip(Size size);
/// Returns an approximation of the clip returned by [getClip], as
/// an axis-aligned Rect. This is used by the semantics layer to
/// determine whether widgets should be excluded.
......@@ -815,6 +816,7 @@ abstract class CustomClipper<T> {
/// same size as the RenderObject (e.g. it's a rounded rectangle
/// with very small arcs in the corners), then this may be adequate.
Rect getApproximateClipRect(Size size) => Point.origin & size;
/// Returns `true` if the new instance will result in a different clip
/// than the oldClipper instance.
bool shouldRepaint(CustomClipper<T> oldClipper);
......@@ -995,7 +997,10 @@ enum DecorationPosition {
/// Paints a [Decoration] either before or after its child paints.
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({
Decoration decoration,
DecorationPosition position: DecorationPosition.background,
......@@ -1010,6 +1015,8 @@ class RenderDecoratedBox extends RenderProxyBox {
BoxPainter _painter;
/// What decoration to paint.
///
/// Commonly a [BoxDecoration].
Decoration get decoration => _decoration;
Decoration _decoration;
void set decoration (Decoration newDecoration) {
......@@ -1023,7 +1030,7 @@ class RenderDecoratedBox extends RenderProxyBox {
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 _position;
void set position (DecorationPosition newPosition) {
......@@ -1118,7 +1125,7 @@ class RenderTransform extends RenderProxyBox {
/// The alignment of the origin, relative to the size of the box.
///
/// This is equivalent to setting an origin based on the size of the box.
/// If it is specificed at the same time as an offset, both are applied.
/// If it is specified at the same time as an offset, both are applied.
FractionalOffset get alignment => _alignment;
FractionalOffset _alignment;
void set alignment (FractionalOffset newAlignment) {
......
......@@ -221,7 +221,7 @@ class RenderPadding extends RenderShiftedBox {
abstract class RenderAligningShiftedBox extends RenderShiftedBox {
RenderAligningShiftedBox({
RenderBox child,
FractionalOffset alignment: const FractionalOffset(0.5, 0.5)
FractionalOffset alignment: FractionalOffset.center
}) : _alignment = alignment,
super(child) {
assert(alignment != null && alignment.dx != null && alignment.dy != null);
......@@ -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
/// 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
/// unconstrained, then in that direction it will be sized to fit the child's
......@@ -288,7 +288,7 @@ class RenderPositionedBox extends RenderAligningShiftedBox {
RenderBox child,
double widthFactor,
double heightFactor,
FractionalOffset alignment: const FractionalOffset(0.5, 0.5)
FractionalOffset alignment: FractionalOffset.center
}) : _widthFactor = widthFactor,
_heightFactor = heightFactor,
super(child: child, alignment: alignment) {
......@@ -432,7 +432,7 @@ class RenderConstrainedOverflowBox extends RenderAligningShiftedBox {
double maxWidth,
double minHeight,
double maxHeight,
FractionalOffset alignment: const FractionalOffset(0.5, 0.5)
FractionalOffset alignment: FractionalOffset.center
}) : _minWidth = minWidth,
_maxWidth = maxWidth,
_minHeight = minHeight,
......@@ -548,7 +548,7 @@ class RenderSizedOverflowBox extends RenderAligningShiftedBox {
RenderSizedOverflowBox({
RenderBox child,
Size requestedSize,
FractionalOffset alignment: const FractionalOffset(0.5, 0.5)
FractionalOffset alignment: FractionalOffset.center
}) : _requestedSize = requestedSize,
super(child: child, alignment: alignment) {
assert(requestedSize != null);
......@@ -620,7 +620,7 @@ class RenderFractionallySizedOverflowBox extends RenderAligningShiftedBox {
RenderBox child,
double widthFactor,
double heightFactor,
FractionalOffset alignment: const FractionalOffset(0.5, 0.5)
FractionalOffset alignment: FractionalOffset.center
}) : _widthFactor = widthFactor,
_heightFactor = heightFactor,
super(child: child, alignment: alignment) {
......
......@@ -133,7 +133,7 @@ class RelativeRect {
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> {
/// The distance by which the child's top edge is inset from the top of the stack.
double top;
......@@ -437,6 +437,10 @@ abstract class RenderStackBase extends RenderBox
/// 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
/// the stack.
///
/// See also:
///
/// * [RenderFlow]
class RenderStack extends RenderStackBase {
RenderStack({
List<RenderBox> children,
......
......@@ -14,7 +14,7 @@ import 'package:flutter/foundation.dart';
class ImageInfo {
/// 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 }) {
assert(image != null);
assert(scale != null);
......
......@@ -21,6 +21,8 @@ export 'package:flutter/rendering.dart' show
CustomPainter,
FixedColumnCountGridDelegate,
FlexDirection,
FlowDelegate,
FlowPaintingContext,
FractionalOffsetTween,
GridDelegate,
GridDelegateWithInOrderChildPlacement,
......@@ -165,7 +167,7 @@ class DecoratedBox extends SingleChildRenderObjectWidget {
/// Commonly a [BoxDecoration].
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;
@override
......@@ -431,16 +433,25 @@ class Padding extends SingleChildRenderObjectWidget {
///
/// 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,
/// 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
/// unconstrained, then in that direction it will be sized to fit the child's
/// dimensions. Using widthFactor and heightFactor you can force this latter
/// 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 {
/// Creates an alignment widget.
///
/// The alignment defaults to [FractionalOffset.center].
Align({
Key key,
this.alignment: const FractionalOffset(0.5, 0.5),
this.alignment: FractionalOffset.center,
this.widthFactor,
this.heightFactor,
Widget child
......@@ -494,6 +505,10 @@ class Align extends SingleChildRenderObjectWidget {
}
/// Centers its child within itself.
///
/// See also:
///
/// * [Align]
class Center extends Align {
Center({ Key key, double widthFactor, double heightFactor, Widget child })
: super(key: key, widthFactor: widthFactor, heightFactor: heightFactor, child: child);
......@@ -505,7 +520,17 @@ class Center extends Align {
/// 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
/// 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 {
/// Creates a custom single child layout.
///
/// The delegate argument must not be null.
CustomSingleChildLayout({
Key key,
this.delegate,
......@@ -514,6 +539,7 @@ class CustomSingleChildLayout extends SingleChildRenderObjectWidget {
assert(delegate != null);
}
/// The delegate that controls the layout of the child.
final SingleChildLayoutDelegate delegate;
@override
......@@ -527,6 +553,9 @@ class CustomSingleChildLayout extends SingleChildRenderObjectWidget {
/// Metadata for identifying children in a [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({
Key key,
Widget child,
......@@ -566,7 +595,17 @@ const List<Widget> _emptyWidgetList = const <Widget>[];
/// 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
/// the children.
///
/// See also:
///
/// * [MultiChildLayoutDelegate]
/// * [CustomSingleChildLayout]
/// * [Stack]
/// * [Flow]
class CustomMultiChildLayout extends MultiChildRenderObjectWidget {
/// Creates a custom multi-child layout.
///
/// The delegate argument must not be null.
CustomMultiChildLayout({
Key key,
List<Widget> children: _emptyWidgetList,
......@@ -667,7 +706,7 @@ class ConstrainedBox extends SingleChildRenderObjectWidget {
class FractionallySizedBox extends SingleChildRenderObjectWidget {
FractionallySizedBox({
Key key,
this.alignment: const FractionalOffset(0.5, 0.5),
this.alignment: FractionalOffset.center,
this.width,
this.height,
Widget child
......@@ -772,7 +811,7 @@ class LimitedBox extends SingleChildRenderObjectWidget {
class OverflowBox extends SingleChildRenderObjectWidget {
OverflowBox({
Key key,
this.alignment: const FractionalOffset(0.5, 0.5),
this.alignment: FractionalOffset.center,
this.minWidth,
this.maxWidth,
this.minHeight,
......@@ -844,7 +883,7 @@ class OverflowBox extends SingleChildRenderObjectWidget {
class SizedOverflowBox extends SingleChildRenderObjectWidget {
SizedOverflowBox({
Key key,
this.alignment: const FractionalOffset(0.5, 0.5),
this.alignment: FractionalOffset.center,
this.size,
Widget child
}) : super(key: key, child: child) {
......@@ -1265,11 +1304,18 @@ abstract class StackRenderObjectWidgetBase extends MultiChildRenderObjectWidget
/// For more details about the stack layout algorithm, see
/// [RenderStack]. To control the position of child widgets, see the
/// [Positioned] widget.
///
/// See also:
///
/// * [Flow]
/// * [Align] (which positions a single child according to a [FractionalOffset])
/// * [CustomSingleChildLayout]
/// * [CustomMultiChildLayout]
class Stack extends StackRenderObjectWidgetBase {
Stack({
Key key,
List<Widget> children: _emptyWidgetList,
this.alignment: const FractionalOffset(0.0, 0.0)
this.alignment: FractionalOffset.topLeft
}) : super(key: key, children: children);
/// How to align the non-positioned children in the stack.
......@@ -1289,7 +1335,7 @@ class IndexedStack extends StackRenderObjectWidgetBase {
IndexedStack({
Key key,
List<Widget> children: _emptyWidgetList,
this.alignment: const FractionalOffset(0.0, 0.0),
this.alignment: FractionalOffset.topLeft,
this.index: 0
}) : super(key: key, children: children) {
assert(index != null);
......@@ -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.
///
/// The [RichText] widget displays text that uses multiple different styles. The
......@@ -2571,6 +2682,13 @@ class RepaintBoundary extends SingleChildRenderObjectWidget {
return new RepaintBoundary(key: key, child: child);
}
static List<RepaintBoundary> wrapAll(List<Widget> widgets) {
List<RepaintBoundary> result = new List<RepaintBoundary>(widgets.length);
for (int i = 0; i < result.length; ++i)
result[i] = new RepaintBoundary.wrap(widgets[i], i);
return result;
}
@override
RenderRepaintBoundary createRenderObject(BuildContext context) => new RenderRepaintBoundary();
}
......
// 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