Add clipBehavior to widgets with clipRect (#55977)
* Add clipBehavior to RenderFlex
* Add clipBehavior to FittedBox
* Add clipBehavior to Flex and FittedBox
* Add clipBehavior to UnconstrainedBox
* Add clipBehavior to Stack and Wrap
* Add clipBehavior to TextEditable
* Add clipBehavior to ListWheelScrollView
* Add clipBehavior to SingleChildScrollView
* Add clipBehavior to RenderViewportBase's widgets
Those widgets are NestedScrollView and ShrinkWrappingViewport.
* Fix tests
* Remove enum Overflow and fix typo
* Remove clipToSize
* Analyze fix
* Remove Mixin and other small fixes
* Fix tests and respect Stack's default clipBehavior
* Add Overflow back to make it non-breaking
* Restore clipBehavior to make it non-breaking
* Small fixes
* Fix rebase
diff --git a/dev/integration_tests/flutter_gallery/lib/demo/pesto_demo.dart b/dev/integration_tests/flutter_gallery/lib/demo/pesto_demo.dart
index 328dce7..bac6263 100644
--- a/dev/integration_tests/flutter_gallery/lib/demo/pesto_demo.dart
+++ b/dev/integration_tests/flutter_gallery/lib/demo/pesto_demo.dart
@@ -224,7 +224,7 @@
child: SizedBox(
width: kLogoWidth,
child: Stack(
- overflow: Overflow.visible,
+ clipBehavior: Clip.none,
children: <Widget>[
Positioned.fromRect(
rect: _imageRectTween.lerp(widget.t),
diff --git a/packages/flutter/lib/src/rendering/editable.dart b/packages/flutter/lib/src/rendering/editable.dart
index 0f31f6a..4d5f46b 100644
--- a/packages/flutter/lib/src/rendering/editable.dart
+++ b/packages/flutter/lib/src/rendering/editable.dart
@@ -227,6 +227,7 @@
EdgeInsets floatingCursorAddedMargin = const EdgeInsets.fromLTRB(4, 4, 4, 5),
TextRange promptRectRange,
Color promptRectColor,
+ Clip clipBehavior = Clip.hardEdge,
@required this.textSelectionDelegate,
}) : assert(textAlign != null),
assert(textDirection != null, 'RenderEditable created without a textDirection.'),
@@ -257,6 +258,7 @@
assert(devicePixelRatio != null),
assert(selectionHeightStyle != null),
assert(selectionWidthStyle != null),
+ assert(clipBehavior != null),
_textPainter = TextPainter(
text: text,
textAlign: textAlign,
@@ -290,7 +292,8 @@
_obscureText = obscureText,
_readOnly = readOnly,
_forceLine = forceLine,
- _promptRectRange = promptRectRange {
+ _promptRectRange = promptRectRange,
+ _clipBehavior = clipBehavior {
assert(_showCursor != null);
assert(!_showCursor.value || cursorColor != null);
this.hasFocus = hasFocus ?? false;
@@ -1241,6 +1244,20 @@
double get _caretMargin => _kCaretGap + cursorWidth;
+ /// {@macro flutter.widgets.Clip}
+ ///
+ /// Defaults to [Clip.hardEdge], and must not be null.
+ Clip get clipBehavior => _clipBehavior;
+ Clip _clipBehavior = Clip.hardEdge;
+ set clipBehavior(Clip value) {
+ assert(value != null);
+ if (value != _clipBehavior) {
+ _clipBehavior = value;
+ markNeedsPaint();
+ markNeedsSemanticsUpdate();
+ }
+ }
+
@override
void describeSemanticsConfiguration(SemanticsConfiguration config) {
super.describeSemanticsConfiguration(config);
@@ -2133,8 +2150,8 @@
@override
void paint(PaintingContext context, Offset offset) {
_layoutText(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
- if (_hasVisualOverflow)
- context.pushClipRect(needsCompositing, offset, Offset.zero & size, _paintContents);
+ if (_hasVisualOverflow && clipBehavior != Clip.none)
+ context.pushClipRect(needsCompositing, offset, Offset.zero & size, _paintContents, clipBehavior: clipBehavior);
else
_paintContents(context, offset);
_paintHandleLayers(context, getEndpointsForSelection(selection));
diff --git a/packages/flutter/lib/src/rendering/flex.dart b/packages/flutter/lib/src/rendering/flex.dart
index 45cdc72..91819f3 100644
--- a/packages/flutter/lib/src/rendering/flex.dart
+++ b/packages/flutter/lib/src/rendering/flex.dart
@@ -277,17 +277,20 @@
TextDirection textDirection,
VerticalDirection verticalDirection = VerticalDirection.down,
TextBaseline textBaseline,
+ Clip clipBehavior = Clip.none,
}) : assert(direction != null),
assert(mainAxisAlignment != null),
assert(mainAxisSize != null),
assert(crossAxisAlignment != null),
+ assert(clipBehavior != null),
_direction = direction,
_mainAxisAlignment = mainAxisAlignment,
_mainAxisSize = mainAxisSize,
_crossAxisAlignment = crossAxisAlignment,
_textDirection = textDirection,
_verticalDirection = verticalDirection,
- _textBaseline = textBaseline {
+ _textBaseline = textBaseline,
+ _clipBehavior = clipBehavior {
addAll(children);
}
@@ -474,6 +477,20 @@
// are treated as not overflowing.
bool get _hasOverflow => _overflow > precisionErrorTolerance;
+ /// {@macro flutter.widgets.Clip}
+ ///
+ /// Defaults to [Clip.none], and must not be null.
+ Clip get clipBehavior => _clipBehavior;
+ Clip _clipBehavior = Clip.none;
+ set clipBehavior(Clip value) {
+ assert(value != null);
+ if (value != _clipBehavior) {
+ _clipBehavior = value;
+ markNeedsPaint();
+ markNeedsSemanticsUpdate();
+ }
+ }
+
@override
void setupParentData(RenderBox child) {
if (child.parentData is! FlexParentData)
@@ -955,8 +972,12 @@
if (size.isEmpty)
return;
- // We have overflow. Clip it.
- context.pushClipRect(needsCompositing, offset, Offset.zero & size, defaultPaint);
+ if (clipBehavior == Clip.none) {
+ defaultPaint(context, offset);
+ } else {
+ // We have overflow and the clipBehavior isn't none. Clip it.
+ context.pushClipRect(needsCompositing, offset, Offset.zero & size, defaultPaint, clipBehavior: clipBehavior);
+ }
assert(() {
// Only set this if it's null to save work. It gets reset to null if the
diff --git a/packages/flutter/lib/src/rendering/list_wheel_viewport.dart b/packages/flutter/lib/src/rendering/list_wheel_viewport.dart
index 7ae0aab..92baf8d 100644
--- a/packages/flutter/lib/src/rendering/list_wheel_viewport.dart
+++ b/packages/flutter/lib/src/rendering/list_wheel_viewport.dart
@@ -143,8 +143,8 @@
double overAndUnderCenterOpacity = 1,
@required double itemExtent,
double squeeze = 1,
- bool clipToSize = true,
bool renderChildrenOutsideViewport = false,
+ Clip clipBehavior = Clip.none,
List<RenderBox> children,
}) : assert(childManager != null),
assert(offset != null),
@@ -163,11 +163,11 @@
assert(squeeze != null),
assert(squeeze > 0),
assert(itemExtent > 0),
- assert(clipToSize != null),
assert(renderChildrenOutsideViewport != null),
+ assert(clipBehavior != null),
assert(
- !renderChildrenOutsideViewport || !clipToSize,
- clipToSizeAndRenderChildrenOutsideViewportConflict,
+ !renderChildrenOutsideViewport || clipBehavior == Clip.none,
+ clipBehaviorAndRenderChildrenOutsideViewportConflict,
),
_offset = offset,
_diameterRatio = diameterRatio,
@@ -178,8 +178,8 @@
_overAndUnderCenterOpacity = overAndUnderCenterOpacity,
_itemExtent = itemExtent,
_squeeze = squeeze,
- _clipToSize = clipToSize,
- _renderChildrenOutsideViewport = renderChildrenOutsideViewport {
+ _renderChildrenOutsideViewport = renderChildrenOutsideViewport,
+ _clipBehavior = clipBehavior {
addAll(children);
}
@@ -199,10 +199,10 @@
'be clipped in the z-axis and therefore not renderable. Value must be '
'between 0 and 0.01.';
- /// An error message to show when [clipToSize] and [renderChildrenOutsideViewport]
+ /// An error message to show when [clipBehavior] and [renderChildrenOutsideViewport]
/// are set to conflicting values.
- static const String clipToSizeAndRenderChildrenOutsideViewportConflict =
- 'Cannot renderChildrenOutsideViewport and clipToSize since children '
+ static const String clipBehaviorAndRenderChildrenOutsideViewportConflict =
+ 'Cannot renderChildrenOutsideViewport and clip since children '
'rendered outside will be clipped anyway.';
/// The delegate that manages the children of this object.
@@ -441,37 +441,14 @@
markNeedsSemanticsUpdate();
}
- /// {@template flutter.rendering.wheelList.clipToSize}
- /// Whether to clip painted children to the inside of this viewport.
- ///
- /// Defaults to [true]. Must not be null.
- ///
- /// If this is false and [renderChildrenOutsideViewport] is false, the
- /// first and last children may be painted partly outside of this scroll view.
- /// {@endtemplate}
- bool get clipToSize => _clipToSize;
- bool _clipToSize;
- set clipToSize(bool value) {
- assert(value != null);
- assert(
- !renderChildrenOutsideViewport || !clipToSize,
- clipToSizeAndRenderChildrenOutsideViewportConflict,
- );
- if (value == _clipToSize)
- return;
- _clipToSize = value;
- markNeedsPaint();
- markNeedsSemanticsUpdate();
- }
-
/// {@template flutter.rendering.wheelList.renderChildrenOutsideViewport}
/// Whether to paint children inside the viewport only.
///
/// If false, every child will be painted. However the [Scrollable] is still
/// the size of the viewport and detects gestures inside only.
///
- /// Defaults to [false]. Must not be null. Cannot be true if [clipToSize]
- /// is also true since children outside the viewport will be clipped, and
+ /// Defaults to [false]. Must not be null. Cannot be true if [clipBehavior]
+ /// is not [Clip.none] since children outside the viewport will be clipped, and
/// therefore cannot render children outside the viewport.
/// {@endtemplate}
bool get renderChildrenOutsideViewport => _renderChildrenOutsideViewport;
@@ -479,8 +456,8 @@
set renderChildrenOutsideViewport(bool value) {
assert(value != null);
assert(
- !renderChildrenOutsideViewport || !clipToSize,
- clipToSizeAndRenderChildrenOutsideViewportConflict,
+ !renderChildrenOutsideViewport || clipBehavior == Clip.none,
+ clipBehaviorAndRenderChildrenOutsideViewportConflict,
);
if (value == _renderChildrenOutsideViewport)
return;
@@ -489,6 +466,20 @@
markNeedsSemanticsUpdate();
}
+ /// {@macro flutter.widgets.Clip}
+ ///
+ /// Defaults to [Clip.hardEdge], and must not be null.
+ Clip get clipBehavior => _clipBehavior;
+ Clip _clipBehavior = Clip.hardEdge;
+ set clipBehavior(Clip value) {
+ assert(value != null);
+ if (value != _clipBehavior) {
+ _clipBehavior = value;
+ markNeedsPaint();
+ markNeedsSemanticsUpdate();
+ }
+ }
+
void _hasScrolled() {
markNeedsLayout();
markNeedsSemanticsUpdate();
@@ -787,12 +778,13 @@
@override
void paint(PaintingContext context, Offset offset) {
if (childCount > 0) {
- if (_clipToSize && _shouldClipAtCurrentOffset()) {
+ if (_shouldClipAtCurrentOffset() && clipBehavior != Clip.none) {
context.pushClipRect(
needsCompositing,
offset,
Offset.zero & size,
_paintVisibleChildren,
+ clipBehavior: clipBehavior,
);
} else {
_paintVisibleChildren(context, offset);
diff --git a/packages/flutter/lib/src/rendering/proxy_box.dart b/packages/flutter/lib/src/rendering/proxy_box.dart
index 0d85ea2..1a756a3 100644
--- a/packages/flutter/lib/src/rendering/proxy_box.dart
+++ b/packages/flutter/lib/src/rendering/proxy_box.dart
@@ -2292,11 +2292,14 @@
AlignmentGeometry alignment = Alignment.center,
TextDirection textDirection,
RenderBox child,
+ Clip clipBehavior = Clip.none,
}) : assert(fit != null),
assert(alignment != null),
+ assert(clipBehavior != null),
_fit = fit,
_alignment = alignment,
_textDirection = textDirection,
+ _clipBehavior = clipBehavior,
super(child);
Alignment _resolvedAlignment;
@@ -2373,6 +2376,20 @@
bool _hasVisualOverflow;
Matrix4 _transform;
+ /// {@macro flutter.widgets.Clip}
+ ///
+ /// Defaults to [Clip.none], and must not be null.
+ Clip get clipBehavior => _clipBehavior;
+ Clip _clipBehavior = Clip.none;
+ set clipBehavior(Clip value) {
+ assert(value != null);
+ if (value != _clipBehavior) {
+ _clipBehavior = value;
+ markNeedsPaint();
+ markNeedsSemanticsUpdate();
+ }
+ }
+
void _clearPaintData() {
_hasVisualOverflow = null;
_transform = null;
@@ -2418,9 +2435,9 @@
return;
_updatePaintData();
if (child != null) {
- if (_hasVisualOverflow)
+ if (_hasVisualOverflow && clipBehavior != Clip.none)
layer = context.pushClipRect(needsCompositing, offset, Offset.zero & size, _paintChildWithTransform,
- oldLayer: layer is ClipRectLayer ? layer as ClipRectLayer : null);
+ oldLayer: layer is ClipRectLayer ? layer as ClipRectLayer : null, clipBehavior: clipBehavior);
else
layer = _paintChildWithTransform(context, offset);
}
diff --git a/packages/flutter/lib/src/rendering/shifted_box.dart b/packages/flutter/lib/src/rendering/shifted_box.dart
index 832359c..a1a9776 100644
--- a/packages/flutter/lib/src/rendering/shifted_box.dart
+++ b/packages/flutter/lib/src/rendering/shifted_box.dart
@@ -626,8 +626,11 @@
@required TextDirection textDirection,
Axis constrainedAxis,
RenderBox child,
+ Clip clipBehavior = Clip.none,
}) : assert(alignment != null),
+ assert(clipBehavior != null),
_constrainedAxis = constrainedAxis,
+ _clipBehavior = clipBehavior,
super.mixin(alignment, textDirection, child);
/// The axis to retain constraints on, if any.
@@ -649,6 +652,20 @@
Rect _overflowChildRect = Rect.zero;
bool _isOverflowing = false;
+ /// {@macro flutter.widgets.Clip}
+ ///
+ /// Defaults to [Clip.none], and must not be null.
+ Clip get clipBehavior => _clipBehavior;
+ Clip _clipBehavior = Clip.none;
+ set clipBehavior(Clip value) {
+ assert(value != null);
+ if (value != _clipBehavior) {
+ _clipBehavior = value;
+ markNeedsPaint();
+ markNeedsSemanticsUpdate();
+ }
+ }
+
@override
void performLayout() {
final BoxConstraints constraints = this.constraints;
@@ -694,8 +711,12 @@
return;
}
- // We have overflow. Clip it.
- context.pushClipRect(needsCompositing, offset, Offset.zero & size, super.paint);
+ if (clipBehavior == Clip.none) {
+ super.paint(context, offset);
+ } else {
+ // We have overflow and the clipBehavior isn't none. Clip it.
+ context.pushClipRect(needsCompositing, offset, Offset.zero & size, super.paint, clipBehavior: clipBehavior);
+ }
// Display the overflow indicator.
assert(() {
diff --git a/packages/flutter/lib/src/rendering/stack.dart b/packages/flutter/lib/src/rendering/stack.dart
index 5eacb7b..5977d22 100644
--- a/packages/flutter/lib/src/rendering/stack.dart
+++ b/packages/flutter/lib/src/rendering/stack.dart
@@ -268,6 +268,8 @@
passthrough,
}
+// TODO(liyuqian): Deprecate and remove `Overflow` once its usages are removed from Google.
+
/// Whether overflowing children should be clipped, or their overflow be
/// visible.
enum Overflow {
@@ -326,14 +328,14 @@
AlignmentGeometry alignment = AlignmentDirectional.topStart,
TextDirection textDirection,
StackFit fit = StackFit.loose,
- Overflow overflow = Overflow.clip,
+ Clip clipBehavior = Clip.hardEdge,
}) : assert(alignment != null),
assert(fit != null),
- assert(overflow != null),
+ assert(clipBehavior != null),
_alignment = alignment,
_textDirection = textDirection,
_fit = fit,
- _overflow = overflow {
+ _clipBehavior = clipBehavior {
addAll(children);
}
@@ -411,17 +413,17 @@
}
}
- /// Whether overflowing children should be clipped. See [Overflow].
+ /// {@macro flutter.widgets.Clip}
///
- /// Some children in a stack might overflow its box. When this flag is set to
- /// [Overflow.clip], children cannot paint outside of the stack's box.
- Overflow get overflow => _overflow;
- Overflow _overflow;
- set overflow(Overflow value) {
+ /// Defaults to [Clip.hardEdge], and must not be null.
+ Clip get clipBehavior => _clipBehavior;
+ Clip _clipBehavior = Clip.hardEdge;
+ set clipBehavior(Clip value) {
assert(value != null);
- if (_overflow != value) {
- _overflow = value;
+ if (value != _clipBehavior) {
+ _clipBehavior = value;
markNeedsPaint();
+ markNeedsSemanticsUpdate();
}
}
@@ -604,8 +606,8 @@
@override
void paint(PaintingContext context, Offset offset) {
- if (_overflow == Overflow.clip && _hasVisualOverflow) {
- context.pushClipRect(needsCompositing, offset, Offset.zero & size, paintStack);
+ if (clipBehavior != Clip.none && _hasVisualOverflow) {
+ context.pushClipRect(needsCompositing, offset, Offset.zero & size, paintStack, clipBehavior: clipBehavior);
} else {
paintStack(context, offset);
}
@@ -620,7 +622,7 @@
properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment));
properties.add(EnumProperty<TextDirection>('textDirection', textDirection));
properties.add(EnumProperty<StackFit>('fit', fit));
- properties.add(EnumProperty<Overflow>('overflow', overflow));
+ properties.add(EnumProperty<Clip>('clipBehavior', clipBehavior, defaultValue: Clip.hardEdge));
}
}
diff --git a/packages/flutter/lib/src/rendering/viewport.dart b/packages/flutter/lib/src/rendering/viewport.dart
index 0424305..3fcc91a 100644
--- a/packages/flutter/lib/src/rendering/viewport.dart
+++ b/packages/flutter/lib/src/rendering/viewport.dart
@@ -171,17 +171,20 @@
@required ViewportOffset offset,
double cacheExtent,
CacheExtentStyle cacheExtentStyle = CacheExtentStyle.pixel,
+ Clip clipBehavior = Clip.hardEdge,
}) : assert(axisDirection != null),
assert(crossAxisDirection != null),
assert(offset != null),
assert(axisDirectionToAxis(axisDirection) != axisDirectionToAxis(crossAxisDirection)),
assert(cacheExtentStyle != null),
assert(cacheExtent != null || cacheExtentStyle == CacheExtentStyle.pixel),
+ assert(clipBehavior != null),
_axisDirection = axisDirection,
_crossAxisDirection = crossAxisDirection,
_offset = offset,
_cacheExtent = cacheExtent ?? RenderAbstractViewport.defaultCacheExtent,
- _cacheExtentStyle = cacheExtentStyle;
+ _cacheExtentStyle = cacheExtentStyle,
+ _clipBehavior = clipBehavior;
@override
void describeSemanticsConfiguration(SemanticsConfiguration config) {
@@ -314,6 +317,20 @@
markNeedsLayout();
}
+ /// {@macro flutter.widgets.Clip}
+ ///
+ /// Defaults to [Clip.hardEdge], and must not be null.
+ Clip get clipBehavior => _clipBehavior;
+ Clip _clipBehavior = Clip.hardEdge;
+ set clipBehavior(Clip value) {
+ assert(value != null);
+ if (value != _clipBehavior) {
+ _clipBehavior = value;
+ markNeedsPaint();
+ markNeedsSemanticsUpdate();
+ }
+ }
+
@override
void attach(PipelineOwner owner) {
super.attach(owner);
@@ -574,8 +591,8 @@
void paint(PaintingContext context, Offset offset) {
if (firstChild == null)
return;
- if (hasVisualOverflow) {
- context.pushClipRect(needsCompositing, offset, Offset.zero & size, _paintContents);
+ if (hasVisualOverflow && clipBehavior != Clip.none) {
+ context.pushClipRect(needsCompositing, offset, Offset.zero & size, _paintContents, clipBehavior: clipBehavior);
} else {
_paintContents(context, offset);
}
@@ -1136,9 +1153,11 @@
RenderSliver center,
double cacheExtent,
CacheExtentStyle cacheExtentStyle = CacheExtentStyle.pixel,
+ Clip clipBehavior = Clip.hardEdge,
}) : assert(anchor != null),
assert(anchor >= 0.0 && anchor <= 1.0),
assert(cacheExtentStyle != CacheExtentStyle.viewport || cacheExtent != null),
+ assert(clipBehavior != null),
_anchor = anchor,
_center = center,
super(
@@ -1147,6 +1166,7 @@
offset: offset,
cacheExtent: cacheExtent,
cacheExtentStyle: cacheExtentStyle,
+ clipBehavior: clipBehavior,
) {
addAll(children);
if (center == null && firstChild != null)
@@ -1658,8 +1678,14 @@
AxisDirection axisDirection = AxisDirection.down,
@required AxisDirection crossAxisDirection,
@required ViewportOffset offset,
+ Clip clipBehavior = Clip.hardEdge,
List<RenderSliver> children,
- }) : super(axisDirection: axisDirection, crossAxisDirection: crossAxisDirection, offset: offset) {
+ }) : super(
+ axisDirection: axisDirection,
+ crossAxisDirection: crossAxisDirection,
+ offset: offset,
+ clipBehavior: clipBehavior,
+ ) {
addAll(children);
}
diff --git a/packages/flutter/lib/src/rendering/wrap.dart b/packages/flutter/lib/src/rendering/wrap.dart
index c1bb0d7..40359b5 100644
--- a/packages/flutter/lib/src/rendering/wrap.dart
+++ b/packages/flutter/lib/src/rendering/wrap.dart
@@ -101,8 +101,9 @@
///
/// The runs themselves are then positioned in the cross axis according to the
/// [runSpacing] and [runAlignment].
-class RenderWrap extends RenderBox with ContainerRenderObjectMixin<RenderBox, WrapParentData>,
- RenderBoxContainerDefaultsMixin<RenderBox, WrapParentData> {
+class RenderWrap extends RenderBox
+ with ContainerRenderObjectMixin<RenderBox, WrapParentData>,
+ RenderBoxContainerDefaultsMixin<RenderBox, WrapParentData> {
/// Creates a wrap render object.
///
/// By default, the wrap layout is horizontal and both the children and the
@@ -117,12 +118,14 @@
WrapCrossAlignment crossAxisAlignment = WrapCrossAlignment.start,
TextDirection textDirection,
VerticalDirection verticalDirection = VerticalDirection.down,
+ Clip clipBehavior = Clip.none,
}) : assert(direction != null),
assert(alignment != null),
assert(spacing != null),
assert(runAlignment != null),
assert(runSpacing != null),
assert(crossAxisAlignment != null),
+ assert(clipBehavior != null),
_direction = direction,
_alignment = alignment,
_spacing = spacing,
@@ -130,7 +133,8 @@
_runSpacing = runSpacing,
_crossAxisAlignment = crossAxisAlignment,
_textDirection = textDirection,
- _verticalDirection = verticalDirection {
+ _verticalDirection = verticalDirection,
+ _clipBehavior = clipBehavior {
addAll(children);
}
@@ -326,6 +330,20 @@
}
}
+ /// {@macro flutter.widgets.Clip}
+ ///
+ /// Defaults to [Clip.none], and must not be null.
+ Clip get clipBehavior => _clipBehavior;
+ Clip _clipBehavior = Clip.none;
+ set clipBehavior(Clip value) {
+ assert(value != null);
+ if (value != _clipBehavior) {
+ _clipBehavior = value;
+ markNeedsPaint();
+ markNeedsSemanticsUpdate();
+ }
+ }
+
bool get _debugHasNecessaryDirections {
assert(direction != null);
assert(alignment != null);
@@ -749,8 +767,8 @@
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)
- context.pushClipRect(needsCompositing, offset, Offset.zero & size, defaultPaint);
+ if (_hasVisualOverflow && clipBehavior != Clip.none)
+ context.pushClipRect(needsCompositing, offset, Offset.zero & size, defaultPaint, clipBehavior: clipBehavior);
else
defaultPaint(context, offset);
}
diff --git a/packages/flutter/lib/src/widgets/animated_cross_fade.dart b/packages/flutter/lib/src/widgets/animated_cross_fade.dart
index f23dd52..e1c563d 100644
--- a/packages/flutter/lib/src/widgets/animated_cross_fade.dart
+++ b/packages/flutter/lib/src/widgets/animated_cross_fade.dart
@@ -216,7 +216,7 @@
/// [AnimatedCrossFadeBuilder].
static Widget defaultLayoutBuilder(Widget topChild, Key topChildKey, Widget bottomChild, Key bottomChildKey) {
return Stack(
- overflow: Overflow.visible,
+ clipBehavior: Clip.none,
children: <Widget>[
Positioned(
key: bottomChildKey,
diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart
index de3530b..5c7e915 100644
--- a/packages/flutter/lib/src/widgets/basic.dart
+++ b/packages/flutter/lib/src/widgets/basic.dart
@@ -42,7 +42,6 @@
MainAxisAlignment,
MainAxisSize,
MultiChildLayoutDelegate,
- Overflow,
PaintingContext,
PointerCancelEvent,
PointerCancelEventListener,
@@ -1411,9 +1410,11 @@
Key key,
this.fit = BoxFit.contain,
this.alignment = Alignment.center,
+ this.clipBehavior = Clip.hardEdge,
Widget child,
}) : assert(fit != null),
assert(alignment != null),
+ assert(clipBehavior != null),
super(key: key, child: child);
/// How to inscribe the child into the space allocated during layout.
@@ -1435,12 +1436,19 @@
/// relative to text direction.
final AlignmentGeometry alignment;
+ // TODO(liyuqian): defaults to [Clip.none] once Google references are updated.
+ /// {@macro flutter.widgets.Clip}
+ ///
+ /// Defaults to [Clip.hardEdge].
+ final Clip clipBehavior;
+
@override
RenderFittedBox createRenderObject(BuildContext context) {
return RenderFittedBox(
fit: fit,
alignment: alignment,
textDirection: Directionality.of(context),
+ clipBehavior: clipBehavior,
);
}
@@ -1449,7 +1457,8 @@
renderObject
..fit = fit
..alignment = alignment
- ..textDirection = Directionality.of(context);
+ ..textDirection = Directionality.of(context)
+ ..clipBehavior = clipBehavior;
}
@override
@@ -2231,7 +2240,9 @@
this.textDirection,
this.alignment = Alignment.center,
this.constrainedAxis,
+ this.clipBehavior = Clip.hardEdge,
}) : assert(alignment != null),
+ assert(clipBehavior != null),
super(key: key, child: child);
/// The text direction to use when interpreting the [alignment] if it is an
@@ -2257,12 +2268,19 @@
/// will be retained.
final Axis constrainedAxis;
+ // TODO(liyuqian): defaults to [Clip.none] once Google references are updated.
+ /// {@macro flutter.widgets.Clip}
+ ///
+ /// Defaults to [Clip.hardEdge].
+ final Clip clipBehavior;
+
@override
void updateRenderObject(BuildContext context, covariant RenderUnconstrainedBox renderObject) {
renderObject
..textDirection = textDirection ?? Directionality.of(context)
..alignment = alignment
- ..constrainedAxis = constrainedAxis;
+ ..constrainedAxis = constrainedAxis
+ ..clipBehavior = clipBehavior;
}
@override
@@ -2270,6 +2288,7 @@
textDirection: textDirection ?? Directionality.of(context),
alignment: alignment,
constrainedAxis: constrainedAxis,
+ clipBehavior: clipBehavior,
);
@override
@@ -3214,8 +3233,10 @@
this.textDirection,
this.fit = StackFit.loose,
this.overflow = Overflow.clip,
+ this.clipBehavior = Clip.hardEdge,
List<Widget> children = const <Widget>[],
- }) : super(key: key, children: children);
+ }) : assert(clipBehavior != null),
+ super(key: key, children: children);
/// How to align the non-positioned and partially-positioned children in the
/// stack.
@@ -3252,19 +3273,29 @@
/// ([StackFit.expand]).
final StackFit fit;
+ // TODO(liyuqian): Deprecate and remove [overflow] once its usages are removed from Google.
+
/// Whether overflowing children should be clipped. See [Overflow].
///
/// Some children in a stack might overflow its box. When this flag is set to
/// [Overflow.clip], children cannot paint outside of the stack's box.
+ ///
+ /// This overrides [clipBehavior] for now due to a staged roll out without
+ /// breaking Google. We will remove it and only use [clipBehavior] soon.
final Overflow overflow;
+ /// {@macro flutter.widgets.Clip}
+ ///
+ /// Defaults to [Clip.hardEdge].
+ final Clip clipBehavior;
+
@override
RenderStack createRenderObject(BuildContext context) {
return RenderStack(
alignment: alignment,
textDirection: textDirection ?? Directionality.of(context),
fit: fit,
- overflow: overflow,
+ clipBehavior: overflow == Overflow.clip ? Clip.hardEdge : clipBehavior,
);
}
@@ -3274,7 +3305,7 @@
..alignment = alignment
..textDirection = textDirection ?? Directionality.of(context)
..fit = fit
- ..overflow = overflow;
+ ..clipBehavior = overflow == Overflow.clip ? Clip.hardEdge : clipBehavior;
}
@override
@@ -3283,7 +3314,7 @@
properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment));
properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
properties.add(EnumProperty<StackFit>('fit', fit));
- properties.add(EnumProperty<Overflow>('overflow', overflow));
+ properties.add(EnumProperty<Clip>('clipBehavior', clipBehavior, defaultValue: Clip.hardEdge));
}
}
@@ -3809,6 +3840,7 @@
this.textDirection,
this.verticalDirection = VerticalDirection.down,
this.textBaseline,
+ this.clipBehavior = Clip.hardEdge,
List<Widget> children = const <Widget>[],
}) : assert(direction != null),
assert(mainAxisAlignment != null),
@@ -3816,6 +3848,7 @@
assert(crossAxisAlignment != null),
assert(verticalDirection != null),
assert(crossAxisAlignment != CrossAxisAlignment.baseline || textBaseline != null),
+ assert(clipBehavior != null),
super(key: key, children: children);
/// The direction to use as the main axis.
@@ -3900,6 +3933,12 @@
/// If aligning items according to their baseline, which baseline to use.
final TextBaseline textBaseline;
+ // TODO(liyuqian): defaults to [Clip.none] once Google references are updated.
+ /// {@macro flutter.widgets.Clip}
+ ///
+ /// Defaults to [Clip.hardEdge].
+ final Clip clipBehavior;
+
bool get _needTextDirection {
assert(direction != null);
switch (direction) {
@@ -3943,6 +3982,7 @@
textDirection: getEffectiveTextDirection(context),
verticalDirection: verticalDirection,
textBaseline: textBaseline,
+ clipBehavior: clipBehavior,
);
}
@@ -3955,7 +3995,8 @@
..crossAxisAlignment = crossAxisAlignment
..textDirection = getEffectiveTextDirection(context)
..verticalDirection = verticalDirection
- ..textBaseline = textBaseline;
+ ..textBaseline = textBaseline
+ ..clipBehavior = clipBehavior;
}
@override
@@ -4626,8 +4667,9 @@
this.crossAxisAlignment = WrapCrossAlignment.start,
this.textDirection,
this.verticalDirection = VerticalDirection.down,
+ this.clipBehavior = Clip.hardEdge,
List<Widget> children = const <Widget>[],
- }) : super(key: key, children: children);
+ }) : assert(clipBehavior != null), super(key: key, children: children);
/// The direction to use as the main axis.
///
@@ -4761,6 +4803,12 @@
/// [verticalDirection] must not be null.
final VerticalDirection verticalDirection;
+ // TODO(liyuqian): defaults to [Clip.none] once Google references are updated.
+ /// {@macro flutter.widgets.Clip}
+ ///
+ /// Defaults to [Clip.hardEdge].
+ final Clip clipBehavior;
+
@override
RenderWrap createRenderObject(BuildContext context) {
return RenderWrap(
@@ -4772,6 +4820,7 @@
crossAxisAlignment: crossAxisAlignment,
textDirection: textDirection ?? Directionality.of(context),
verticalDirection: verticalDirection,
+ clipBehavior: clipBehavior,
);
}
@@ -4785,7 +4834,8 @@
..runSpacing = runSpacing
..crossAxisAlignment = crossAxisAlignment
..textDirection = textDirection ?? Directionality.of(context)
- ..verticalDirection = verticalDirection;
+ ..verticalDirection = verticalDirection
+ ..clipBehavior = clipBehavior;
}
@override
diff --git a/packages/flutter/lib/src/widgets/editable_text.dart b/packages/flutter/lib/src/widgets/editable_text.dart
index ff29484..eae0a2c 100644
--- a/packages/flutter/lib/src/widgets/editable_text.dart
+++ b/packages/flutter/lib/src/widgets/editable_text.dart
@@ -413,6 +413,7 @@
selectAll: true,
),
this.autofillHints,
+ this.clipBehavior = Clip.hardEdge,
}) : assert(controller != null),
assert(focusNode != null),
assert(obscuringCharacter != null && obscuringCharacter.length == 1),
@@ -450,6 +451,7 @@
assert(scrollPadding != null),
assert(dragStartBehavior != null),
assert(toolbarOptions != null),
+ assert(clipBehavior != null),
_strutStyle = strutStyle,
keyboardType = keyboardType ?? (maxLines == 1 ? TextInputType.text : TextInputType.multiline),
inputFormatters = maxLines == 1
@@ -1120,6 +1122,11 @@
/// {@endtemplate}
final Iterable<String> autofillHints;
+ /// {@macro flutter.widgets.Clip}
+ ///
+ /// Defaults to [Clip.hardEdge].
+ final Clip clipBehavior;
+
@override
EditableTextState createState() => EditableTextState();
@@ -2089,6 +2096,7 @@
devicePixelRatio: _devicePixelRatio,
promptRectRange: _currentPromptRectRange,
promptRectColor: widget.autocorrectionTextRectColor,
+ clipBehavior: widget.clipBehavior,
),
),
);
@@ -2163,6 +2171,7 @@
this.devicePixelRatio,
this.promptRectRange,
this.promptRectColor,
+ this.clipBehavior,
}) : assert(textDirection != null),
assert(rendererIgnoresPointer != null),
super(key: key);
@@ -2208,6 +2217,7 @@
final double devicePixelRatio;
final TextRange promptRectRange;
final Color promptRectColor;
+ final Clip clipBehavior;
@override
RenderEditable createRenderObject(BuildContext context) {
@@ -2249,6 +2259,7 @@
devicePixelRatio: devicePixelRatio,
promptRectRange: promptRectRange,
promptRectColor: promptRectColor,
+ clipBehavior: clipBehavior,
);
}
@@ -2289,6 +2300,7 @@
..devicePixelRatio = devicePixelRatio
..paintCursorAboveText = paintCursorAboveText
..promptRectColor = promptRectColor
+ ..clipBehavior = clipBehavior
..setPromptRectRange(promptRectRange);
}
}
diff --git a/packages/flutter/lib/src/widgets/list_wheel_scroll_view.dart b/packages/flutter/lib/src/widgets/list_wheel_scroll_view.dart
index 40fdc2b..49952a8 100644
--- a/packages/flutter/lib/src/widgets/list_wheel_scroll_view.dart
+++ b/packages/flutter/lib/src/widgets/list_wheel_scroll_view.dart
@@ -579,8 +579,8 @@
@required this.itemExtent,
this.squeeze = 1.0,
this.onSelectedItemChanged,
- this.clipToSize = true,
this.renderChildrenOutsideViewport = false,
+ this.clipBehavior = Clip.hardEdge,
@required List<Widget> children,
}) : assert(children != null),
assert(diameterRatio != null),
@@ -595,11 +595,11 @@
assert(itemExtent > 0),
assert(squeeze != null),
assert(squeeze > 0),
- assert(clipToSize != null),
assert(renderChildrenOutsideViewport != null),
+ assert(clipBehavior != null),
assert(
- !renderChildrenOutsideViewport || !clipToSize,
- RenderListWheelViewport.clipToSizeAndRenderChildrenOutsideViewportConflict,
+ !renderChildrenOutsideViewport || clipBehavior == Clip.none,
+ RenderListWheelViewport.clipBehaviorAndRenderChildrenOutsideViewportConflict,
),
childDelegate = ListWheelChildListDelegate(children: children),
super(key: key);
@@ -619,8 +619,8 @@
@required this.itemExtent,
this.squeeze = 1.0,
this.onSelectedItemChanged,
- this.clipToSize = true,
this.renderChildrenOutsideViewport = false,
+ this.clipBehavior = Clip.hardEdge,
@required this.childDelegate,
}) : assert(childDelegate != null),
assert(diameterRatio != null),
@@ -635,11 +635,11 @@
assert(itemExtent > 0),
assert(squeeze != null),
assert(squeeze > 0),
- assert(clipToSize != null),
assert(renderChildrenOutsideViewport != null),
+ assert(clipBehavior != null),
assert(
- !renderChildrenOutsideViewport || !clipToSize,
- RenderListWheelViewport.clipToSizeAndRenderChildrenOutsideViewportConflict,
+ !renderChildrenOutsideViewport || clipBehavior == Clip.none,
+ RenderListWheelViewport.clipBehaviorAndRenderChildrenOutsideViewportConflict,
),
super(key: key);
@@ -698,15 +698,17 @@
/// On optional listener that's called when the centered item changes.
final ValueChanged<int> onSelectedItemChanged;
- /// {@macro flutter.rendering.wheelList.clipToSize}
- final bool clipToSize;
-
/// {@macro flutter.rendering.wheelList.renderChildrenOutsideViewport}
final bool renderChildrenOutsideViewport;
/// A delegate that helps lazily instantiating child.
final ListWheelChildDelegate childDelegate;
+ /// {@macro flutter.widgets.Clip}
+ ///
+ /// Defaults to [Clip.hardEdge].
+ final Clip clipBehavior;
+
@override
_ListWheelScrollViewState createState() => _ListWheelScrollViewState();
}
@@ -769,10 +771,10 @@
overAndUnderCenterOpacity: widget.overAndUnderCenterOpacity,
itemExtent: widget.itemExtent,
squeeze: widget.squeeze,
- clipToSize: widget.clipToSize,
renderChildrenOutsideViewport: widget.renderChildrenOutsideViewport,
offset: offset,
childDelegate: widget.childDelegate,
+ clipBehavior: widget.clipBehavior,
);
},
),
@@ -950,7 +952,7 @@
///
/// The [itemExtent] argument in pixels must be provided and must be positive.
///
- /// The [clipToSize] argument defaults to true and must not be null.
+ /// The [clipBehavior] argument defaults to [Clip.hardEdge] and must not be null.
///
/// The [renderChildrenOutsideViewport] argument defaults to false and must
/// not be null.
@@ -966,10 +968,10 @@
this.overAndUnderCenterOpacity = 1.0,
@required this.itemExtent,
this.squeeze = 1.0,
- this.clipToSize = true,
this.renderChildrenOutsideViewport = false,
@required this.offset,
@required this.childDelegate,
+ this.clipBehavior = Clip.hardEdge,
}) : assert(childDelegate != null),
assert(offset != null),
assert(diameterRatio != null),
@@ -983,11 +985,11 @@
assert(itemExtent > 0),
assert(squeeze != null),
assert(squeeze > 0),
- assert(clipToSize != null),
assert(renderChildrenOutsideViewport != null),
+ assert(clipBehavior != null),
assert(
- !renderChildrenOutsideViewport || !clipToSize,
- RenderListWheelViewport.clipToSizeAndRenderChildrenOutsideViewportConflict,
+ !renderChildrenOutsideViewport || clipBehavior == Clip.none,
+ RenderListWheelViewport.clipBehaviorAndRenderChildrenOutsideViewportConflict,
),
super(key: key);
@@ -1017,9 +1019,6 @@
/// Defaults to 1.
final double squeeze;
- /// {@macro flutter.rendering.wheelList.clipToSize}
- final bool clipToSize;
-
/// {@macro flutter.rendering.wheelList.renderChildrenOutsideViewport}
final bool renderChildrenOutsideViewport;
@@ -1030,6 +1029,11 @@
/// A delegate that lazily instantiates children.
final ListWheelChildDelegate childDelegate;
+ /// {@macro flutter.widgets.Clip}
+ ///
+ /// Defaults to [Clip.none].
+ final Clip clipBehavior;
+
@override
ListWheelElement createElement() => ListWheelElement(this);
@@ -1047,8 +1051,8 @@
overAndUnderCenterOpacity: overAndUnderCenterOpacity,
itemExtent: itemExtent,
squeeze: squeeze,
- clipToSize: clipToSize,
renderChildrenOutsideViewport: renderChildrenOutsideViewport,
+ clipBehavior: clipBehavior,
);
}
@@ -1064,7 +1068,7 @@
..overAndUnderCenterOpacity = overAndUnderCenterOpacity
..itemExtent = itemExtent
..squeeze = squeeze
- ..clipToSize = clipToSize
- ..renderChildrenOutsideViewport = renderChildrenOutsideViewport;
+ ..renderChildrenOutsideViewport = renderChildrenOutsideViewport
+ ..clipBehavior = clipBehavior;
}
}
diff --git a/packages/flutter/lib/src/widgets/nested_scroll_view.dart b/packages/flutter/lib/src/widgets/nested_scroll_view.dart
index d552373..14f3e76 100644
--- a/packages/flutter/lib/src/widgets/nested_scroll_view.dart
+++ b/packages/flutter/lib/src/widgets/nested_scroll_view.dart
@@ -193,10 +193,12 @@
@required this.headerSliverBuilder,
@required this.body,
this.dragStartBehavior = DragStartBehavior.start,
+ this.clipBehavior = Clip.hardEdge,
}) : assert(scrollDirection != null),
assert(reverse != null),
assert(headerSliverBuilder != null),
assert(body != null),
+ assert(clipBehavior != null),
super(key: key);
/// An object that can be used to control the position to which the outer
@@ -260,6 +262,11 @@
/// {@macro flutter.widgets.scrollable.dragStartBehavior}
final DragStartBehavior dragStartBehavior;
+ /// {@macro flutter.widgets.Clip}
+ ///
+ /// Defaults to [Clip.hardEdge].
+ final Clip clipBehavior;
+
/// Returns the [SliverOverlapAbsorberHandle] of the nearest ancestor
/// [NestedScrollView].
///
@@ -437,6 +444,7 @@
_lastHasScrolledBody,
),
handle: _absorberHandle,
+ clipBehavior: widget.clipBehavior,
);
},
),
@@ -452,6 +460,7 @@
@required ScrollController controller,
@required List<Widget> slivers,
@required this.handle,
+ @required this.clipBehavior,
DragStartBehavior dragStartBehavior = DragStartBehavior.start,
}) : super(
scrollDirection: scrollDirection,
@@ -463,6 +472,7 @@
);
final SliverOverlapAbsorberHandle handle;
+ final Clip clipBehavior;
@override
Widget buildViewport(
@@ -477,6 +487,7 @@
offset: offset,
slivers: slivers,
handle: handle,
+ clipBehavior: clipBehavior,
);
}
}
@@ -1824,6 +1835,7 @@
Key center,
List<Widget> slivers = const <Widget>[],
@required this.handle,
+ Clip clipBehavior = Clip.hardEdge,
}) : assert(handle != null),
super(
key: key,
@@ -1833,6 +1845,7 @@
offset: offset,
center: center,
slivers: slivers,
+ clipBehavior: clipBehavior,
);
/// The handle to the [SliverOverlapAbsorber] that is feeding this injector.
@@ -1849,6 +1862,7 @@
anchor: anchor,
offset: offset,
handle: handle,
+ clipBehavior: clipBehavior,
);
}
@@ -1862,7 +1876,8 @@
)
..anchor = anchor
..offset = offset
- ..handle = handle;
+ ..handle = handle
+ ..clipBehavior = clipBehavior;
}
@override
@@ -1889,6 +1904,7 @@
List<RenderSliver> children,
RenderSliver center,
@required SliverOverlapAbsorberHandle handle,
+ Clip clipBehavior = Clip.hardEdge,
}) : assert(handle != null),
_handle = handle,
super(
@@ -1898,6 +1914,7 @@
anchor: anchor,
children: children,
center: center,
+ clipBehavior: clipBehavior,
);
/// The object to notify when [markNeedsLayout] is called.
diff --git a/packages/flutter/lib/src/widgets/single_child_scroll_view.dart b/packages/flutter/lib/src/widgets/single_child_scroll_view.dart
index f4deca6..0d5b98a 100644
--- a/packages/flutter/lib/src/widgets/single_child_scroll_view.dart
+++ b/packages/flutter/lib/src/widgets/single_child_scroll_view.dart
@@ -219,8 +219,10 @@
this.controller,
this.child,
this.dragStartBehavior = DragStartBehavior.start,
+ this.clipBehavior = Clip.hardEdge,
}) : assert(scrollDirection != null),
assert(dragStartBehavior != null),
+ assert(clipBehavior != null),
assert(!(controller != null && primary == true),
'Primary ScrollViews obtain their ScrollController via inheritance from a PrimaryScrollController widget. '
'You cannot both set primary to true and pass an explicit controller.'
@@ -290,6 +292,11 @@
/// {@macro flutter.widgets.scrollable.dragStartBehavior}
final DragStartBehavior dragStartBehavior;
+ /// {@macro flutter.widgets.Clip}
+ ///
+ /// Defaults to [Clip.hardEdge].
+ final Clip clipBehavior;
+
AxisDirection _getDirection(BuildContext context) {
return getAxisDirectionFromAxisReverseAndDirectionality(context, scrollDirection, reverse);
}
@@ -313,6 +320,7 @@
axisDirection: axisDirection,
offset: offset,
child: contents,
+ clipBehavior: clipBehavior,
);
},
);
@@ -328,17 +336,21 @@
this.axisDirection = AxisDirection.down,
this.offset,
Widget child,
+ @required this.clipBehavior,
}) : assert(axisDirection != null),
+ assert(clipBehavior != null),
super(key: key, child: child);
final AxisDirection axisDirection;
final ViewportOffset offset;
+ final Clip clipBehavior;
@override
_RenderSingleChildViewport createRenderObject(BuildContext context) {
return _RenderSingleChildViewport(
axisDirection: axisDirection,
offset: offset,
+ clipBehavior: clipBehavior,
);
}
@@ -347,7 +359,8 @@
// Order dependency: The offset setter reads the axis direction.
renderObject
..axisDirection = axisDirection
- ..offset = offset;
+ ..offset = offset
+ ..clipBehavior = clipBehavior;
}
}
@@ -357,12 +370,15 @@
@required ViewportOffset offset,
double cacheExtent = RenderAbstractViewport.defaultCacheExtent,
RenderBox child,
+ @required Clip clipBehavior,
}) : assert(axisDirection != null),
assert(offset != null),
assert(cacheExtent != null),
+ assert(clipBehavior != null),
_axisDirection = axisDirection,
_offset = offset,
- _cacheExtent = cacheExtent {
+ _cacheExtent = cacheExtent,
+ _clipBehavior = clipBehavior {
this.child = child;
}
@@ -403,6 +419,20 @@
markNeedsLayout();
}
+ /// {@macro flutter.widgets.Clip}
+ ///
+ /// Defaults to [Clip.none], and must not be null.
+ Clip get clipBehavior => _clipBehavior;
+ Clip _clipBehavior = Clip.none;
+ set clipBehavior(Clip value) {
+ assert(value != null);
+ if (value != _clipBehavior) {
+ _clipBehavior = value;
+ markNeedsPaint();
+ markNeedsSemanticsUpdate();
+ }
+ }
+
void _hasScrolled() {
markNeedsPaint();
markNeedsSemanticsUpdate();
@@ -548,8 +578,8 @@
context.paintChild(child, offset + paintOffset);
}
- if (_shouldClipAtPaintOffset(paintOffset)) {
- context.pushClipRect(needsCompositing, offset, Offset.zero & size, paintContents);
+ if (_shouldClipAtPaintOffset(paintOffset) && clipBehavior != Clip.none) {
+ context.pushClipRect(needsCompositing, offset, Offset.zero & size, paintContents, clipBehavior: clipBehavior);
} else {
paintContents(context, offset);
}
diff --git a/packages/flutter/lib/src/widgets/viewport.dart b/packages/flutter/lib/src/widgets/viewport.dart
index 2623ad2..e478ec3 100644
--- a/packages/flutter/lib/src/widgets/viewport.dart
+++ b/packages/flutter/lib/src/widgets/viewport.dart
@@ -58,12 +58,14 @@
this.center,
this.cacheExtent,
this.cacheExtentStyle = CacheExtentStyle.pixel,
+ this.clipBehavior = Clip.hardEdge,
List<Widget> slivers = const <Widget>[],
}) : assert(offset != null),
assert(slivers != null),
assert(center == null || slivers.where((Widget child) => child.key == center).length == 1),
assert(cacheExtentStyle != null),
assert(cacheExtentStyle != CacheExtentStyle.viewport || cacheExtent != null),
+ assert(clipBehavior != null),
super(key: key, children: slivers);
/// The direction in which the [offset]'s [ViewportOffset.pixels] increases.
@@ -118,6 +120,11 @@
/// {@macro flutter.rendering.viewport.cacheExtentStyle}
final CacheExtentStyle cacheExtentStyle;
+ /// {@macro flutter.widgets.Clip}
+ ///
+ /// Defaults to [Clip.none].
+ final Clip clipBehavior;
+
/// Given a [BuildContext] and an [AxisDirection], determine the correct cross
/// axis direction.
///
@@ -147,6 +154,7 @@
offset: offset,
cacheExtent: cacheExtent,
cacheExtentStyle: cacheExtentStyle,
+ clipBehavior: clipBehavior,
);
}
@@ -158,7 +166,8 @@
..anchor = anchor
..offset = offset
..cacheExtent = cacheExtent
- ..cacheExtentStyle = cacheExtentStyle;
+ ..cacheExtentStyle = cacheExtentStyle
+ ..clipBehavior = clipBehavior;
}
@override
@@ -263,6 +272,7 @@
this.axisDirection = AxisDirection.down,
this.crossAxisDirection,
@required this.offset,
+ this.clipBehavior = Clip.hardEdge,
List<Widget> slivers = const <Widget>[],
}) : assert(offset != null),
super(key: key, children: slivers);
@@ -295,12 +305,18 @@
/// Typically a [ScrollPosition].
final ViewportOffset offset;
+ /// {@macro flutter.widgets.Clip}
+ ///
+ /// Defaults to [Clip.hardEdge].
+ final Clip clipBehavior;
+
@override
RenderShrinkWrappingViewport createRenderObject(BuildContext context) {
return RenderShrinkWrappingViewport(
axisDirection: axisDirection,
crossAxisDirection: crossAxisDirection ?? Viewport.getDefaultCrossAxisDirection(context, axisDirection),
offset: offset,
+ clipBehavior: clipBehavior,
);
}
@@ -309,7 +325,8 @@
renderObject
..axisDirection = axisDirection
..crossAxisDirection = crossAxisDirection ?? Viewport.getDefaultCrossAxisDirection(context, axisDirection)
- ..offset = offset;
+ ..offset = offset
+ ..clipBehavior = clipBehavior;
}
@override
diff --git a/packages/flutter/test/material/dropdown_test.dart b/packages/flutter/test/material/dropdown_test.dart
index 1050e89..3a180bd 100644
--- a/packages/flutter/test/material/dropdown_test.dart
+++ b/packages/flutter/test/material/dropdown_test.dart
@@ -2143,8 +2143,8 @@
// hard coded 16px margin in the dropdown code, so that
// this hint aligns "properly" with the menu.
return Stack(
+ clipBehavior: Clip.none,
alignment: Alignment.topCenter,
- overflow: Overflow.visible,
children: <Widget>[
PositionedDirectional(
width: constraints.maxWidth + hintPaddingOffset,
diff --git a/packages/flutter/test/rendering/box_test.dart b/packages/flutter/test/rendering/box_test.dart
index bb4acbe..e0dcb784 100644
--- a/packages/flutter/test/rendering/box_test.dart
+++ b/packages/flutter/test/rendering/box_test.dart
@@ -670,6 +670,33 @@
expect(unconstrained.size.height, equals(100.0), reason: 'constrained height');
});
+ test('clipBehavior is respected', () {
+ const BoxConstraints viewport = BoxConstraints(maxHeight: 100.0, maxWidth: 100.0);
+ final TestClipPaintingContext context = TestClipPaintingContext();
+
+ // By default, clipBehavior should be Clip.none
+ final RenderUnconstrainedBox defaultBox = RenderUnconstrainedBox(
+ alignment: Alignment.center,
+ textDirection: TextDirection.ltr,
+ child: box200x200,
+ );
+ layout(defaultBox, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors);
+ defaultBox.paint(context, Offset.zero);
+ expect(context.clipBehavior, equals(Clip.none));
+
+ for (final Clip clip in Clip.values) {
+ final RenderUnconstrainedBox box = RenderUnconstrainedBox(
+ alignment: Alignment.center,
+ textDirection: TextDirection.ltr,
+ child: box200x200,
+ clipBehavior: clip,
+ );
+ layout(box, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors);
+ box.paint(context, Offset.zero);
+ expect(context.clipBehavior, equals(clip));
+ }
+ });
+
group('hit testing', () {
test('BoxHitTestResult wrapping HitTestResult', () {
final HitTestEntry entry1 = HitTestEntry(_DummyHitTestTarget());
diff --git a/packages/flutter/test/rendering/editable_test.dart b/packages/flutter/test/rendering/editable_test.dart
index 48d793b..1614dea 100644
--- a/packages/flutter/test/rendering/editable_test.dart
+++ b/packages/flutter/test/rendering/editable_test.dart
@@ -24,6 +24,44 @@
}
void main() {
+ test('RenderEditable respects clipBehavior', () {
+ const BoxConstraints viewport = BoxConstraints(maxHeight: 100.0, maxWidth: 100.0);
+ final TestClipPaintingContext context = TestClipPaintingContext();
+
+ final String longString = 'a' * 10000;
+
+ // By default, clipBehavior should be Clip.none
+ final RenderEditable defaultEditable = RenderEditable(
+ text: TextSpan(text: longString),
+ textDirection: TextDirection.ltr,
+ startHandleLayerLink: LayerLink(),
+ endHandleLayerLink: LayerLink(),
+ offset: ViewportOffset.zero(),
+ textSelectionDelegate: FakeEditableTextState(),
+ selection: const TextSelection(baseOffset: 0, extentOffset: 0),
+ );
+ layout(defaultEditable, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors);
+ defaultEditable.paint(context, Offset.zero);
+ expect(context.clipBehavior, equals(Clip.hardEdge));
+
+ context.clipBehavior = Clip.none; // Reset as Clip.none won't write into clipBehavior.
+ for (final Clip clip in Clip.values) {
+ final RenderEditable editable = RenderEditable(
+ text: TextSpan(text: longString),
+ textDirection: TextDirection.ltr,
+ startHandleLayerLink: LayerLink(),
+ endHandleLayerLink: LayerLink(),
+ offset: ViewportOffset.zero(),
+ textSelectionDelegate: FakeEditableTextState(),
+ selection: const TextSelection(baseOffset: 0, extentOffset: 0),
+ clipBehavior: clip,
+ );
+ layout(editable, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors);
+ editable.paint(context, Offset.zero);
+ expect(context.clipBehavior, equals(clip));
+ }
+ });
+
test('editable intrinsics', () {
final TextSelectionDelegate delegate = FakeEditableTextState();
final RenderEditable editable = RenderEditable(
diff --git a/packages/flutter/test/rendering/flex_test.dart b/packages/flutter/test/rendering/flex_test.dart
index 824aff1..81ed36c 100644
--- a/packages/flutter/test/rendering/flex_test.dart
+++ b/packages/flutter/test/rendering/flex_test.dart
@@ -52,6 +52,24 @@
FlutterError.onError = oldHandler;
});
+ test('Clip behavior is respected', () {
+ const BoxConstraints viewport = BoxConstraints(maxHeight: 100.0, maxWidth: 100.0);
+ final TestClipPaintingContext context = TestClipPaintingContext();
+
+ // By default, clipBehavior should be Clip.none
+ final RenderFlex defaultFlex = RenderFlex(direction: Axis.vertical, children: <RenderBox>[box200x200]);
+ layout(defaultFlex, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors);
+ defaultFlex.paint(context, Offset.zero);
+ expect(context.clipBehavior, equals(Clip.none));
+
+ for (final Clip clip in Clip.values) {
+ final RenderFlex flex = RenderFlex(direction: Axis.vertical, children: <RenderBox>[box200x200], clipBehavior: clip);
+ layout(flex, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors);
+ flex.paint(context, Offset.zero);
+ expect(context.clipBehavior, equals(clip));
+ }
+ });
+
test('Vertical Overflow', () {
final RenderConstrainedBox flexible = RenderConstrainedBox(
additionalConstraints: const BoxConstraints.expand()
diff --git a/packages/flutter/test/rendering/proxy_box_test.dart b/packages/flutter/test/rendering/proxy_box_test.dart
index 6a49841..1d0d180 100644
--- a/packages/flutter/test/rendering/proxy_box_test.dart
+++ b/packages/flutter/test/rendering/proxy_box_test.dart
@@ -431,6 +431,7 @@
_testLayerReuse<ClipRectLayer>(RenderFittedBox(
alignment: Alignment.center,
fit: BoxFit.cover,
+ clipBehavior: Clip.hardEdge,
// Inject opacity under the clip to force compositing.
child: RenderOpacity(
opacity: 0.5,
@@ -468,6 +469,24 @@
_testFittedBoxWithClipRectLayer();
});
+ test('RenderFittedBox respects clipBehavior', () {
+ const BoxConstraints viewport = BoxConstraints(maxHeight: 100.0, maxWidth: 100.0);
+ final TestClipPaintingContext context = TestClipPaintingContext();
+
+ // By default, clipBehavior should be Clip.none
+ final RenderFittedBox defaultBox = RenderFittedBox(child: box200x200, fit: BoxFit.none);
+ layout(defaultBox, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors);
+ defaultBox.paint(context, Offset.zero);
+ expect(context.clipBehavior, equals(Clip.none));
+
+ for (final Clip clip in Clip.values) {
+ final RenderFittedBox box = RenderFittedBox(child: box200x200, fit: BoxFit.none, clipBehavior: clip);
+ layout(box, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors);
+ box.paint(context, Offset.zero);
+ expect(context.clipBehavior, equals(clip));
+ }
+ });
+
test('RenderMouseRegion can change properties when detached', () {
renderer.initMouseTracker(MouseTracker(
renderer.pointerRouter,
diff --git a/packages/flutter/test/rendering/rendering_tester.dart b/packages/flutter/test/rendering/rendering_tester.dart
index 6d24df8..cfd7236 100644
--- a/packages/flutter/test/rendering/rendering_tester.dart
+++ b/packages/flutter/test/rendering/rendering_tester.dart
@@ -294,3 +294,26 @@
return DiagnosticsProperty<Ticker>(name, this, style: DiagnosticsTreeStyle.errorProperty);
}
}
+
+class TestClipPaintingContext extends PaintingContext {
+ TestClipPaintingContext() : super(ContainerLayer(), Rect.zero);
+
+ @override
+ ClipRectLayer pushClipRect(bool needsCompositing, Offset offset, Rect clipRect, PaintingContextCallback painter, {Clip clipBehavior = Clip.hardEdge, ClipRectLayer oldLayer}) {
+ this.clipBehavior = clipBehavior;
+ return null;
+ }
+
+ Clip clipBehavior = Clip.none;
+}
+
+void expectOverflowedErrors() {
+ final FlutterErrorDetails errorDetails = renderer.takeFlutterErrorDetails();
+ final bool overflowed = errorDetails.toString().contains('overflowed');
+ if (!overflowed) {
+ FlutterError.reportError(errorDetails);
+ }
+}
+
+RenderConstrainedBox get box200x200 =>
+ RenderConstrainedBox(additionalConstraints: const BoxConstraints.tightFor(height: 200.0, width: 200.0));
diff --git a/packages/flutter/test/rendering/stack_test.dart b/packages/flutter/test/rendering/stack_test.dart
index 1243dc3..9449f0d 100644
--- a/packages/flutter/test/rendering/stack_test.dart
+++ b/packages/flutter/test/rendering/stack_test.dart
@@ -61,6 +61,33 @@
expect(stack.size.height, equals(100.0));
});
+ test('Stack respects clipBehavior', () {
+ const BoxConstraints viewport = BoxConstraints(maxHeight: 100.0, maxWidth: 100.0);
+ final TestClipPaintingContext context = TestClipPaintingContext();
+
+ // By default, clipBehavior should be Clip.none
+ final RenderStack defaultStack = RenderStack(textDirection: TextDirection.ltr, children: <RenderBox>[box200x200]);
+ layout(defaultStack, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors);
+ defaultStack.paint(context, Offset.zero);
+ expect(context.clipBehavior, equals(Clip.none));
+
+ for (final Clip clip in Clip.values) {
+ final RenderBox child = box200x200;
+ final RenderStack stack = RenderStack(
+ textDirection: TextDirection.ltr,
+ children: <RenderBox>[child],
+ clipBehavior: clip,
+ );
+ { // Make sure that the child is positioned so the stack will consider it as overflowed.
+ final StackParentData parentData = child.parentData as StackParentData;
+ parentData.left = parentData.right = 0;
+ }
+ layout(stack, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors);
+ stack.paint(context, Offset.zero);
+ expect(context.clipBehavior, equals(clip));
+ }
+ });
+
group('RenderIndexedStack', () {
test('visitChildrenForSemantics only visits displayed child', () {
final RenderBox child1 = RenderConstrainedBox(
diff --git a/packages/flutter/test/rendering/wrap_test.dart b/packages/flutter/test/rendering/wrap_test.dart
index e6dac87..7667ff8 100644
--- a/packages/flutter/test/rendering/wrap_test.dart
+++ b/packages/flutter/test/rendering/wrap_test.dart
@@ -5,6 +5,8 @@
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
+import 'rendering_tester.dart';
+
void main() {
test('Wrap test; toStringDeep', () {
final RenderWrap renderWrap = RenderWrap();
@@ -151,4 +153,22 @@
expect(renderWrap.computeMinIntrinsicWidth(79), 80);
expect(renderWrap.computeMinIntrinsicWidth(80), 80);
});
+
+ test('Wrap respects clipBehavior', () {
+ const BoxConstraints viewport = BoxConstraints(maxHeight: 100.0, maxWidth: 100.0);
+ final TestClipPaintingContext context = TestClipPaintingContext();
+
+ // By default, clipBehavior should be Clip.none
+ final RenderWrap defaultWrap = RenderWrap(textDirection: TextDirection.ltr, children: <RenderBox>[box200x200]);
+ layout(defaultWrap, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors);
+ defaultWrap.paint(context, Offset.zero);
+ expect(context.clipBehavior, equals(Clip.none));
+
+ for (final Clip clip in Clip.values) {
+ final RenderWrap wrap = RenderWrap(textDirection: TextDirection.ltr, children: <RenderBox>[box200x200], clipBehavior: clip);
+ layout(wrap, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors);
+ wrap.paint(context, Offset.zero);
+ expect(context.clipBehavior, equals(clip));
+ }
+ });
}
diff --git a/packages/flutter/test/widgets/basic_test.dart b/packages/flutter/test/widgets/basic_test.dart
index f1c502c..0c75008 100644
--- a/packages/flutter/test/widgets/basic_test.dart
+++ b/packages/flutter/test/widgets/basic_test.dart
@@ -280,6 +280,15 @@
);
});
+ testWidgets('UnconstrainedBox can set and update clipBehavior', (WidgetTester tester) async {
+ await tester.pumpWidget(const UnconstrainedBox());
+ final RenderUnconstrainedBox renderObject = tester.allRenderObjects.whereType<RenderUnconstrainedBox>().first;
+ expect(renderObject.clipBehavior, equals(Clip.hardEdge));
+
+ await tester.pumpWidget(const UnconstrainedBox(clipBehavior: Clip.antiAlias));
+ expect(renderObject.clipBehavior, equals(Clip.antiAlias));
+ });
+
group('ColoredBox', () {
_MockCanvas mockCanvas;
_MockPaintingContext mockContext;
diff --git a/packages/flutter/test/widgets/editable_text_test.dart b/packages/flutter/test/widgets/editable_text_test.dart
index 05c4c8e..7a3e894 100644
--- a/packages/flutter/test/widgets/editable_text_test.dart
+++ b/packages/flutter/test/widgets/editable_text_test.dart
@@ -4761,6 +4761,48 @@
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);
});
+
+ testWidgets('EditableText can set and update clipBehavior', (WidgetTester tester) async {
+ await tester.pumpWidget(MediaQuery(
+ data: const MediaQueryData(devicePixelRatio: 1.0),
+ child: Directionality(
+ textDirection: TextDirection.ltr,
+ child: FocusScope(
+ node: focusScopeNode,
+ autofocus: true,
+ child: EditableText(
+ backgroundCursorColor: Colors.grey,
+ controller: controller,
+ focusNode: focusNode,
+ style: textStyle,
+ cursorColor: cursorColor,
+ ),
+ ),
+ ),
+ ));
+ final RenderEditable renderObject = tester.allRenderObjects.whereType<RenderEditable>().first;
+ expect(renderObject.clipBehavior, equals(Clip.hardEdge));
+
+ await tester.pumpWidget(MediaQuery(
+ data: const MediaQueryData(devicePixelRatio: 1.0),
+ child: Directionality(
+ textDirection: TextDirection.ltr,
+ child: FocusScope(
+ node: focusScopeNode,
+ autofocus: true,
+ child: EditableText(
+ backgroundCursorColor: Colors.grey,
+ controller: controller,
+ focusNode: focusNode,
+ style: textStyle,
+ cursorColor: cursorColor,
+ clipBehavior: Clip.antiAlias,
+ ),
+ ),
+ ),
+ ));
+ expect(renderObject.clipBehavior, equals(Clip.antiAlias));
+ });
}
class MockTextFormatter extends TextInputFormatter {
diff --git a/packages/flutter/test/widgets/fitted_box_test.dart b/packages/flutter/test/widgets/fitted_box_test.dart
index 0469a5a..7d47d6e 100644
--- a/packages/flutter/test/widgets/fitted_box_test.dart
+++ b/packages/flutter/test/widgets/fitted_box_test.dart
@@ -369,6 +369,7 @@
height: 10.0,
child: FittedBox(
fit: BoxFit.cover,
+ clipBehavior: Clip.hardEdge,
child: SizedBox(
width: 10.0,
height: 50.0,
@@ -391,6 +392,7 @@
height: 100.0,
child: FittedBox(
fit: BoxFit.cover,
+ clipBehavior: Clip.hardEdge,
child: SizedBox(
width: 50.0,
height: 10.0,
@@ -418,6 +420,7 @@
height: b,
child: FittedBox(
fit: BoxFit.none,
+ clipBehavior: Clip.hardEdge,
child: SizedBox(
width: c,
height: d,
@@ -472,6 +475,15 @@
await tester.tap(find.byKey(key1));
expect(_pointerDown, isTrue);
});
+
+ testWidgets('Can set and update clipBehavior', (WidgetTester tester) async {
+ await tester.pumpWidget(FittedBox(fit: BoxFit.none, child: Container()));
+ final RenderFittedBox renderObject = tester.allRenderObjects.whereType<RenderFittedBox>().first;
+ expect(renderObject.clipBehavior, equals(Clip.hardEdge));
+
+ await tester.pumpWidget(FittedBox(fit: BoxFit.none, child: Container(), clipBehavior: Clip.antiAlias));
+ expect(renderObject.clipBehavior, equals(Clip.antiAlias));
+ });
}
List<Type> getLayers() {
diff --git a/packages/flutter/test/widgets/flex_test.dart b/packages/flutter/test/widgets/flex_test.dart
index 993dc7e..a249a7e 100644
--- a/packages/flutter/test/widgets/flex_test.dart
+++ b/packages/flutter/test/widgets/flex_test.dart
@@ -140,4 +140,13 @@
final String message = tester.takeException().toString();
expect(message, contains('\nSee also:'));
});
+
+ testWidgets('Can set and update clipBehavior', (WidgetTester tester) async {
+ await tester.pumpWidget(Flex(direction: Axis.vertical));
+ final RenderFlex renderObject = tester.allRenderObjects.whereType<RenderFlex>().first;
+ expect(renderObject.clipBehavior, equals(Clip.hardEdge));
+
+ await tester.pumpWidget(Flex(direction: Axis.vertical, clipBehavior: Clip.antiAlias));
+ expect(renderObject.clipBehavior, equals(Clip.antiAlias));
+ });
}
diff --git a/packages/flutter/test/widgets/framework_test.dart b/packages/flutter/test/widgets/framework_test.dart
index 68bb488..7586da7 100644
--- a/packages/flutter/test/widgets/framework_test.dart
+++ b/packages/flutter/test/widgets/framework_test.dart
@@ -719,7 +719,7 @@
equalsIgnoringHashCodes(
'Duplicate keys found.\n'
'If multiple keyed nodes exist as children of another node, they must have unique keys.\n'
- 'Stack(alignment: AlignmentDirectional.topStart, textDirection: ltr, fit: loose, overflow: clip) has multiple children with key [GlobalKey#00000 problematic].'
+ 'Stack(alignment: AlignmentDirectional.topStart, textDirection: ltr, fit: loose) has multiple children with key [GlobalKey#00000 problematic].'
),
);
});
@@ -889,7 +889,7 @@
'The specific parent that did not update after having one or more children forcibly '
'removed due to GlobalKey reparenting is:\n'
'- Stack(alignment: AlignmentDirectional.topStart, textDirection: ltr, fit: loose, '
- 'overflow: clip, renderObject: RenderStack#00000)\n'
+ 'renderObject: RenderStack#00000)\n'
'A GlobalKey can only be specified on one widget at a time in the widget tree.'
),
);
diff --git a/packages/flutter/test/widgets/list_wheel_scroll_view_test.dart b/packages/flutter/test/widgets/list_wheel_scroll_view_test.dart
index 4de089f..f26e307 100644
--- a/packages/flutter/test/widgets/list_wheel_scroll_view_test.dart
+++ b/packages/flutter/test/widgets/list_wheel_scroll_view_test.dart
@@ -11,6 +11,44 @@
import '../rendering/rendering_tester.dart';
void main() {
+ testWidgets('ListWheelScrollView respects clipBehavior', (WidgetTester tester) async {
+ await tester.pumpWidget(
+ Directionality(
+ textDirection: TextDirection.ltr,
+ child: ListWheelScrollView(
+ itemExtent: 2000.0, // huge extent to trigger clip
+ children: <Widget>[Container()],
+ ),
+ ),
+ );
+
+ // 1st, check that the render object has received the default clip behavior.
+ final RenderListWheelViewport renderObject = tester.allRenderObjects.whereType<RenderListWheelViewport>().first;
+ expect(renderObject.clipBehavior, equals(Clip.hardEdge));
+
+ // 2nd, check that the painting context has received the default clip behavior.
+ final TestClipPaintingContext context = TestClipPaintingContext();
+ renderObject.paint(context, Offset.zero);
+ expect(context.clipBehavior, equals(Clip.hardEdge));
+
+ // 3rd, pump a new widget to check that the render object can update its clip behavior.
+ await tester.pumpWidget(
+ Directionality(
+ textDirection: TextDirection.ltr,
+ child: ListWheelScrollView(
+ itemExtent: 2000.0, // huge extent to trigger clip
+ children: <Widget>[Container()],
+ clipBehavior: Clip.antiAlias,
+ ),
+ ),
+ );
+ expect(renderObject.clipBehavior, equals(Clip.antiAlias));
+
+ // 4th, check that a non-default clip behavior can be sent to the painting context.
+ renderObject.paint(context, Offset.zero);
+ expect(context.clipBehavior, equals(Clip.antiAlias));
+ });
+
group('construction check', () {
testWidgets('ListWheelScrollView needs positive diameter ratio', (WidgetTester tester) async {
try {
diff --git a/packages/flutter/test/widgets/nested_scroll_view_test.dart b/packages/flutter/test/widgets/nested_scroll_view_test.dart
index c01c311..e40ea07 100644
--- a/packages/flutter/test/widgets/nested_scroll_view_test.dart
+++ b/packages/flutter/test/widgets/nested_scroll_view_test.dart
@@ -8,6 +8,8 @@
import 'package:flutter/gestures.dart' show DragStartBehavior;
import 'package:flutter/rendering.dart';
+import '../rendering/rendering_tester.dart';
+
class _CustomPhysics extends ClampingScrollPhysics {
const _CustomPhysics({ ScrollPhysics parent }) : super(parent: parent);
@@ -118,6 +120,55 @@
}
void main() {
+ testWidgets('NestedScrollView respects clipBehavior', (WidgetTester tester) async {
+ Widget build(NestedScrollView nestedScrollView) {
+ return Localizations(
+ locale: const Locale('en', 'US'),
+ delegates: const <LocalizationsDelegate<dynamic>>[
+ DefaultMaterialLocalizations.delegate,
+ DefaultWidgetsLocalizations.delegate,
+ ],
+ child: Directionality(
+ textDirection: TextDirection.ltr,
+ child: MediaQuery(
+ data: const MediaQueryData(),
+ child: nestedScrollView,
+ ),
+ ),
+ );
+ }
+
+ await tester.pumpWidget(build(
+ NestedScrollView(
+ headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) => <Widget>[const SliverAppBar()],
+ body: Container(height: 2000.0),
+ )
+ ));
+
+ // 1st, check that the render object has received the default clip behavior.
+ final RenderNestedScrollViewViewport renderObject = tester.allRenderObjects.whereType<RenderNestedScrollViewViewport>().first;
+ expect(renderObject.clipBehavior, equals(Clip.hardEdge));
+
+ // 2nd, check that the painting context has received the default clip behavior.
+ final TestClipPaintingContext context = TestClipPaintingContext();
+ renderObject.paint(context, Offset.zero);
+ expect(context.clipBehavior, equals(Clip.hardEdge));
+
+ // 3rd, pump a new widget to check that the render object can update its clip behavior.
+ await tester.pumpWidget(build(
+ NestedScrollView(
+ headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) => <Widget>[const SliverAppBar()],
+ body: Container(height: 2000.0),
+ clipBehavior: Clip.antiAlias,
+ )
+ ));
+ expect(renderObject.clipBehavior, equals(Clip.antiAlias));
+
+ // 4th, check that a non-default clip behavior can be sent to the painting context.
+ renderObject.paint(context, Offset.zero);
+ expect(context.clipBehavior, equals(Clip.antiAlias));
+ });
+
testWidgets('NestedScrollView overscroll and release and hold', (WidgetTester tester) async {
await tester.pumpWidget(buildTest());
expect(find.text('aaa2'), findsOneWidget);
diff --git a/packages/flutter/test/widgets/shrink_wrapping_viewport_test.dart b/packages/flutter/test/widgets/shrink_wrapping_viewport_test.dart
new file mode 100644
index 0000000..f377e11
--- /dev/null
+++ b/packages/flutter/test/widgets/shrink_wrapping_viewport_test.dart
@@ -0,0 +1,50 @@
+// Copyright 2014 The Flutter 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/material.dart';
+import 'package:flutter/rendering.dart';
+
+import '../rendering/rendering_tester.dart';
+
+void main() {
+ testWidgets('ShrinkWrappingViewport respects clipBehavior', (WidgetTester tester) async {
+ Widget build(ShrinkWrappingViewport child) {
+ return Directionality(
+ textDirection: TextDirection.ltr,
+ child: child,
+ );
+ }
+
+ await tester.pumpWidget(build(
+ ShrinkWrappingViewport(
+ offset: ViewportOffset.zero(),
+ slivers: <Widget>[SliverToBoxAdapter(child: Container(height: 2000.0))],
+ )
+ ));
+
+ // 1st, check that the render object has received the default clip behavior.
+ final RenderShrinkWrappingViewport renderObject = tester.allRenderObjects.whereType<RenderShrinkWrappingViewport>().first;
+ expect(renderObject.clipBehavior, equals(Clip.hardEdge));
+
+ // 2nd, check that the painting context has received the default clip behavior.
+ final TestClipPaintingContext context = TestClipPaintingContext();
+ renderObject.paint(context, Offset.zero);
+ expect(context.clipBehavior, equals(Clip.hardEdge));
+
+ // 3rd, pump a new widget to check that the render object can update its clip behavior.
+ await tester.pumpWidget(build(
+ ShrinkWrappingViewport(
+ offset: ViewportOffset.zero(),
+ slivers: <Widget>[SliverToBoxAdapter(child: Container(height: 2000.0))],
+ clipBehavior: Clip.antiAlias,
+ )
+ ));
+ expect(renderObject.clipBehavior, equals(Clip.antiAlias));
+
+ // 4th, check that a non-default clip behavior can be sent to the painting context.
+ renderObject.paint(context, Offset.zero);
+ expect(context.clipBehavior, equals(Clip.antiAlias));
+ });
+}
diff --git a/packages/flutter/test/widgets/single_child_scroll_view_test.dart b/packages/flutter/test/widgets/single_child_scroll_view_test.dart
index be96c1c..43dd611 100644
--- a/packages/flutter/test/widgets/single_child_scroll_view_test.dart
+++ b/packages/flutter/test/widgets/single_child_scroll_view_test.dart
@@ -8,6 +8,7 @@
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
+import '../rendering/rendering_tester.dart';
import 'semantics_tester.dart';
class TestScrollPosition extends ScrollPositionWithSingleContext {
@@ -37,6 +38,27 @@
}
void main() {
+ testWidgets('SingleChildScrollView respects clipBehavior', (WidgetTester tester) async {
+ await tester.pumpWidget(SingleChildScrollView(child: Container(height: 2000.0)));
+
+ // 1st, check that the render object has received the default clip behavior.
+ final dynamic renderObject = tester.allRenderObjects.where((RenderObject o) => o.runtimeType.toString() == '_RenderSingleChildViewport').first;
+ expect(renderObject.clipBehavior, equals(Clip.hardEdge));
+
+ // 2nd, check that the painting context has received the default clip behavior.
+ final TestClipPaintingContext context = TestClipPaintingContext();
+ renderObject.paint(context, Offset.zero);
+ expect(context.clipBehavior, equals(Clip.hardEdge));
+
+ // 3rd, pump a new widget to check that the render object can update its clip behavior.
+ await tester.pumpWidget(SingleChildScrollView(clipBehavior: Clip.antiAlias, child: Container(height: 2000.0)));
+ expect(renderObject.clipBehavior, equals(Clip.antiAlias));
+
+ // 4th, check that a non-default clip behavior can be sent to the painting context.
+ renderObject.paint(context, Offset.zero);
+ expect(context.clipBehavior, equals(Clip.antiAlias));
+ });
+
testWidgets('SingleChildScrollView control test', (WidgetTester tester) async {
await tester.pumpWidget(SingleChildScrollView(
child: Container(
diff --git a/packages/flutter/test/widgets/stack_test.dart b/packages/flutter/test/widgets/stack_test.dart
index 7447140..878ac4a 100644
--- a/packages/flutter/test/widgets/stack_test.dart
+++ b/packages/flutter/test/widgets/stack_test.dart
@@ -376,6 +376,15 @@
expect(renderBox.size.height, equals(12.0));
});
+ testWidgets('Can set and update clipBehavior', (WidgetTester tester) async {
+ await tester.pumpWidget(Stack(textDirection: TextDirection.ltr));
+ final RenderStack renderObject = tester.allRenderObjects.whereType<RenderStack>().first;
+ expect(renderObject.clipBehavior, equals(Clip.hardEdge));
+
+ await tester.pumpWidget(Stack(textDirection: TextDirection.ltr, clipBehavior: Clip.hardEdge));
+ expect(renderObject.clipBehavior, equals(Clip.hardEdge));
+ });
+
testWidgets('IndexedStack with null index', (WidgetTester tester) async {
bool tapped;
@@ -412,6 +421,7 @@
textDirection: TextDirection.ltr,
child: Center(
child: Stack(
+ clipBehavior: Clip.hardEdge,
children: const <Widget>[
SizedBox(
width: 100.0,
@@ -442,6 +452,7 @@
child: Center(
child: Stack(
overflow: Overflow.visible,
+ clipBehavior: Clip.none,
children: const <Widget>[
SizedBox(
width: 100.0,
diff --git a/packages/flutter/test/widgets/wrap_test.dart b/packages/flutter/test/widgets/wrap_test.dart
index 46b7b83..d94884d 100644
--- a/packages/flutter/test/widgets/wrap_test.dart
+++ b/packages/flutter/test/widgets/wrap_test.dart
@@ -733,6 +733,7 @@
await tester.pumpWidget(Wrap(
textDirection: TextDirection.ltr,
+ clipBehavior: Clip.hardEdge,
children: const <Widget>[
SizedBox(width: 500.0, height: 500.0),
SizedBox(width: 500.0, height: 500.0),
@@ -895,4 +896,13 @@
const Offset(0.0, 20.0),
]);
});
+
+ testWidgets('Wrap can set and update clipBehavior', (WidgetTester tester) async {
+ await tester.pumpWidget(Wrap(textDirection: TextDirection.ltr));
+ final RenderWrap renderObject = tester.allRenderObjects.whereType<RenderWrap>().first;
+ expect(renderObject.clipBehavior, equals(Clip.hardEdge));
+
+ await tester.pumpWidget(Wrap(textDirection: TextDirection.ltr, clipBehavior: Clip.antiAlias));
+ expect(renderObject.clipBehavior, equals(Clip.antiAlias));
+ });
}