mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
This commit is contained in:
parent
02402392f1
commit
ea3d4dbd6e
@ -85,7 +85,8 @@ class _OverflowRegionData {
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [RenderUnconstrainedBox] and [RenderFlex] for examples of classes that use this indicator mixin.
|
||||
/// * [RenderConstraintsTransformBox], [RenderUnconstrainedBox] and
|
||||
/// [RenderFlex], for examples of classes that use this indicator mixin.
|
||||
mixin DebugOverflowIndicatorMixin on RenderObject {
|
||||
static const Color _black = Color(0xBF000000);
|
||||
static const Color _yellow = Color(0xBFFFFF00);
|
||||
|
@ -10,10 +10,17 @@ import 'box.dart';
|
||||
import 'debug.dart';
|
||||
import 'debug_overflow_indicator.dart';
|
||||
import 'layer.dart';
|
||||
import 'layout_helper.dart';
|
||||
import 'object.dart';
|
||||
import 'stack.dart' show RelativeRect;
|
||||
|
||||
/// Signature for a function that transforms a [BoxConstraints] to another
|
||||
/// [BoxConstraints].
|
||||
///
|
||||
/// Used by [RenderConstraintsTransformBox] and [ConstraintsTransformBox].
|
||||
/// Typically the caller requires the returned [BoxConstraints] to be
|
||||
/// [BoxConstraints.isNormalized].
|
||||
typedef BoxConstraintsTransform = BoxConstraints Function(BoxConstraints);
|
||||
|
||||
/// Abstract class for one-child-layout render boxes that provide control over
|
||||
/// the child's position.
|
||||
abstract class RenderShiftedBox extends RenderBox with RenderObjectWithChildMixin<RenderBox> {
|
||||
@ -628,71 +635,83 @@ class RenderConstrainedOverflowBox extends RenderAligningShiftedBox {
|
||||
}
|
||||
}
|
||||
|
||||
/// Renders a box, imposing no constraints on its child, allowing the child to
|
||||
/// render at its "natural" size.
|
||||
/// A [RenderBox] that applies an arbitrary transform to its [constraints]
|
||||
/// before sizing its child using the new constraints, treating any overflow as
|
||||
/// error.
|
||||
///
|
||||
/// This allows a child to render at the size it would render if it were alone
|
||||
/// on an infinite canvas with no constraints. This box will then attempt to
|
||||
/// adopt the same size, within the limits of its own constraints. If it ends
|
||||
/// up with a different size, it will align the child based on [alignment].
|
||||
/// If the box cannot expand enough to accommodate the entire child, the
|
||||
/// child will be clipped.
|
||||
/// This [RenderBox] sizes its child using a [BoxConstraints] created by
|
||||
/// applying [constraintsTransform] to this [RenderBox]'s own [constraints].
|
||||
/// This box will then attempt to adopt the same size, within the limits of its
|
||||
/// own constraints. If it ends up with a different size, it will align the
|
||||
/// child based on [alignment]. If the box cannot expand enough to accommodate
|
||||
/// the entire child, the child will be clipped if [clipBehavior] is not
|
||||
/// [Clip.none].
|
||||
///
|
||||
/// In debug mode, if the child overflows the box, a warning will be printed on
|
||||
/// the console, and black and yellow striped areas will appear where the
|
||||
/// overflow occurs.
|
||||
///
|
||||
/// When [child] is null, this [RenderBox] takes the smallest possible size and
|
||||
/// never overflows.
|
||||
///
|
||||
/// This [RenderBox] can be used to ensure some of [child]'s natrual dimensions
|
||||
/// are honored, and get an early warning during development otherwise. For
|
||||
/// instance, if [child] requires a minimum height to fully display its content,
|
||||
/// [constraintsTransform] can be set to a function that removes the `maxHeight`
|
||||
/// constraint from the incoming [BoxConstraints], so that if the parent
|
||||
/// [RenderObject] fails to provide enough vertical space, a warning will be
|
||||
/// displayed in debug mode, while still allowing [child] to grow vertically.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [ConstraintsTransformBox], the widget that makes use of this
|
||||
/// [RenderObject] and exposes the same functionality.
|
||||
/// * [RenderConstrainedBox], which renders a box which imposes constraints
|
||||
/// on its child.
|
||||
/// * [RenderConstrainedOverflowBox], which renders a box that imposes different
|
||||
/// constraints on its child than it gets from its parent, possibly allowing
|
||||
/// the child to overflow the parent.
|
||||
/// * [RenderSizedOverflowBox], a render object that is a specific size but
|
||||
/// passes its original constraints through to its child, which it allows to
|
||||
/// overflow.
|
||||
class RenderUnconstrainedBox extends RenderAligningShiftedBox with DebugOverflowIndicatorMixin {
|
||||
/// Create a render object that sizes itself to the child but does not
|
||||
/// pass the [constraints] down to that child.
|
||||
/// * [RenderUnconstrainedBox] which allows its children to render themselves
|
||||
/// unconstrained, expands to fit them, and considers overflow to be an error.
|
||||
class RenderConstraintsTransformBox extends RenderAligningShiftedBox with DebugOverflowIndicatorMixin {
|
||||
/// Creates a [RenderBox] that sizes itself to the child and modifies the
|
||||
/// [constraints] before passing it down to that child.
|
||||
///
|
||||
/// The [alignment] must not be null.
|
||||
RenderUnconstrainedBox({
|
||||
/// The [alignment] and [clipBehavior] must not be null.
|
||||
RenderConstraintsTransformBox({
|
||||
required AlignmentGeometry alignment,
|
||||
required TextDirection? textDirection,
|
||||
Axis? constrainedAxis,
|
||||
required BoxConstraintsTransform constraintsTransform,
|
||||
RenderBox? child,
|
||||
Clip clipBehavior = Clip.none,
|
||||
}) : assert(alignment != null),
|
||||
assert(clipBehavior != null),
|
||||
_constrainedAxis = constrainedAxis,
|
||||
assert(constraintsTransform != null),
|
||||
_constraintsTransform = constraintsTransform,
|
||||
_clipBehavior = clipBehavior,
|
||||
super.mixin(alignment, textDirection, child);
|
||||
|
||||
/// The axis to retain constraints on, if any.
|
||||
///
|
||||
/// If not set, or set to null (the default), neither axis will retain its
|
||||
/// constraints. If set to [Axis.vertical], then vertical constraints will
|
||||
/// be retained, and if set to [Axis.horizontal], then horizontal constraints
|
||||
/// will be retained.
|
||||
Axis? get constrainedAxis => _constrainedAxis;
|
||||
Axis? _constrainedAxis;
|
||||
set constrainedAxis(Axis? value) {
|
||||
if (_constrainedAxis == value)
|
||||
/// {@macro flutter.widgets.constraintsTransform}
|
||||
BoxConstraintsTransform get constraintsTransform => _constraintsTransform;
|
||||
BoxConstraintsTransform _constraintsTransform;
|
||||
set constraintsTransform(BoxConstraintsTransform value) {
|
||||
if (_constraintsTransform == value)
|
||||
return;
|
||||
_constrainedAxis = value;
|
||||
markNeedsLayout();
|
||||
_constraintsTransform = value;
|
||||
// The RenderObject only needs layout if the new transform maps the current
|
||||
// `constraints` to a different value, or the render object has never been
|
||||
// laid out before.
|
||||
final bool needsLayout = _childConstraints == null
|
||||
|| _childConstraints != value(constraints);
|
||||
if (needsLayout)
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
Rect _overflowContainerRect = Rect.zero;
|
||||
Rect _overflowChildRect = Rect.zero;
|
||||
bool _isOverflowing = false;
|
||||
|
||||
/// {@macro flutter.material.Material.clipBehavior}
|
||||
///
|
||||
/// Defaults to [Clip.none], and must not be null.
|
||||
Clip get clipBehavior => _clipBehavior;
|
||||
Clip _clipBehavior = Clip.none;
|
||||
Clip _clipBehavior;
|
||||
set clipBehavior(Clip value) {
|
||||
assert(value != null);
|
||||
if (value != _clipBehavior) {
|
||||
@ -702,49 +721,61 @@ class RenderUnconstrainedBox extends RenderAligningShiftedBox with DebugOverflow
|
||||
}
|
||||
}
|
||||
|
||||
Size _calculateSizeWithChild({required BoxConstraints constraints, required ChildLayouter layoutChild}) {
|
||||
assert(child != null);
|
||||
// Let the child lay itself out at it's "natural" size, but if
|
||||
// constrainedAxis is non-null, keep any constraints on that axis.
|
||||
final BoxConstraints childConstraints;
|
||||
if (constrainedAxis != null) {
|
||||
switch (constrainedAxis!) {
|
||||
case Axis.horizontal:
|
||||
childConstraints = BoxConstraints(maxWidth: constraints.maxWidth, minWidth: constraints.minWidth);
|
||||
break;
|
||||
case Axis.vertical:
|
||||
childConstraints = BoxConstraints(maxHeight: constraints.maxHeight, minHeight: constraints.minHeight);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
childConstraints = const BoxConstraints();
|
||||
}
|
||||
return constraints.constrain(layoutChild(child!, childConstraints));
|
||||
}
|
||||
|
||||
@override
|
||||
Size computeDryLayout(BoxConstraints constraints) {
|
||||
if (child == null) {
|
||||
return constraints.smallest;
|
||||
}
|
||||
return _calculateSizeWithChild(
|
||||
constraints: constraints,
|
||||
layoutChild: ChildLayoutHelper.dryLayoutChild,
|
||||
double computeMinIntrinsicHeight(double width) {
|
||||
return super.computeMinIntrinsicHeight(
|
||||
constraintsTransform(BoxConstraints(maxWidth: width)).maxWidth,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
double computeMaxIntrinsicHeight(double width) {
|
||||
return super.computeMaxIntrinsicHeight(
|
||||
constraintsTransform(BoxConstraints(maxWidth: width)).maxWidth,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
double computeMinIntrinsicWidth(double height) {
|
||||
return super.computeMinIntrinsicWidth(
|
||||
constraintsTransform(BoxConstraints(maxHeight: height)).maxHeight,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
double computeMaxIntrinsicWidth(double height) {
|
||||
return super.computeMaxIntrinsicWidth(
|
||||
constraintsTransform(BoxConstraints(maxHeight: height)).maxHeight,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Size computeDryLayout(BoxConstraints constraints) {
|
||||
final Size? childSize = child?.getDryLayout(constraintsTransform(constraints));
|
||||
return childSize == null ? constraints.smallest : constraints.constrain(childSize);
|
||||
}
|
||||
|
||||
Rect _overflowContainerRect = Rect.zero;
|
||||
Rect _overflowChildRect = Rect.zero;
|
||||
bool _isOverflowing = false;
|
||||
|
||||
BoxConstraints? _childConstraints;
|
||||
|
||||
@override
|
||||
void performLayout() {
|
||||
final BoxConstraints constraints = this.constraints;
|
||||
final RenderBox? child = this.child;
|
||||
if (child != null) {
|
||||
size = _calculateSizeWithChild(
|
||||
constraints: constraints,
|
||||
layoutChild: ChildLayoutHelper.layoutChild,
|
||||
);
|
||||
final BoxConstraints childConstraints = constraintsTransform(constraints);
|
||||
assert(childConstraints != null);
|
||||
assert(childConstraints.isNormalized, '$childConstraints is not normalized');
|
||||
_childConstraints = childConstraints;
|
||||
child.layout(childConstraints, parentUsesSize: true);
|
||||
size = constraints.constrain(child.size);
|
||||
alignChild();
|
||||
final BoxParentData childParentData = child!.parentData! as BoxParentData;
|
||||
final BoxParentData childParentData = child.parentData! as BoxParentData;
|
||||
_overflowContainerRect = Offset.zero & size;
|
||||
_overflowChildRect = childParentData.offset & child!.size;
|
||||
_overflowChildRect = childParentData.offset & child.size;
|
||||
} else {
|
||||
size = constraints.smallest;
|
||||
_overflowContainerRect = Rect.zero;
|
||||
@ -797,6 +828,94 @@ class RenderUnconstrainedBox extends RenderAligningShiftedBox with DebugOverflow
|
||||
}
|
||||
}
|
||||
|
||||
/// Renders a box, imposing no constraints on its child, allowing the child to
|
||||
/// render at its "natural" size.
|
||||
///
|
||||
/// The class is deprecated, use [RenderConstraintsTransformBox] instead.
|
||||
///
|
||||
/// This allows a child to render at the size it would render if it were alone
|
||||
/// on an infinite canvas with no constraints. This box will then attempt to
|
||||
/// adopt the same size, within the limits of its own constraints. If it ends
|
||||
/// up with a different size, it will align the child based on [alignment].
|
||||
/// If the box cannot expand enough to accommodate the entire child, the
|
||||
/// child will be clipped.
|
||||
///
|
||||
/// In debug mode, if the child overflows the box, a warning will be printed on
|
||||
/// the console, and black and yellow striped areas will appear where the
|
||||
/// overflow occurs.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [RenderConstrainedBox], which renders a box which imposes constraints
|
||||
/// on its child.
|
||||
/// * [RenderConstrainedOverflowBox], which renders a box that imposes different
|
||||
/// constraints on its child than it gets from its parent, possibly allowing
|
||||
/// the child to overflow the parent.
|
||||
/// * [RenderSizedOverflowBox], a render object that is a specific size but
|
||||
/// passes its original constraints through to its child, which it allows to
|
||||
/// overflow.
|
||||
///
|
||||
@Deprecated(
|
||||
'Use RenderConstraintsTransformBox instead. '
|
||||
'This feature was deprecated after v2.1.0-11.0.pre.'
|
||||
)
|
||||
class RenderUnconstrainedBox extends RenderConstraintsTransformBox {
|
||||
/// Create a render object that sizes itself to the child but does not
|
||||
/// pass the [constraints] down to that child.
|
||||
///
|
||||
/// The [alignment] and [clipBehavior] must not be null.
|
||||
@Deprecated(
|
||||
'Use RenderConstraintsTransformBox instead. '
|
||||
'This feature was deprecated after v2.1.0-11.0.pre.'
|
||||
)
|
||||
RenderUnconstrainedBox({
|
||||
required AlignmentGeometry alignment,
|
||||
required TextDirection? textDirection,
|
||||
Axis? constrainedAxis,
|
||||
RenderBox? child,
|
||||
Clip clipBehavior = Clip.none,
|
||||
}) : assert(alignment != null),
|
||||
assert(clipBehavior != null),
|
||||
_constrainedAxis = constrainedAxis,
|
||||
super(
|
||||
alignment: alignment,
|
||||
textDirection: textDirection,
|
||||
child: child,
|
||||
clipBehavior: clipBehavior,
|
||||
constraintsTransform: _convertAxis(constrainedAxis),
|
||||
);
|
||||
|
||||
/// The axis to retain constraints on, if any.
|
||||
///
|
||||
/// If not set, or set to null (the default), neither axis will retain its
|
||||
/// constraints. If set to [Axis.vertical], then vertical constraints will
|
||||
/// be retained, and if set to [Axis.horizontal], then horizontal constraints
|
||||
/// will be retained.
|
||||
Axis? get constrainedAxis => _constrainedAxis;
|
||||
Axis? _constrainedAxis;
|
||||
set constrainedAxis(Axis? value) {
|
||||
if (_constrainedAxis == value)
|
||||
return;
|
||||
_constrainedAxis = value;
|
||||
constraintsTransform = _convertAxis(constrainedAxis);
|
||||
}
|
||||
|
||||
static BoxConstraints _unconstrained(BoxConstraints constraints) => const BoxConstraints();
|
||||
static BoxConstraints _widthConstrained(BoxConstraints constraints) => constraints.widthConstraints();
|
||||
static BoxConstraints _heightConstrained(BoxConstraints constraints) => constraints.heightConstraints();
|
||||
static BoxConstraintsTransform _convertAxis(Axis? constrainedAxis) {
|
||||
if (constrainedAxis == null) {
|
||||
return _unconstrained;
|
||||
}
|
||||
switch (constrainedAxis) {
|
||||
case Axis.horizontal:
|
||||
return _widthConstrained;
|
||||
case Axis.vertical:
|
||||
return _heightConstrained;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A render object that is a specific size but passes its original constraints
|
||||
/// through to its child, which it allows to overflow.
|
||||
///
|
||||
|
@ -28,6 +28,7 @@ export 'package:flutter/rendering.dart' show
|
||||
AlignmentGeometryTween,
|
||||
Axis,
|
||||
BoxConstraints,
|
||||
BoxConstraintsTransform,
|
||||
CrossAxisAlignment,
|
||||
CustomClipper,
|
||||
CustomPainter,
|
||||
@ -2317,6 +2318,221 @@ class ConstrainedBox extends SingleChildRenderObjectWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// A container widget that applies an arbitrary transform to its constraints,
|
||||
/// and sizes its child using the resulting [BoxConstraints], treating any
|
||||
/// overflow as error.
|
||||
///
|
||||
/// This container sizes its child using a [BoxConstraints] created by applying
|
||||
/// [constraintsTransform] to its own constraints. This container will then
|
||||
/// attempt to adopt the same size, within the limits of its own constraints. If
|
||||
/// it ends up with a different size, it will align the child based on
|
||||
/// [alignment]. If the container cannot expand enough to accommodate the entire
|
||||
/// child, the child will be clipped if [clipBehavior] is not [Clip.none].
|
||||
///
|
||||
/// In debug mode, if the child overflows the container, a warning will be
|
||||
/// printed on the console, and black and yellow striped areas will appear where
|
||||
/// the overflow occurs.
|
||||
///
|
||||
/// When [child] is null, this widget becomes as small as possible and never
|
||||
/// overflows
|
||||
///
|
||||
/// This widget can be used to ensure some of [child]'s natrual dimensions are
|
||||
/// honored, and get an early warning otherwise during development. For
|
||||
/// instance, if [child] requires a minimum height to fully display its content,
|
||||
/// [constraintsTransform] can be set to [maxHeightUnconstrained], so that if
|
||||
/// the parent [RenderObject] fails to provide enough vertical space, a warning
|
||||
/// will be displayed in debug mode, while still allowing [child] to grow
|
||||
/// vertically:
|
||||
///
|
||||
/// {@tool snippet}
|
||||
/// In the following snippet, the [Card] is guaranteed to be at least as tall as
|
||||
/// its "natrual" height. Unlike [UnconstrainedBox], it will become taller if
|
||||
/// its "natrual" height is smaller than 40 px. If the [Container] isn't high
|
||||
/// enough to show the full content of the [Card], in debug mode a warning will
|
||||
/// be given.
|
||||
///
|
||||
/// ```dart
|
||||
/// Container(
|
||||
/// constraints: const BoxConstraints(minHeight: 40, maxHeight: 100),
|
||||
/// alignment: Alignment.center,
|
||||
/// child: const ConstraintsTransformBox(
|
||||
/// constraintsTransform: ConstraintsTransformBox.maxHeightUnconstrained,
|
||||
/// child: Card(child: Text('Hello World!')),
|
||||
/// )
|
||||
/// )
|
||||
/// ```
|
||||
/// {@end-tool}
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [ConstrainedBox], which renders a box which imposes constraints
|
||||
/// on its child.
|
||||
/// * [OverflowBox], a widget that imposes additional constraints on its child,
|
||||
/// and allows the child to overflow itself.
|
||||
/// * [UnconstrainedBox] which allows its children to render themselves
|
||||
/// unconstrained, expands to fit them, and considers overflow to be an error.
|
||||
class ConstraintsTransformBox extends SingleChildRenderObjectWidget {
|
||||
/// Creates a widget that uses a function to transform the constraints it
|
||||
/// passes to its child. If the child overflows the parent's constraints, a
|
||||
/// warning will be given in debug mode.
|
||||
///
|
||||
/// The `debugTransformType` argument adds a debug label to this widget.
|
||||
///
|
||||
/// The `alignment`, `clipBehavior` and `constraintsTransform` arguments must
|
||||
/// not be null.
|
||||
const ConstraintsTransformBox({
|
||||
Key? key,
|
||||
Widget? child,
|
||||
this.textDirection,
|
||||
this.alignment = Alignment.center,
|
||||
required this.constraintsTransform,
|
||||
this.clipBehavior = Clip.none,
|
||||
String debugTransformType = '',
|
||||
}) : _debugTransformLabel = debugTransformType,
|
||||
assert(alignment != null),
|
||||
assert(clipBehavior != null),
|
||||
assert(constraintsTransform != null),
|
||||
assert(debugTransformType != null),
|
||||
super(key: key, child: child);
|
||||
|
||||
/// A [BoxConstraintsTransform] that always returns its argument as-is (i.e.,
|
||||
/// it is an identity function).
|
||||
///
|
||||
/// The [ConstraintsTransformBox] becomes a proxy widget that has no effect on
|
||||
/// layout if [constraintsTransform] is set to this.
|
||||
static BoxConstraints unmodified(BoxConstraints constraints) => constraints;
|
||||
|
||||
/// A [BoxConstraintsTransform] that always returns a [BoxConstraints] that
|
||||
/// imposes no constraints on either dimension (i.e. `const BoxConstraints()`).
|
||||
///
|
||||
/// Setting [constraintsTransform] to this allows [child] to render at its
|
||||
/// "natural" size (equivalent to an [UnconstrainedBox] with `constrainedAxis`
|
||||
/// set to null).
|
||||
static BoxConstraints unconstrained(BoxConstraints constraints) => const BoxConstraints();
|
||||
|
||||
/// A [BoxConstraintsTransform] that removes the width constraints from the
|
||||
/// input.
|
||||
///
|
||||
/// Setting [constraintsTransform] to this allows [child] to render at its
|
||||
/// "natural" width (equivalent to an [UnconstrainedBox] with
|
||||
/// `constrainedAxis` set to [Axis.horizontal]).
|
||||
static BoxConstraints widthUnconstrained(BoxConstraints constraints) => constraints.heightConstraints();
|
||||
|
||||
/// A [BoxConstraintsTransform] that removes the height constraints from the
|
||||
/// input.
|
||||
///
|
||||
/// Setting [constraintsTransform] to this allows [child] to render at its
|
||||
/// "natural" height (equivalent to an [UnconstrainedBox] with
|
||||
/// `constrainedAxis` set to [Axis.vertical]).
|
||||
static BoxConstraints heightUnconstrained(BoxConstraints constraints) => constraints.widthConstraints();
|
||||
|
||||
/// A [BoxConstraintsTransform] that removes the `maxHeight` constraint from
|
||||
/// the input.
|
||||
///
|
||||
/// Setting [constraintsTransform] to this allows [child] to render at its
|
||||
/// "natural" height or the `minHeight` of the incoming [BoxConstraints],
|
||||
/// whichever is larger.
|
||||
static BoxConstraints maxHeightUnconstrained(BoxConstraints constraints) => constraints.copyWith(maxHeight: double.infinity);
|
||||
|
||||
/// A [BoxConstraintsTransform] that removes the `maxWidth` constraint from
|
||||
/// the input.
|
||||
///
|
||||
/// Setting [constraintsTransform] to this allows [child] to render at its
|
||||
/// "natural" width or the `minWidth` of the incoming [BoxConstraints],
|
||||
/// whichever is larger.
|
||||
static BoxConstraints maxWidthUnconstrained(BoxConstraints constraints) => constraints.copyWith(maxWidth: double.infinity);
|
||||
|
||||
/// A [BoxConstraintsTransform] that removes both the `maxWidth` and the
|
||||
/// `maxHeight` constraints from the input.
|
||||
///
|
||||
/// Setting [constraintsTransform] to this allows [child] to render at least
|
||||
/// its "natural" size, and grow along an axis if the incoming
|
||||
/// [BoxConstraints] has a larger minimum constraint on that axis.
|
||||
static BoxConstraints maxUnconstrained(BoxConstraints constraints) => constraints.copyWith(maxWidth: double.infinity, maxHeight: double.infinity);
|
||||
|
||||
static final Map<BoxConstraintsTransform, String> _debugKnownTransforms = <BoxConstraintsTransform, String>{
|
||||
unmodified: 'unmodified',
|
||||
unconstrained: 'unconstrained',
|
||||
widthUnconstrained: 'width constraints removed',
|
||||
heightUnconstrained: 'height constraints removed',
|
||||
maxWidthUnconstrained: 'maxWidth constraint removed',
|
||||
maxHeightUnconstrained: 'maxHeight constraint removed',
|
||||
maxUnconstrained: 'maxWidth & maxHeight constraints removed',
|
||||
};
|
||||
|
||||
/// The text direction to use when interpreting the [alignment] if it is an
|
||||
/// [AlignmentDirectional].
|
||||
///
|
||||
/// Defaults to null, in which case [Directionality.maybeOf] is used to determine
|
||||
/// the text direction.
|
||||
final TextDirection? textDirection;
|
||||
|
||||
/// The alignment to use when laying out the child, if it has a different size
|
||||
/// than this widget.
|
||||
///
|
||||
/// If this is an [AlignmentDirectional], then [textDirection] must not be
|
||||
/// null.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [Alignment] for non-[Directionality]-aware alignments.
|
||||
/// * [AlignmentDirectional] for [Directionality]-aware alignments.
|
||||
final AlignmentGeometry alignment;
|
||||
|
||||
/// {@template flutter.widgets.constraintsTransform}
|
||||
/// The function used to transform the incoming [BoxConstraints], to size
|
||||
/// [child].
|
||||
///
|
||||
/// The function must return a [BoxConstraints] that is
|
||||
/// [BoxConstraints.isNormalized].
|
||||
///
|
||||
/// See [ConstraintsTransformBox] for predefined common
|
||||
/// [BoxConstraintsTransform]s.
|
||||
/// {@endtemplate}
|
||||
final BoxConstraintsTransform constraintsTransform;
|
||||
|
||||
/// {@macro flutter.material.Material.clipBehavior}
|
||||
///
|
||||
/// Defaults to [Clip.none].
|
||||
final Clip clipBehavior;
|
||||
|
||||
final String _debugTransformLabel;
|
||||
|
||||
@override
|
||||
RenderConstraintsTransformBox createRenderObject(BuildContext context) {
|
||||
return RenderConstraintsTransformBox(
|
||||
textDirection: textDirection ?? Directionality.maybeOf(context),
|
||||
alignment: alignment,
|
||||
constraintsTransform: constraintsTransform,
|
||||
clipBehavior: clipBehavior,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void updateRenderObject(BuildContext context, covariant RenderConstraintsTransformBox renderObject) {
|
||||
renderObject
|
||||
..textDirection = textDirection ?? Directionality.maybeOf(context)
|
||||
..constraintsTransform = constraintsTransform
|
||||
..alignment = alignment
|
||||
..clipBehavior = clipBehavior;
|
||||
}
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment));
|
||||
properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
|
||||
|
||||
final String? debugTransformLabel = _debugTransformLabel.isNotEmpty
|
||||
? _debugTransformLabel
|
||||
: _debugKnownTransforms[constraintsTransform];
|
||||
|
||||
if (debugTransformLabel != null) {
|
||||
properties.add(DiagnosticsProperty<String>('constraints transform', debugTransformLabel));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A widget that imposes no constraints on its child, allowing it to render
|
||||
/// at its "natural" size.
|
||||
///
|
||||
@ -2341,20 +2557,23 @@ class ConstrainedBox extends SingleChildRenderObjectWidget {
|
||||
/// * [OverflowBox], a widget that imposes different constraints on its child
|
||||
/// than it gets from its parent, possibly allowing the child to overflow
|
||||
/// the parent.
|
||||
class UnconstrainedBox extends SingleChildRenderObjectWidget {
|
||||
/// * [ConstraintsTransformBox], a widget that sizes its child using a
|
||||
/// transformed [BoxConstraints], and shows a warning if the child overflows
|
||||
/// in debug mode.
|
||||
class UnconstrainedBox extends StatelessWidget {
|
||||
/// Creates a widget that imposes no constraints on its child, allowing it to
|
||||
/// render at its "natural" size. If the child overflows the parents
|
||||
/// constraints, a warning will be given in debug mode.
|
||||
const UnconstrainedBox({
|
||||
Key? key,
|
||||
Widget? child,
|
||||
this.child,
|
||||
this.textDirection,
|
||||
this.alignment = Alignment.center,
|
||||
this.constrainedAxis,
|
||||
this.clipBehavior = Clip.none,
|
||||
}) : assert(alignment != null),
|
||||
assert(clipBehavior != null),
|
||||
super(key: key, child: child);
|
||||
super(key: key);
|
||||
|
||||
/// The text direction to use when interpreting the [alignment] if it is an
|
||||
/// [AlignmentDirectional].
|
||||
@ -2384,22 +2603,34 @@ class UnconstrainedBox extends SingleChildRenderObjectWidget {
|
||||
/// Defaults to [Clip.none].
|
||||
final Clip clipBehavior;
|
||||
|
||||
@override
|
||||
void updateRenderObject(BuildContext context, covariant RenderUnconstrainedBox renderObject) {
|
||||
renderObject
|
||||
..textDirection = textDirection ?? Directionality.maybeOf(context)
|
||||
..alignment = alignment
|
||||
..constrainedAxis = constrainedAxis
|
||||
..clipBehavior = clipBehavior;
|
||||
/// The widget below this widget in the tree.
|
||||
///
|
||||
/// {@macro flutter.widgets.ProxyWidget.child}
|
||||
final Widget? child;
|
||||
|
||||
BoxConstraintsTransform _axisToTransform(Axis? constrainedAxis) {
|
||||
if (constrainedAxis != null) {
|
||||
switch (constrainedAxis) {
|
||||
case Axis.horizontal:
|
||||
return ConstraintsTransformBox.heightUnconstrained;
|
||||
case Axis.vertical:
|
||||
return ConstraintsTransformBox.widthUnconstrained;
|
||||
}
|
||||
} else {
|
||||
return ConstraintsTransformBox.unconstrained;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
RenderUnconstrainedBox createRenderObject(BuildContext context) => RenderUnconstrainedBox(
|
||||
textDirection: textDirection ?? Directionality.maybeOf(context),
|
||||
alignment: alignment,
|
||||
constrainedAxis: constrainedAxis,
|
||||
clipBehavior: clipBehavior,
|
||||
);
|
||||
Widget build(BuildContext context) {
|
||||
return ConstraintsTransformBox(
|
||||
child: child,
|
||||
textDirection: textDirection,
|
||||
alignment: alignment,
|
||||
clipBehavior: clipBehavior,
|
||||
constraintsTransform: _axisToTransform(constrainedAxis),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
|
@ -378,6 +378,71 @@ void main() {
|
||||
expect(unconstrained.getMaxIntrinsicWidth(100.0), equals(200.0));
|
||||
});
|
||||
|
||||
group('ConstraintsTransfromBox', () {
|
||||
FlutterErrorDetails? firstErrorDetails;
|
||||
void exhaustErrors() {
|
||||
FlutterErrorDetails? next;
|
||||
do {
|
||||
next = renderer.takeFlutterErrorDetails();
|
||||
firstErrorDetails ??= next;
|
||||
} while (next != null);
|
||||
}
|
||||
|
||||
tearDown(() {
|
||||
firstErrorDetails = null;
|
||||
RenderObject.debugCheckingIntrinsics = false;
|
||||
});
|
||||
|
||||
test('throws if the resulting constraints are not normalized', () {
|
||||
final RenderConstrainedBox child = RenderConstrainedBox(additionalConstraints: const BoxConstraints.tightFor(height: 0));
|
||||
final RenderConstraintsTransformBox box = RenderConstraintsTransformBox(
|
||||
alignment: Alignment.center,
|
||||
textDirection: TextDirection.ltr,
|
||||
constraintsTransform: (BoxConstraints constraints) => const BoxConstraints(maxHeight: -1, minHeight: 200),
|
||||
child: child,
|
||||
);
|
||||
|
||||
layout(box, constraints: const BoxConstraints(), onErrors: exhaustErrors);
|
||||
|
||||
expect(firstErrorDetails?.toString(), contains('is not normalized'));
|
||||
});
|
||||
|
||||
test('overflow is reported when insufficient size is given', () {
|
||||
final RenderConstrainedBox child = RenderConstrainedBox(additionalConstraints: const BoxConstraints.tightFor(width: double.maxFinite));
|
||||
final RenderConstraintsTransformBox box = RenderConstraintsTransformBox(
|
||||
alignment: Alignment.center,
|
||||
textDirection: TextDirection.ltr,
|
||||
constraintsTransform: (BoxConstraints constraints) => constraints.copyWith(maxWidth: double.infinity),
|
||||
child: child,
|
||||
);
|
||||
|
||||
layout(box, constraints: const BoxConstraints(), phase: EnginePhase.composite, onErrors: expectOverflowedErrors);
|
||||
});
|
||||
|
||||
test('handles flow layout', () {
|
||||
final RenderParagraph child = RenderParagraph(
|
||||
TextSpan(text: 'a' * 100),
|
||||
textDirection: TextDirection.ltr,
|
||||
);
|
||||
final RenderConstraintsTransformBox box = RenderConstraintsTransformBox(
|
||||
alignment: Alignment.center,
|
||||
textDirection: TextDirection.ltr,
|
||||
constraintsTransform: (BoxConstraints constraints) => constraints.copyWith(maxWidth: double.infinity),
|
||||
child: child,
|
||||
);
|
||||
|
||||
// With a width of 30, the RenderParagraph would have wrapped, but the
|
||||
// RenderConstraintsTransformBox allows the paragraph to expand regardless
|
||||
// of the width constraint:
|
||||
// unconstrainedHeight * numberOfLines = constrainedHeight.
|
||||
final double constrainedHeight = child.getMinIntrinsicHeight(30);
|
||||
final double unconstrainedHeight = box.getMinIntrinsicHeight(30);
|
||||
|
||||
// At least 2 lines.
|
||||
expect(constrainedHeight, greaterThanOrEqualTo(2 * unconstrainedHeight));
|
||||
});
|
||||
});
|
||||
|
||||
test ('getMinIntrinsicWidth error handling', () {
|
||||
final RenderUnconstrainedBox unconstrained = RenderUnconstrainedBox(
|
||||
textDirection: TextDirection.ltr,
|
||||
|
@ -36,7 +36,7 @@ void main() {
|
||||
final dynamic exception = tester.takeException();
|
||||
expect(exception, isFlutterError);
|
||||
expect(exception.diagnostics.first.level, DiagnosticLevel.summary);
|
||||
expect(exception.diagnostics.first.toString(), startsWith('A RenderUnconstrainedBox overflowed by '));
|
||||
expect(exception.diagnostics.first.toString(), startsWith('A RenderConstraintsTransformBox overflowed by '));
|
||||
expect(find.byType(UnconstrainedBox), paints..rect());
|
||||
|
||||
await tester.pumpWidget(
|
||||
|
@ -323,6 +323,7 @@ void main() {
|
||||
const UnconstrainedBox(constrainedAxis: Axis.vertical,).toString(),
|
||||
equals('UnconstrainedBox(alignment: Alignment.center, constrainedAxis: vertical)'),
|
||||
);
|
||||
|
||||
expect(
|
||||
const UnconstrainedBox(constrainedAxis: Axis.horizontal, textDirection: TextDirection.rtl, alignment: Alignment.topRight).toString(),
|
||||
equals('UnconstrainedBox(alignment: Alignment.topRight, constrainedAxis: horizontal, textDirection: rtl)'),
|
||||
@ -331,13 +332,32 @@ void main() {
|
||||
|
||||
testWidgets('UnconstrainedBox can set and update clipBehavior', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(const UnconstrainedBox());
|
||||
final RenderUnconstrainedBox renderObject = tester.allRenderObjects.whereType<RenderUnconstrainedBox>().first;
|
||||
final RenderConstraintsTransformBox renderObject = tester.allRenderObjects.whereType<RenderConstraintsTransformBox>().first;
|
||||
expect(renderObject.clipBehavior, equals(Clip.none));
|
||||
|
||||
await tester.pumpWidget(const UnconstrainedBox(clipBehavior: Clip.antiAlias));
|
||||
expect(renderObject.clipBehavior, equals(Clip.antiAlias));
|
||||
});
|
||||
|
||||
group('ConstraintsTransformBox', () {
|
||||
test('toString', () {
|
||||
expect(
|
||||
const ConstraintsTransformBox(
|
||||
constraintsTransform: ConstraintsTransformBox.unconstrained,
|
||||
).toString(),
|
||||
equals('ConstraintsTransformBox(alignment: Alignment.center, constraints transform: unconstrained)'),
|
||||
);
|
||||
expect(
|
||||
const ConstraintsTransformBox(
|
||||
textDirection: TextDirection.rtl,
|
||||
alignment: Alignment.topRight,
|
||||
constraintsTransform: ConstraintsTransformBox.widthUnconstrained,
|
||||
).toString(),
|
||||
equals('ConstraintsTransformBox(alignment: Alignment.topRight, textDirection: rtl, constraints transform: width constraints removed)'),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('ColoredBox', () {
|
||||
late _MockCanvas mockCanvas;
|
||||
late _MockPaintingContext mockContext;
|
||||
|
Loading…
Reference in New Issue
Block a user