初始化
This commit is contained in:
@@ -0,0 +1,847 @@
|
||||
import 'dart:math' as math;
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class ShrinkWrap extends MultiChildRenderObjectWidget {
|
||||
ShrinkWrap({
|
||||
super.key,
|
||||
this.direction = Axis.horizontal,
|
||||
this.alignment = WrapAlignment.start,
|
||||
this.spacing = 0.0,
|
||||
this.runAlignment = WrapAlignment.start,
|
||||
this.runSpacing = 0.0,
|
||||
this.crossAxisAlignment = WrapCrossAlignment.start,
|
||||
this.textDirection,
|
||||
this.verticalDirection = VerticalDirection.down,
|
||||
this.clipBehavior = Clip.none,
|
||||
this.maxLines = 0,
|
||||
super.children,
|
||||
}) : assert(maxLines >= 0, 'maxLines must be >= 0');
|
||||
|
||||
final Axis direction;
|
||||
final WrapAlignment alignment;
|
||||
final double spacing;
|
||||
final WrapAlignment runAlignment;
|
||||
final double runSpacing;
|
||||
final WrapCrossAlignment crossAxisAlignment;
|
||||
final TextDirection? textDirection;
|
||||
final VerticalDirection verticalDirection;
|
||||
final Clip clipBehavior;
|
||||
|
||||
/// maximum rows when expand; when it is 0, the maximum rows is not limited;
|
||||
final int maxLines;
|
||||
|
||||
@override
|
||||
RenderObject createRenderObject(BuildContext context) {
|
||||
RenderShrinkWrap renderShrinkWrap = RenderShrinkWrap(
|
||||
direction: direction,
|
||||
alignment: alignment,
|
||||
spacing: spacing,
|
||||
runAlignment: runAlignment,
|
||||
runSpacing: runSpacing,
|
||||
crossAxisAlignment: crossAxisAlignment,
|
||||
textDirection: textDirection ?? Directionality.maybeOf(context),
|
||||
verticalDirection: verticalDirection,
|
||||
clipBehavior: clipBehavior,
|
||||
maxLines: maxLines,
|
||||
);
|
||||
return renderShrinkWrap;
|
||||
}
|
||||
|
||||
@override
|
||||
void updateRenderObject(BuildContext context, RenderShrinkWrap renderObject) {
|
||||
renderObject
|
||||
..alignment = alignment
|
||||
..spacing = spacing
|
||||
..runAlignment = runAlignment
|
||||
..runSpacing = runSpacing
|
||||
..crossAxisAlignment = crossAxisAlignment
|
||||
..textDirection = textDirection ?? Directionality.maybeOf(context)
|
||||
..verticalDirection = verticalDirection
|
||||
..clipBehavior = clipBehavior
|
||||
..maxLines = maxLines;
|
||||
}
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties.add(EnumProperty<Axis>('direction', direction));
|
||||
properties.add(EnumProperty<WrapAlignment>('alignment', alignment));
|
||||
properties.add(DoubleProperty('spacing', spacing));
|
||||
properties.add(EnumProperty<WrapAlignment>('runAlignment', runAlignment));
|
||||
properties.add(DoubleProperty('runSpacing', runSpacing));
|
||||
properties.add(EnumProperty<WrapCrossAlignment>('crossAxisAlignment', crossAxisAlignment));
|
||||
properties.add(EnumProperty<TextDirection>('textDirection', textDirection));
|
||||
properties.add(EnumProperty<VerticalDirection>('verticalDirection', verticalDirection, defaultValue: VerticalDirection.down));
|
||||
properties.add(IntProperty('maxLines', maxLines, defaultValue: 0));
|
||||
}
|
||||
}
|
||||
|
||||
class _RunMetrics {
|
||||
_RunMetrics(this.mainAxisExtent, this.crossAxisExtent, this.childCount);
|
||||
|
||||
final double mainAxisExtent;
|
||||
final double crossAxisExtent;
|
||||
final int childCount;
|
||||
}
|
||||
|
||||
/// Parent data for use with [RenderWrap].
|
||||
class ShrinkWrapParentData extends ContainerBoxParentData<RenderBox> {
|
||||
int _runIndex = 0;
|
||||
}
|
||||
|
||||
/// Displays its children in multiple horizontal or vertical runs.
|
||||
///
|
||||
/// A [RenderWrap] lays out each child and attempts to place the child adjacent
|
||||
/// to the previous child in the main axis, given by [direction], leaving
|
||||
/// [spacing] space in between. If there is not enough space to fit the child,
|
||||
/// [RenderWrap] creates a new _run_ adjacent to the existing children in the
|
||||
/// cross axis.
|
||||
///
|
||||
/// After all the children have been allocated to runs, the children within the
|
||||
/// runs are positioned according to the [alignment] in the main axis and
|
||||
/// according to the [crossAxisAlignment] in the cross axis.
|
||||
///
|
||||
/// The runs themselves are then positioned in the cross axis according to the
|
||||
/// [runSpacing] and [runAlignment].
|
||||
class RenderShrinkWrap extends RenderBox
|
||||
with ContainerRenderObjectMixin<RenderBox, ShrinkWrapParentData>, RenderBoxContainerDefaultsMixin<RenderBox, ShrinkWrapParentData> {
|
||||
/// Creates a wrap render object.
|
||||
///
|
||||
/// By default, the wrap layout is horizontal and both the children and the
|
||||
/// runs are aligned to the start.
|
||||
RenderShrinkWrap({
|
||||
List<RenderBox>? children,
|
||||
Axis direction = Axis.horizontal,
|
||||
WrapAlignment alignment = WrapAlignment.start,
|
||||
double spacing = 0.0,
|
||||
WrapAlignment runAlignment = WrapAlignment.start,
|
||||
double runSpacing = 0.0,
|
||||
WrapCrossAlignment crossAxisAlignment = WrapCrossAlignment.start,
|
||||
TextDirection? textDirection,
|
||||
VerticalDirection verticalDirection = VerticalDirection.down,
|
||||
Clip clipBehavior = Clip.none,
|
||||
int maxLines = 0,
|
||||
}) : _direction = direction,
|
||||
_alignment = alignment,
|
||||
_spacing = spacing,
|
||||
_runAlignment = runAlignment,
|
||||
_runSpacing = runSpacing,
|
||||
_crossAxisAlignment = crossAxisAlignment,
|
||||
_textDirection = textDirection,
|
||||
_verticalDirection = verticalDirection,
|
||||
_clipBehavior = clipBehavior,
|
||||
_maxLines = maxLines {
|
||||
addAll(children);
|
||||
}
|
||||
|
||||
/// The direction to use as the main axis.
|
||||
///
|
||||
/// For example, if [direction] is [Axis.horizontal], the default, the
|
||||
/// children are placed adjacent to one another in a horizontal run until the
|
||||
/// available horizontal space is consumed, at which point a subsequent
|
||||
/// children are placed in a new run vertically adjacent to the previous run.
|
||||
Axis get direction => _direction;
|
||||
Axis _direction;
|
||||
|
||||
set direction(Axis value) {
|
||||
if (_direction == value) return;
|
||||
_direction = value;
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
/// How the children within a run should be placed in the main axis.
|
||||
///
|
||||
/// For example, if [alignment] is [WrapAlignment.center], the children in
|
||||
/// each run are grouped together in the center of their run in the main axis.
|
||||
///
|
||||
/// Defaults to [WrapAlignment.start].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [runAlignment], which controls how the runs are placed relative to each
|
||||
/// other in the cross axis.
|
||||
/// * [crossAxisAlignment], which controls how the children within each run
|
||||
/// are placed relative to each other in the cross axis.
|
||||
WrapAlignment get alignment => _alignment;
|
||||
WrapAlignment _alignment;
|
||||
|
||||
set alignment(WrapAlignment value) {
|
||||
if (_alignment == value) return;
|
||||
_alignment = value;
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
/// How much space to place between children in a run in the main axis.
|
||||
///
|
||||
/// For example, if [spacing] is 10.0, the children will be spaced at least
|
||||
/// 10.0 logical pixels apart in the main axis.
|
||||
///
|
||||
/// If there is additional free space in a run (e.g., because the wrap has a
|
||||
/// minimum size that is not filled or because some runs are longer than
|
||||
/// others), the additional free space will be allocated according to the
|
||||
/// [alignment].
|
||||
///
|
||||
/// Defaults to 0.0.
|
||||
double get spacing => _spacing;
|
||||
double _spacing;
|
||||
|
||||
set spacing(double value) {
|
||||
if (_spacing == value) return;
|
||||
_spacing = value;
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
/// How the runs themselves should be placed in the cross axis.
|
||||
///
|
||||
/// For example, if [runAlignment] is [WrapAlignment.center], the runs are
|
||||
/// grouped together in the center of the overall [RenderWrap] in the cross
|
||||
/// axis.
|
||||
///
|
||||
/// Defaults to [WrapAlignment.start].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [alignment], which controls how the children within each run are placed
|
||||
/// relative to each other in the main axis.
|
||||
/// * [crossAxisAlignment], which controls how the children within each run
|
||||
/// are placed relative to each other in the cross axis.
|
||||
WrapAlignment get runAlignment => _runAlignment;
|
||||
WrapAlignment _runAlignment;
|
||||
|
||||
set runAlignment(WrapAlignment value) {
|
||||
if (_runAlignment == value) return;
|
||||
_runAlignment = value;
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
/// How much space to place between the runs themselves in the cross axis.
|
||||
///
|
||||
/// For example, if [runSpacing] is 10.0, the runs will be spaced at least
|
||||
/// 10.0 logical pixels apart in the cross axis.
|
||||
///
|
||||
/// If there is additional free space in the overall [RenderWrap] (e.g.,
|
||||
/// because the wrap has a minimum size that is not filled), the additional
|
||||
/// free space will be allocated according to the [runAlignment].
|
||||
///
|
||||
/// Defaults to 0.0.
|
||||
double get runSpacing => _runSpacing;
|
||||
double _runSpacing;
|
||||
|
||||
set runSpacing(double value) {
|
||||
if (_runSpacing == value) return;
|
||||
_runSpacing = value;
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
/// How the children within a run should be aligned relative to each other in
|
||||
/// the cross axis.
|
||||
///
|
||||
/// For example, if this is set to [WrapCrossAlignment.end], and the
|
||||
/// [direction] is [Axis.horizontal], then the children within each
|
||||
/// run will have their bottom edges aligned to the bottom edge of the run.
|
||||
///
|
||||
/// Defaults to [WrapCrossAlignment.start].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [alignment], which controls how the children within each run are placed
|
||||
/// relative to each other in the main axis.
|
||||
/// * [runAlignment], which controls how the runs are placed relative to each
|
||||
/// other in the cross axis.
|
||||
WrapCrossAlignment get crossAxisAlignment => _crossAxisAlignment;
|
||||
WrapCrossAlignment _crossAxisAlignment;
|
||||
|
||||
set crossAxisAlignment(WrapCrossAlignment value) {
|
||||
if (_crossAxisAlignment == value) return;
|
||||
_crossAxisAlignment = value;
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
/// Determines the order to lay children out horizontally and how to interpret
|
||||
/// `start` and `end` in the horizontal direction.
|
||||
///
|
||||
/// If the [direction] is [Axis.horizontal], this controls the order in which
|
||||
/// children are positioned (left-to-right or right-to-left), and the meaning
|
||||
/// of the [alignment] property's [WrapAlignment.start] and
|
||||
/// [WrapAlignment.end] values.
|
||||
///
|
||||
/// If the [direction] is [Axis.horizontal], and either the
|
||||
/// [alignment] is either [WrapAlignment.start] or [WrapAlignment.end], or
|
||||
/// there's more than one child, then the [textDirection] must not be null.
|
||||
///
|
||||
/// If the [direction] is [Axis.vertical], this controls the order in
|
||||
/// which runs are positioned, the meaning of the [runAlignment] property's
|
||||
/// [WrapAlignment.start] and [WrapAlignment.end] values, as well as the
|
||||
/// [crossAxisAlignment] property's [WrapCrossAlignment.start] and
|
||||
/// [WrapCrossAlignment.end] values.
|
||||
///
|
||||
/// If the [direction] is [Axis.vertical], and either the
|
||||
/// [runAlignment] is either [WrapAlignment.start] or [WrapAlignment.end], the
|
||||
/// [crossAxisAlignment] is either [WrapCrossAlignment.start] or
|
||||
/// [WrapCrossAlignment.end], or there's more than one child, then the
|
||||
/// [textDirection] must not be null.
|
||||
TextDirection? get textDirection => _textDirection;
|
||||
TextDirection? _textDirection;
|
||||
|
||||
set textDirection(TextDirection? value) {
|
||||
if (_textDirection == value) return;
|
||||
_textDirection = value;
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
/// Determines the order to lay children out vertically and how to interpret
|
||||
/// `start` and `end` in the vertical direction.
|
||||
///
|
||||
/// If the [direction] is [Axis.vertical], this controls which order children
|
||||
/// are painted in (down or up), the meaning of the [alignment] property's
|
||||
/// [WrapAlignment.start] and [WrapAlignment.end] values.
|
||||
///
|
||||
/// If the [direction] is [Axis.vertical], and either the [alignment]
|
||||
/// is either [WrapAlignment.start] or [WrapAlignment.end], or there's
|
||||
/// more than one child, then the [verticalDirection] must not be null.
|
||||
///
|
||||
/// If the [direction] is [Axis.horizontal], this controls the order in which
|
||||
/// runs are positioned, the meaning of the [runAlignment] property's
|
||||
/// [WrapAlignment.start] and [WrapAlignment.end] values, as well as the
|
||||
/// [crossAxisAlignment] property's [WrapCrossAlignment.start] and
|
||||
/// [WrapCrossAlignment.end] values.
|
||||
///
|
||||
/// If the [direction] is [Axis.horizontal], and either the
|
||||
/// [runAlignment] is either [WrapAlignment.start] or [WrapAlignment.end], the
|
||||
/// [crossAxisAlignment] is either [WrapCrossAlignment.start] or
|
||||
/// [WrapCrossAlignment.end], or there's more than one child, then the
|
||||
/// [verticalDirection] must not be null.
|
||||
VerticalDirection get verticalDirection => _verticalDirection;
|
||||
VerticalDirection _verticalDirection;
|
||||
|
||||
set verticalDirection(VerticalDirection value) {
|
||||
if (_verticalDirection == value) return;
|
||||
_verticalDirection = value;
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
/// {@macro flutter.material.Material.clipBehavior}
|
||||
///
|
||||
/// Defaults to [Clip.none], and must not be null.
|
||||
Clip get clipBehavior => _clipBehavior;
|
||||
Clip _clipBehavior = Clip.none;
|
||||
|
||||
set clipBehavior(Clip value) {
|
||||
if (value == _clipBehavior) return;
|
||||
_clipBehavior = value;
|
||||
markNeedsPaint();
|
||||
markNeedsSemanticsUpdate();
|
||||
}
|
||||
|
||||
/// maximum rows when expand; when it is 0, the maximum rows is not limited;
|
||||
int _maxLines;
|
||||
|
||||
int get maxLines => _maxLines;
|
||||
|
||||
set maxLines(int value) {
|
||||
if (_maxLines == value) return;
|
||||
_maxLines = value;
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
bool get _debugHasNecessaryDirections {
|
||||
if (firstChild != null && lastChild != firstChild) {
|
||||
// i.e. there's more than one child
|
||||
switch (direction) {
|
||||
case Axis.horizontal:
|
||||
assert(textDirection != null,
|
||||
'Horizontal $runtimeType with multiple children has a null textDirection, so the layout order is undefined.');
|
||||
break;
|
||||
case Axis.vertical:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (alignment == WrapAlignment.start || alignment == WrapAlignment.end) {
|
||||
switch (direction) {
|
||||
case Axis.horizontal:
|
||||
assert(textDirection != null,
|
||||
'Horizontal $runtimeType with alignment $alignment has a null textDirection, so the alignment cannot be resolved.');
|
||||
break;
|
||||
case Axis.vertical:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (runAlignment == WrapAlignment.start || runAlignment == WrapAlignment.end) {
|
||||
switch (direction) {
|
||||
case Axis.horizontal:
|
||||
break;
|
||||
case Axis.vertical:
|
||||
assert(textDirection != null,
|
||||
'Vertical $runtimeType with runAlignment $runAlignment has a null textDirection, so the alignment cannot be resolved.');
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (crossAxisAlignment == WrapCrossAlignment.start || crossAxisAlignment == WrapCrossAlignment.end) {
|
||||
switch (direction) {
|
||||
case Axis.horizontal:
|
||||
break;
|
||||
case Axis.vertical:
|
||||
assert(textDirection != null,
|
||||
'Vertical $runtimeType with crossAxisAlignment $crossAxisAlignment has a null textDirection, so the alignment cannot be resolved.');
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
void setupParentData(RenderBox child) {
|
||||
if (child.parentData is! ShrinkWrapParentData) {
|
||||
child.parentData = ShrinkWrapParentData();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
double computeMinIntrinsicWidth(double height) {
|
||||
switch (direction) {
|
||||
case Axis.horizontal:
|
||||
double width = 0.0;
|
||||
RenderBox? child = firstChild;
|
||||
while (child != null) {
|
||||
width = math.max(width, child.getMinIntrinsicWidth(double.infinity));
|
||||
child = childAfter(child);
|
||||
}
|
||||
return width;
|
||||
case Axis.vertical:
|
||||
return computeDryLayout(BoxConstraints(maxHeight: height)).width;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
double computeMaxIntrinsicWidth(double height) {
|
||||
switch (direction) {
|
||||
case Axis.horizontal:
|
||||
double width = 0.0;
|
||||
RenderBox? child = firstChild;
|
||||
while (child != null) {
|
||||
width += child.getMaxIntrinsicWidth(double.infinity);
|
||||
child = childAfter(child);
|
||||
}
|
||||
return width;
|
||||
case Axis.vertical:
|
||||
return computeDryLayout(BoxConstraints(maxHeight: height)).width;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
double computeMinIntrinsicHeight(double width) {
|
||||
switch (direction) {
|
||||
case Axis.horizontal:
|
||||
return computeDryLayout(BoxConstraints(maxWidth: width)).height;
|
||||
case Axis.vertical:
|
||||
double height = 0.0;
|
||||
RenderBox? child = firstChild;
|
||||
while (child != null) {
|
||||
height = math.max(height, child.getMinIntrinsicHeight(double.infinity));
|
||||
child = childAfter(child);
|
||||
}
|
||||
return height;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
double computeMaxIntrinsicHeight(double width) {
|
||||
switch (direction) {
|
||||
case Axis.horizontal:
|
||||
return computeDryLayout(BoxConstraints(maxWidth: width)).height;
|
||||
case Axis.vertical:
|
||||
double height = 0.0;
|
||||
RenderBox? child = firstChild;
|
||||
while (child != null) {
|
||||
height += child.getMaxIntrinsicHeight(double.infinity);
|
||||
child = childAfter(child);
|
||||
}
|
||||
return height;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
double? computeDistanceToActualBaseline(TextBaseline baseline) {
|
||||
return defaultComputeDistanceToHighestActualBaseline(baseline);
|
||||
}
|
||||
|
||||
double _getMainAxisExtent(Size childSize) {
|
||||
switch (direction) {
|
||||
case Axis.horizontal:
|
||||
return childSize.width;
|
||||
case Axis.vertical:
|
||||
return childSize.height;
|
||||
}
|
||||
}
|
||||
|
||||
double _getCrossAxisExtent(Size childSize) {
|
||||
switch (direction) {
|
||||
case Axis.horizontal:
|
||||
return childSize.height;
|
||||
case Axis.vertical:
|
||||
return childSize.width;
|
||||
}
|
||||
}
|
||||
|
||||
Offset _getOffset(double mainAxisOffset, double crossAxisOffset) {
|
||||
switch (direction) {
|
||||
case Axis.horizontal:
|
||||
return Offset(mainAxisOffset, crossAxisOffset);
|
||||
case Axis.vertical:
|
||||
return Offset(crossAxisOffset, mainAxisOffset);
|
||||
}
|
||||
}
|
||||
|
||||
double _getChildCrossAxisOffset(bool flipCrossAxis, double runCrossAxisExtent, double childCrossAxisExtent) {
|
||||
final double freeSpace = runCrossAxisExtent - childCrossAxisExtent;
|
||||
switch (crossAxisAlignment) {
|
||||
case WrapCrossAlignment.start:
|
||||
return flipCrossAxis ? freeSpace : 0.0;
|
||||
case WrapCrossAlignment.end:
|
||||
return flipCrossAxis ? 0.0 : freeSpace;
|
||||
case WrapCrossAlignment.center:
|
||||
return freeSpace / 2.0;
|
||||
}
|
||||
}
|
||||
|
||||
bool _hasVisualOverflow = false;
|
||||
|
||||
@override
|
||||
Size computeDryLayout(BoxConstraints constraints) {
|
||||
return _computeDryLayout(constraints);
|
||||
}
|
||||
|
||||
Size _computeDryLayout(BoxConstraints constraints, [ChildLayouter layoutChild = ChildLayoutHelper.dryLayoutChild]) {
|
||||
final BoxConstraints childConstraints;
|
||||
double mainAxisLimit = 0.0;
|
||||
switch (direction) {
|
||||
case Axis.horizontal:
|
||||
childConstraints = BoxConstraints(maxWidth: constraints.maxWidth);
|
||||
mainAxisLimit = constraints.maxWidth;
|
||||
break;
|
||||
case Axis.vertical:
|
||||
childConstraints = BoxConstraints(maxHeight: constraints.maxHeight);
|
||||
mainAxisLimit = constraints.maxHeight;
|
||||
break;
|
||||
}
|
||||
|
||||
double mainAxisExtent = 0.0;
|
||||
double crossAxisExtent = 0.0;
|
||||
double runMainAxisExtent = 0.0;
|
||||
double runCrossAxisExtent = 0.0;
|
||||
int childCount = 0;
|
||||
RenderBox? child = firstChild;
|
||||
int runMainIndex = 0;
|
||||
while (child != null) {
|
||||
final Size childSize = layoutChild(child, childConstraints);
|
||||
final double childMainAxisExtent = _getMainAxisExtent(childSize);
|
||||
final double childCrossAxisExtent = _getCrossAxisExtent(childSize);
|
||||
// There must be at least one child before we move on to the next run.
|
||||
if (childCount > 0 && runMainAxisExtent + childMainAxisExtent + spacing > mainAxisLimit) {
|
||||
mainAxisExtent = math.max(mainAxisExtent, runMainAxisExtent);
|
||||
crossAxisExtent += runCrossAxisExtent + runSpacing;
|
||||
runMainAxisExtent = 0.0;
|
||||
runCrossAxisExtent = 0.0;
|
||||
childCount = 0;
|
||||
if (_maxLines > 0 && ++runMainIndex > _maxLines) break;
|
||||
}
|
||||
runMainAxisExtent += childMainAxisExtent;
|
||||
runCrossAxisExtent = math.max(runCrossAxisExtent, childCrossAxisExtent);
|
||||
if (childCount > 0) runMainAxisExtent += spacing;
|
||||
childCount += 1;
|
||||
child = childAfter(child);
|
||||
}
|
||||
crossAxisExtent += runCrossAxisExtent;
|
||||
mainAxisExtent = math.max(mainAxisExtent, runMainAxisExtent);
|
||||
|
||||
switch (direction) {
|
||||
case Axis.horizontal:
|
||||
return constraints.constrain(Size(mainAxisExtent, crossAxisExtent));
|
||||
case Axis.vertical:
|
||||
return constraints.constrain(Size(crossAxisExtent, mainAxisExtent));
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取真实行数
|
||||
int _getRowCount(BoxConstraints constraints) {
|
||||
ChildLayouter layoutChild = ChildLayoutHelper.dryLayoutChild;
|
||||
final BoxConstraints childConstraints;
|
||||
double mainAxisLimit = 0.0;
|
||||
switch (direction) {
|
||||
case Axis.horizontal:
|
||||
childConstraints = BoxConstraints(maxWidth: constraints.maxWidth);
|
||||
mainAxisLimit = constraints.maxWidth;
|
||||
break;
|
||||
case Axis.vertical:
|
||||
childConstraints = BoxConstraints(maxHeight: constraints.maxHeight);
|
||||
mainAxisLimit = constraints.maxHeight;
|
||||
break;
|
||||
}
|
||||
double runMainAxisExtent = 0.0;
|
||||
double runCrossAxisExtent = 0.0;
|
||||
int childCount = 0;
|
||||
RenderBox? child = firstChild;
|
||||
int runMainCount = child != null ? 1 : 0;
|
||||
while (child != null) {
|
||||
final Size childSize = layoutChild(child, childConstraints);
|
||||
final double childMainAxisExtent = _getMainAxisExtent(childSize);
|
||||
final double childCrossAxisExtent = _getCrossAxisExtent(childSize);
|
||||
// There must be at least one child before we move on to the next run.
|
||||
if (childCount > 0 && runMainAxisExtent + childMainAxisExtent + spacing > mainAxisLimit) {
|
||||
runMainAxisExtent = 0.0;
|
||||
runCrossAxisExtent = 0.0;
|
||||
childCount = 0;
|
||||
runMainCount++;
|
||||
}
|
||||
runMainAxisExtent += childMainAxisExtent;
|
||||
runCrossAxisExtent = math.max(runCrossAxisExtent, childCrossAxisExtent);
|
||||
if (childCount > 0) runMainAxisExtent += spacing;
|
||||
childCount += 1;
|
||||
child = childAfter(child);
|
||||
}
|
||||
return runMainCount;
|
||||
}
|
||||
|
||||
/// 总行数
|
||||
int _totalRowCount = 0;
|
||||
|
||||
int get totalRowCount => _totalRowCount;
|
||||
|
||||
@override
|
||||
void performLayout() {
|
||||
final BoxConstraints constraints = this.constraints;
|
||||
assert(_debugHasNecessaryDirections);
|
||||
|
||||
_totalRowCount = _getRowCount(constraints); // 计算总行数
|
||||
|
||||
_hasVisualOverflow = false;
|
||||
RenderBox? child = firstChild;
|
||||
if (child == null) {
|
||||
size = constraints.smallest;
|
||||
return;
|
||||
}
|
||||
final BoxConstraints childConstraints;
|
||||
double mainAxisLimit = 0.0;
|
||||
bool flipMainAxis = false;
|
||||
bool flipCrossAxis = false;
|
||||
switch (direction) {
|
||||
case Axis.horizontal:
|
||||
childConstraints = BoxConstraints(maxWidth: constraints.maxWidth);
|
||||
mainAxisLimit = constraints.maxWidth;
|
||||
if (textDirection == TextDirection.rtl) flipMainAxis = true;
|
||||
if (verticalDirection == VerticalDirection.up) flipCrossAxis = true;
|
||||
break;
|
||||
case Axis.vertical:
|
||||
childConstraints = BoxConstraints(maxHeight: constraints.maxHeight);
|
||||
mainAxisLimit = constraints.maxHeight;
|
||||
if (verticalDirection == VerticalDirection.up) flipMainAxis = true;
|
||||
if (textDirection == TextDirection.rtl) flipCrossAxis = true;
|
||||
break;
|
||||
}
|
||||
final double spacing = this.spacing;
|
||||
final double runSpacing = this.runSpacing;
|
||||
final List<_RunMetrics> runMetrics = <_RunMetrics>[];
|
||||
double mainAxisExtent = 0.0;
|
||||
double crossAxisExtent = 0.0;
|
||||
double runMainAxisExtent = 0.0;
|
||||
double runCrossAxisExtent = 0.0;
|
||||
int childCount = 0;
|
||||
int runMainIndex = 1;
|
||||
while (child != null) {
|
||||
final childParentData = child.parentData! as ShrinkWrapParentData;
|
||||
if (_maxLines > 0 && runMainIndex > _maxLines) {
|
||||
child.layout(BoxConstraints.loose(Size.zero), parentUsesSize: true);
|
||||
child = childParentData.nextSibling;
|
||||
continue;
|
||||
} else {
|
||||
child.layout(childConstraints, parentUsesSize: true);
|
||||
}
|
||||
final double childMainAxisExtent = _getMainAxisExtent(child.size);
|
||||
final double childCrossAxisExtent = _getCrossAxisExtent(child.size);
|
||||
if (childCount > 0 && runMainAxisExtent + spacing + childMainAxisExtent > mainAxisLimit) {
|
||||
mainAxisExtent = math.max(mainAxisExtent, runMainAxisExtent);
|
||||
crossAxisExtent += runCrossAxisExtent;
|
||||
if (runMetrics.isNotEmpty) crossAxisExtent += runSpacing;
|
||||
runMetrics.add(_RunMetrics(runMainAxisExtent, runCrossAxisExtent, childCount));
|
||||
runMainAxisExtent = 0.0;
|
||||
runCrossAxisExtent = 0.0;
|
||||
childCount = 0;
|
||||
if (_maxLines > 0 && ++runMainIndex > _maxLines) {
|
||||
child.layout(BoxConstraints.loose(Size.zero), parentUsesSize: true);
|
||||
child = childParentData.nextSibling;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
runMainAxisExtent += childMainAxisExtent;
|
||||
if (childCount > 0) runMainAxisExtent += spacing;
|
||||
runCrossAxisExtent = math.max(runCrossAxisExtent, childCrossAxisExtent);
|
||||
childCount += 1;
|
||||
|
||||
childParentData._runIndex = runMetrics.length;
|
||||
child = childParentData.nextSibling;
|
||||
}
|
||||
if (childCount > 0) {
|
||||
mainAxisExtent = math.max(mainAxisExtent, runMainAxisExtent);
|
||||
crossAxisExtent += runCrossAxisExtent;
|
||||
if (runMetrics.isNotEmpty) crossAxisExtent += runSpacing;
|
||||
runMetrics.add(_RunMetrics(runMainAxisExtent, runCrossAxisExtent, childCount));
|
||||
}
|
||||
|
||||
final int runCount = runMetrics.length;
|
||||
assert(runCount > 0);
|
||||
|
||||
double containerMainAxisExtent = 0.0;
|
||||
double containerCrossAxisExtent = 0.0;
|
||||
|
||||
switch (direction) {
|
||||
case Axis.horizontal:
|
||||
size = constraints.constrain(Size(mainAxisExtent, crossAxisExtent));
|
||||
containerMainAxisExtent = size.width;
|
||||
containerCrossAxisExtent = size.height;
|
||||
break;
|
||||
case Axis.vertical:
|
||||
size = constraints.constrain(Size(crossAxisExtent, mainAxisExtent));
|
||||
containerMainAxisExtent = size.height;
|
||||
containerCrossAxisExtent = size.width;
|
||||
break;
|
||||
}
|
||||
|
||||
_hasVisualOverflow = containerMainAxisExtent < mainAxisExtent || containerCrossAxisExtent < crossAxisExtent;
|
||||
|
||||
final double crossAxisFreeSpace = math.max(0.0, containerCrossAxisExtent - crossAxisExtent);
|
||||
double runLeadingSpace = 0.0;
|
||||
double runBetweenSpace = 0.0;
|
||||
switch (runAlignment) {
|
||||
case WrapAlignment.start:
|
||||
break;
|
||||
case WrapAlignment.end:
|
||||
runLeadingSpace = crossAxisFreeSpace;
|
||||
break;
|
||||
case WrapAlignment.center:
|
||||
runLeadingSpace = crossAxisFreeSpace / 2.0;
|
||||
break;
|
||||
case WrapAlignment.spaceBetween:
|
||||
runBetweenSpace = runCount > 1 ? crossAxisFreeSpace / (runCount - 1) : 0.0;
|
||||
break;
|
||||
case WrapAlignment.spaceAround:
|
||||
runBetweenSpace = crossAxisFreeSpace / runCount;
|
||||
runLeadingSpace = runBetweenSpace / 2.0;
|
||||
break;
|
||||
case WrapAlignment.spaceEvenly:
|
||||
runBetweenSpace = crossAxisFreeSpace / (runCount + 1);
|
||||
runLeadingSpace = runBetweenSpace;
|
||||
break;
|
||||
}
|
||||
|
||||
runBetweenSpace += runSpacing;
|
||||
double crossAxisOffset = flipCrossAxis ? containerCrossAxisExtent - runLeadingSpace : runLeadingSpace;
|
||||
|
||||
child = firstChild;
|
||||
for (int i = 0; i < runCount; ++i) {
|
||||
final _RunMetrics metrics = runMetrics[i];
|
||||
final double runMainAxisExtent = metrics.mainAxisExtent;
|
||||
final double runCrossAxisExtent = metrics.crossAxisExtent;
|
||||
final int childCount = metrics.childCount;
|
||||
|
||||
final double mainAxisFreeSpace = math.max(0.0, containerMainAxisExtent - runMainAxisExtent);
|
||||
double childLeadingSpace = 0.0;
|
||||
double childBetweenSpace = 0.0;
|
||||
|
||||
switch (alignment) {
|
||||
case WrapAlignment.start:
|
||||
break;
|
||||
case WrapAlignment.end:
|
||||
childLeadingSpace = mainAxisFreeSpace;
|
||||
break;
|
||||
case WrapAlignment.center:
|
||||
childLeadingSpace = mainAxisFreeSpace / 2.0;
|
||||
break;
|
||||
case WrapAlignment.spaceBetween:
|
||||
childBetweenSpace = childCount > 1 ? mainAxisFreeSpace / (childCount - 1) : 0.0;
|
||||
break;
|
||||
case WrapAlignment.spaceAround:
|
||||
childBetweenSpace = mainAxisFreeSpace / childCount;
|
||||
childLeadingSpace = childBetweenSpace / 2.0;
|
||||
break;
|
||||
case WrapAlignment.spaceEvenly:
|
||||
childBetweenSpace = mainAxisFreeSpace / (childCount + 1);
|
||||
childLeadingSpace = childBetweenSpace;
|
||||
break;
|
||||
}
|
||||
|
||||
childBetweenSpace += spacing;
|
||||
double childMainPosition = flipMainAxis ? containerMainAxisExtent - childLeadingSpace : childLeadingSpace;
|
||||
|
||||
if (flipCrossAxis) crossAxisOffset -= runCrossAxisExtent;
|
||||
|
||||
while (child != null) {
|
||||
final ShrinkWrapParentData childParentData = child.parentData! as ShrinkWrapParentData;
|
||||
if (childParentData._runIndex != i) break;
|
||||
final double childMainAxisExtent = _getMainAxisExtent(child.size);
|
||||
final double childCrossAxisExtent = _getCrossAxisExtent(child.size);
|
||||
final double childCrossAxisOffset = _getChildCrossAxisOffset(flipCrossAxis, runCrossAxisExtent, childCrossAxisExtent);
|
||||
if (flipMainAxis) childMainPosition -= childMainAxisExtent;
|
||||
childParentData.offset = _getOffset(childMainPosition, crossAxisOffset + childCrossAxisOffset);
|
||||
if (flipMainAxis) {
|
||||
childMainPosition -= childBetweenSpace;
|
||||
} else {
|
||||
childMainPosition += childMainAxisExtent + childBetweenSpace;
|
||||
}
|
||||
child = childParentData.nextSibling;
|
||||
}
|
||||
|
||||
if (flipCrossAxis) {
|
||||
crossAxisOffset -= runBetweenSpace;
|
||||
} else {
|
||||
crossAxisOffset += runCrossAxisExtent + runBetweenSpace;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
|
||||
return defaultHitTestChildren(result, position: position);
|
||||
}
|
||||
|
||||
@override
|
||||
void paint(PaintingContext context, Offset offset) {
|
||||
// TODO(ianh): move the debug flex overflow paint logic somewhere common so
|
||||
// it can be reused here
|
||||
if (_hasVisualOverflow && clipBehavior != Clip.none) {
|
||||
_clipRectLayer.layer = context.pushClipRect(
|
||||
needsCompositing,
|
||||
offset,
|
||||
Offset.zero & size,
|
||||
defaultPaint,
|
||||
clipBehavior: clipBehavior,
|
||||
oldLayer: _clipRectLayer.layer,
|
||||
);
|
||||
} else {
|
||||
_clipRectLayer.layer = null;
|
||||
defaultPaint(context, offset);
|
||||
}
|
||||
}
|
||||
|
||||
final LayerHandle<ClipRectLayer> _clipRectLayer = LayerHandle<ClipRectLayer>();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_clipRectLayer.layer = null;
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties.add(EnumProperty<Axis>('direction', direction));
|
||||
properties.add(EnumProperty<WrapAlignment>('alignment', alignment));
|
||||
properties.add(DoubleProperty('spacing', spacing));
|
||||
properties.add(EnumProperty<WrapAlignment>('runAlignment', runAlignment));
|
||||
properties.add(DoubleProperty('runSpacing', runSpacing));
|
||||
properties.add(DoubleProperty('crossAxisAlignment', runSpacing));
|
||||
properties.add(EnumProperty<TextDirection>('textDirection', textDirection));
|
||||
properties.add(EnumProperty<VerticalDirection>('verticalDirection', verticalDirection, defaultValue: VerticalDirection.down));
|
||||
properties.add(IntProperty('maxLines', maxLines, defaultValue: 0));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user