Normalize assert checking of clipBehavior (#38568)

I noticed that we were pretty inconsistent with the way that we checked the value of clipBehavior in the framework, so I normalized the usages and updated docs where necessary.

This is a breaking change if you used to pass null explicitly to FlatButton, OutlineButton or RaisedButton constructors, expecting to get Clip.none. It will now assert if you do that. Existing implementations that pass null implicitly by not specifying clipBehavior won't need to change their call sites. It always implicitly defaulted to Clip.none before, and it will continue to do that, it's only places where it was explicitly set to null in order to get the implicit default that it will fail.
This commit is contained in:
Greg Spencer 2019-08-23 14:08:04 -07:00 committed by GitHub
parent 53168db99a
commit 365f577c70
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 173 additions and 51 deletions

View file

@ -42,7 +42,7 @@ import 'theme.dart';
class BottomAppBar extends StatefulWidget {
/// Creates a bottom application bar.
///
/// The [clipBehavior] argument must not be null.
/// The [clipBehavior] argument defaults to [Clip.none] and must not be null.
/// Additionally, [elevation] must be non-negative.
///
/// If [color], [elevation], or [shape] are null, their [BottomAppBarTheme] values will be used.
@ -56,9 +56,9 @@ class BottomAppBar extends StatefulWidget {
this.clipBehavior = Clip.none,
this.notchMargin = 4.0,
this.child,
}) : assert(clipBehavior != null),
assert(elevation == null || elevation >= 0.0),
}) : assert(elevation == null || elevation >= 0.0),
assert(notchMargin != null),
assert(clipBehavior != null),
super(key: key);
/// The widget below this widget in the tree.
@ -92,6 +92,8 @@ class BottomAppBar extends StatefulWidget {
final NotchedShape shape;
/// {@macro flutter.widgets.Clip}
///
/// Defaults to [Clip.none], and must not be null.
final Clip clipBehavior;
/// The margin between the [FloatingActionButton] and the [BottomAppBar]'s

View file

@ -241,6 +241,8 @@ class RawMaterialButton extends StatefulWidget {
final bool autofocus;
/// {@macro flutter.widgets.Clip}
///
/// Defaults to [Clip.none], and must not be null.
final Clip clipBehavior;
@override

View file

@ -145,6 +145,7 @@ class Card extends StatelessWidget {
final bool borderOnForeground;
/// {@macro flutter.widgets.Clip}
///
/// If this property is null then [ThemeData.cardTheme.clipBehavior] is used.
/// If that's null then the behavior will be [Clip.none].
final Clip clipBehavior;
@ -177,7 +178,6 @@ class Card extends StatelessWidget {
final Widget child;
static const double _defaultElevation = 1.0;
static const Clip _defaultClipBehavior = Clip.none;
@override
Widget build(BuildContext context) {
@ -195,7 +195,7 @@ class Card extends StatelessWidget {
borderRadius: BorderRadius.all(Radius.circular(4.0)),
),
borderOnForeground: borderOnForeground,
clipBehavior: clipBehavior ?? cardTheme.clipBehavior ?? _defaultClipBehavior,
clipBehavior: clipBehavior ?? cardTheme.clipBehavior ?? Clip.none,
child: Semantics(
explicitChildNodes: !semanticContainer,
child: child,

View file

@ -96,6 +96,8 @@ abstract class ChipAttributes {
ShapeBorder get shape;
/// {@macro flutter.widgets.Clip}
///
/// Defaults to [Clip.none], and must not be null.
Clip get clipBehavior;
/// {@macro flutter.widgets.Focus.focusNode}
@ -1227,6 +1229,7 @@ class ActionChip extends StatelessWidget implements ChipAttributes, TappableChip
this.elevation,
this.shadowColor,
}) : assert(label != null),
assert(clipBehavior != null),
assert(autofocus != null),
assert(
onPressed != null,

View file

@ -115,12 +115,13 @@ class FlatButton extends MaterialButton {
Brightness colorBrightness,
EdgeInsetsGeometry padding,
ShapeBorder shape,
Clip clipBehavior,
Clip clipBehavior = Clip.none,
FocusNode focusNode,
bool autofocus = false,
MaterialTapTargetSize materialTapTargetSize,
@required Widget child,
}) : assert(autofocus != null),
}) : assert(clipBehavior != null),
assert(autofocus != null),
super(
key: key,
onPressed: onPressed,
@ -196,7 +197,7 @@ class FlatButton extends MaterialButton {
padding: buttonTheme.getPadding(this),
constraints: buttonTheme.getConstraints(this),
shape: buttonTheme.getShape(this),
clipBehavior: clipBehavior ?? Clip.none,
clipBehavior: clipBehavior,
focusNode: focusNode,
autofocus: autofocus,
materialTapTargetSize: buttonTheme.getMaterialTapTargetSize(this),
@ -227,7 +228,7 @@ class _FlatButtonWithIcon extends FlatButton with MaterialButtonWithIconMixin {
Brightness colorBrightness,
EdgeInsetsGeometry padding,
ShapeBorder shape,
Clip clipBehavior,
Clip clipBehavior = Clip.none,
FocusNode focusNode,
bool autofocus = false,
MaterialTapTargetSize materialTapTargetSize,
@ -235,6 +236,7 @@ class _FlatButtonWithIcon extends FlatButton with MaterialButtonWithIconMixin {
@required Widget label,
}) : assert(icon != null),
assert(label != null),
assert(clipBehavior != null),
assert(autofocus != null),
super(
key: key,

View file

@ -122,7 +122,7 @@ class _DefaultHeroTag {
class FloatingActionButton extends StatelessWidget {
/// Creates a circular floating action button.
///
/// The [mini] and [clipBehavior] arguments must be non-null. Additionally,
/// The [mini] and [clipBehavior] arguments must not be null. Additionally,
/// [elevation], [highlightElevation], and [disabledElevation] (if specified)
/// must be non-negative.
const FloatingActionButton({
@ -154,6 +154,7 @@ class FloatingActionButton extends StatelessWidget {
assert(highlightElevation == null || highlightElevation >= 0.0),
assert(disabledElevation == null || disabledElevation >= 0.0),
assert(mini != null),
assert(clipBehavior != null),
assert(isExtended != null),
assert(autofocus != null),
_sizeConstraints = mini ? _kMiniSizeConstraints : _kSizeConstraints,
@ -162,7 +163,7 @@ class FloatingActionButton extends StatelessWidget {
/// Creates a wider [StadiumBorder]-shaped floating action button with
/// an optional [icon] and a [label].
///
/// The [label], [autofocus], and [clipBehavior] arguments must non-null.
/// The [label], [autofocus], and [clipBehavior] arguments must not be null.
/// Additionally, [elevation], [highlightElevation], and [disabledElevation]
/// (if specified) must be non-negative.
FloatingActionButton.extended({
@ -194,6 +195,7 @@ class FloatingActionButton extends StatelessWidget {
assert(highlightElevation == null || highlightElevation >= 0.0),
assert(disabledElevation == null || disabledElevation >= 0.0),
assert(isExtended != null),
assert(clipBehavior != null),
assert(autofocus != null),
_sizeConstraints = _kExtendedSizeConstraints,
mini = false,
@ -363,6 +365,8 @@ class FloatingActionButton extends StatelessWidget {
final ShapeBorder shape;
/// {@macro flutter.widgets.Clip}
///
/// Defaults to [Clip.none], and must not be null.
final Clip clipBehavior;
/// True if this is an "extended" floating action button.
@ -463,7 +467,7 @@ class FloatingActionButton extends StatelessWidget {
splashColor: splashColor,
textStyle: textStyle,
shape: shape,
clipBehavior: clipBehavior ?? Clip.none,
clipBehavior: clipBehavior,
focusNode: focusNode,
autofocus: autofocus,
child: child,

View file

@ -157,9 +157,9 @@ abstract class MaterialInkController {
class Material extends StatefulWidget {
/// Creates a piece of material.
///
/// The [type], [elevation], [shadowColor], [borderOnForeground] and
/// [animationDuration] arguments must not be null. Additionally, [elevation]
/// must be non-negative.
/// The [type], [elevation], [shadowColor], [borderOnForeground],
/// [clipBehavior], and [animationDuration] arguments must not be null.
/// Additionally, [elevation] must be non-negative.
///
/// If a [shape] is specified, then the [borderRadius] property must be
/// null and the [type] property must not be [MaterialType.circle]. If the
@ -185,8 +185,8 @@ class Material extends StatefulWidget {
assert(!(shape != null && borderRadius != null)),
assert(animationDuration != null),
assert(!(identical(type, MaterialType.circle) && (borderRadius != null || shape != null))),
assert(clipBehavior != null),
assert(borderOnForeground != null),
assert(clipBehavior != null),
super(key: key);
/// The widget below this widget in the tree.
@ -264,6 +264,8 @@ class Material extends StatefulWidget {
/// See the enum [Clip] for details of all possible options and their common
/// use cases.
/// {@endtemplate}
///
/// Defaults to [Clip.none], and must not be null.
final Clip clipBehavior;
/// Defines the duration of animated changes for [shape], [elevation],
@ -670,6 +672,11 @@ class ShapeBorderTween extends Tween<ShapeBorder> {
///
/// Animates [elevation], [shadowColor], and [shape].
class _MaterialInterior extends ImplicitlyAnimatedWidget {
/// Creates a const instance of [_MaterialInterior].
///
/// The [child], [shape], [clipBehavior], [color], and [shadowColor] arguments
/// must not be null. The [elevation] must be specified and greater than or
/// equal to zero.
const _MaterialInterior({
Key key,
@required this.child,
@ -707,6 +714,8 @@ class _MaterialInterior extends ImplicitlyAnimatedWidget {
final bool borderOnForeground;
/// {@macro flutter.widgets.Clip}
///
/// Defaults to [Clip.none], and must not be null.
final Clip clipBehavior;
/// The target z-coordinate at which to place this physical object relative

View file

@ -77,7 +77,8 @@ class MaterialButton extends StatelessWidget {
this.minWidth,
this.height,
this.child,
}) : assert(autofocus != null),
}) : assert(clipBehavior != null),
assert(autofocus != null),
assert(elevation == null || elevation >= 0.0),
assert(focusElevation == null || focusElevation >= 0.0),
assert(hoverElevation == null || hoverElevation >= 0.0),
@ -306,6 +307,8 @@ class MaterialButton extends StatelessWidget {
final ShapeBorder shape;
/// {@macro flutter.widgets.Clip}
///
/// Defaults to [Clip.none], and must not be null.
final Clip clipBehavior;
/// {@macro flutter.widgets.Focus.focusNode}
@ -362,7 +365,7 @@ class MaterialButton extends StatelessWidget {
minHeight: height,
),
shape: buttonTheme.getShape(this),
clipBehavior: clipBehavior ?? Clip.none,
clipBehavior: clipBehavior,
focusNode: focusNode,
animationDuration: buttonTheme.getAnimationDuration(this),
child: child,

View file

@ -75,11 +75,12 @@ class OutlineButton extends MaterialButton {
this.highlightedBorderColor,
EdgeInsetsGeometry padding,
ShapeBorder shape,
Clip clipBehavior,
Clip clipBehavior = Clip.none,
FocusNode focusNode,
bool autofocus = false,
Widget child,
}) : assert(highlightElevation == null || highlightElevation >= 0.0),
assert(clipBehavior != null),
assert(autofocus != null),
super(
key: key,
@ -220,12 +221,13 @@ class _OutlineButtonWithIcon extends OutlineButton with MaterialButtonWithIconMi
BorderSide borderSide,
EdgeInsetsGeometry padding,
ShapeBorder shape,
Clip clipBehavior,
Clip clipBehavior = Clip.none,
FocusNode focusNode,
bool autofocus = false,
@required Widget icon,
@required Widget label,
}) : assert(highlightElevation == null || highlightElevation >= 0.0),
assert(clipBehavior != null),
assert(autofocus != null),
assert(icon != null),
assert(label != null),
@ -279,12 +281,13 @@ class _OutlineButton extends StatefulWidget {
@required this.highlightedBorderColor,
this.padding,
this.shape,
this.clipBehavior,
this.clipBehavior = Clip.none,
this.focusNode,
this.autofocus = false,
this.child,
}) : assert(highlightElevation != null && highlightElevation >= 0.0),
assert(highlightedBorderColor != null),
assert(clipBehavior != null),
assert(autofocus != null),
super(key: key);

View file

@ -126,7 +126,7 @@ class RaisedButton extends MaterialButton {
double disabledElevation,
EdgeInsetsGeometry padding,
ShapeBorder shape,
Clip clipBehavior,
Clip clipBehavior = Clip.none,
FocusNode focusNode,
bool autofocus = false,
MaterialTapTargetSize materialTapTargetSize,
@ -138,6 +138,7 @@ class RaisedButton extends MaterialButton {
assert(hoverElevation == null || hoverElevation >= 0.0),
assert(highlightElevation == null || highlightElevation >= 0.0),
assert(disabledElevation == null || disabledElevation >= 0.0),
assert(clipBehavior != null),
super(
key: key,
onPressed: onPressed,
@ -209,7 +210,7 @@ class RaisedButton extends MaterialButton {
return RawMaterialButton(
onPressed: onPressed,
onHighlightChanged: onHighlightChanged,
clipBehavior: clipBehavior ?? Clip.none,
clipBehavior: clipBehavior,
fillColor: buttonTheme.getFillColor(this),
textStyle: theme.textTheme.button.copyWith(color: buttonTheme.getTextColor(this)),
focusColor: buttonTheme.getFocusColor(this),
@ -276,6 +277,7 @@ class _RaisedButtonWithIcon extends RaisedButton with MaterialButtonWithIconMixi
}) : assert(elevation == null || elevation >= 0.0),
assert(highlightElevation == null || highlightElevation >= 0.0),
assert(disabledElevation == null || disabledElevation >= 0.0),
assert(clipBehavior != null),
assert(icon != null),
assert(label != null),
assert(autofocus != null),

View file

@ -1058,8 +1058,10 @@ class OffsetLayer extends ContainerLayer {
class ClipRectLayer extends ContainerLayer {
/// Creates a layer with a rectangular clip.
///
/// The [clipRect] and [clipBehavior] properties must be non-null before the
/// compositing phase of the pipeline.
/// The [clipRect] argument must not be null before the compositing phase of
/// the pipeline.
///
/// The [clipBehavior] argument must not be null, and must not be [Clip.none].
ClipRectLayer({
Rect clipRect,
Clip clipBehavior = Clip.hardEdge,
@ -1082,10 +1084,12 @@ class ClipRectLayer extends ContainerLayer {
}
/// {@template flutter.clipper.clipBehavior}
/// Controls how to clip (defaults to [Clip.hardEdge]).
/// Controls how to clip.
///
/// [Clip.none] is not allowed here.
/// Must not be set to null or [Clip.none].
/// {@endtemplate}
///
/// Defaults to [Clip.hardEdge].
Clip get clipBehavior => _clipBehavior;
Clip _clipBehavior;
set clipBehavior(Clip value) {
@ -1170,6 +1174,8 @@ class ClipRRectLayer extends ContainerLayer {
}
/// {@macro flutter.clipper.clipBehavior}
///
/// Defaults to [Clip.antiAlias].
Clip get clipBehavior => _clipBehavior;
Clip _clipBehavior;
set clipBehavior(Clip value) {
@ -1254,6 +1260,8 @@ class ClipPathLayer extends ContainerLayer {
}
/// {@macro flutter.clipper.clipBehavior}
///
/// Defaults to [Clip.antiAlias].
Clip get clipBehavior => _clipBehavior;
Clip _clipBehavior;
set clipBehavior(Clip value) {

View file

@ -1285,12 +1285,14 @@ class RenderClipRect extends _RenderCustomClip<Rect> {
/// If [clipper] is null, the clip will match the layout size and position of
/// the child.
///
/// The [clipBehavior] cannot be [Clip.none].
/// The [clipBehavior] must not be null or [Clip.none].
RenderClipRect({
RenderBox child,
CustomClipper<Rect> clipper,
Clip clipBehavior = Clip.antiAlias,
}) : super(child: child, clipper: clipper, clipBehavior: clipBehavior);
}) : assert(clipBehavior != null),
assert(clipBehavior != Clip.none),
super(child: child, clipper: clipper, clipBehavior: clipBehavior);
@override
Rect get _defaultClip => Offset.zero & size;
@ -1342,13 +1344,14 @@ class RenderClipRRect extends _RenderCustomClip<RRect> {
///
/// If [clipper] is non-null, then [borderRadius] is ignored.
///
/// The [clipBehavior] cannot be [Clip.none].
/// The [clipBehavior] argument must not be null or [Clip.none].
RenderClipRRect({
RenderBox child,
BorderRadius borderRadius = BorderRadius.zero,
CustomClipper<RRect> clipper,
Clip clipBehavior = Clip.antiAlias,
}) : assert(clipBehavior != Clip.none),
}) : assert(clipBehavior != null),
assert(clipBehavior != Clip.none),
_borderRadius = borderRadius,
super(child: child, clipper: clipper, clipBehavior: clipBehavior) {
assert(_borderRadius != null || clipper != null);
@ -1418,12 +1421,13 @@ class RenderClipOval extends _RenderCustomClip<Rect> {
/// If [clipper] is null, the oval will be inscribed into the layout size and
/// position of the child.
///
/// The [clipBehavior] cannot be [Clip.none].
/// The [clipBehavior] argument must not be null or [Clip.none].
RenderClipOval({
RenderBox child,
CustomClipper<Rect> clipper,
Clip clipBehavior = Clip.antiAlias,
}) : assert(clipBehavior != Clip.none),
}) : assert(clipBehavior != null),
assert(clipBehavior != Clip.none),
super(child: child, clipper: clipper, clipBehavior: clipBehavior);
Rect _cachedRect;
@ -1496,12 +1500,13 @@ class RenderClipPath extends _RenderCustomClip<Path> {
/// consider using a [RenderClipRect], which can achieve the same effect more
/// efficiently.
///
/// The [clipBehavior] cannot be [Clip.none].
/// The [clipBehavior] argument must not be null or [Clip.none].
RenderClipPath({
RenderBox child,
CustomClipper<Path> clipper,
Clip clipBehavior = Clip.antiAlias,
}) : assert(clipBehavior != Clip.none),
}) : assert(clipBehavior != null),
assert(clipBehavior != Clip.none),
super(child: child, clipper: clipper, clipBehavior: clipBehavior);
@override
@ -1632,8 +1637,9 @@ class RenderPhysicalModel extends _RenderPhysicalModelBase<RRect> {
///
/// The [color] is required.
///
/// The [shape], [elevation], [color], and [shadowColor] must not be null.
/// Additionally, the [elevation] must be non-negative.
/// The [shape], [elevation], [color], [clipBehavior], and [shadowColor]
/// arguments must not be null. Additionally, the [elevation] must be
/// non-negative.
RenderPhysicalModel({
RenderBox child,
BoxShape shape = BoxShape.rectangle,

View file

@ -582,19 +582,29 @@ class ClipRect extends SingleChildRenderObjectWidget {
///
/// If [clipper] is null, the clip will match the layout size and position of
/// the child.
const ClipRect({ Key key, this.clipper, this.clipBehavior = Clip.hardEdge, Widget child }) : super(key: key, child: child);
///
/// The [clipBehavior] argument must not be null or [Clip.none].
const ClipRect({ Key key, this.clipper, this.clipBehavior = Clip.hardEdge, Widget child })
: assert(clipBehavior != null),
super(key: key, child: child);
/// If non-null, determines which clip to use.
final CustomClipper<Rect> clipper;
/// {@macro flutter.clipper.clipBehavior}
///
/// Defaults to [Clip.hardEdge].
final Clip clipBehavior;
@override
RenderClipRect createRenderObject(BuildContext context) => RenderClipRect(clipper: clipper, clipBehavior: clipBehavior);
RenderClipRect createRenderObject(BuildContext context) {
assert(clipBehavior != Clip.none);
return RenderClipRect(clipper: clipper, clipBehavior: clipBehavior);
}
@override
void updateRenderObject(BuildContext context, RenderClipRect renderObject) {
assert(clipBehavior != Clip.none);
renderObject
..clipper = clipper
..clipBehavior = clipBehavior;
@ -633,6 +643,8 @@ class ClipRRect extends SingleChildRenderObjectWidget {
/// right-angled corners.
///
/// If [clipper] is non-null, then [borderRadius] is ignored.
///
/// The [clipBehavior] argument must not be null or [Clip.none].
const ClipRRect({
Key key,
this.borderRadius,
@ -655,13 +667,19 @@ class ClipRRect extends SingleChildRenderObjectWidget {
final CustomClipper<RRect> clipper;
/// {@macro flutter.clipper.clipBehavior}
///
/// Defaults to [Clip.antiAlias].
final Clip clipBehavior;
@override
RenderClipRRect createRenderObject(BuildContext context) => RenderClipRRect(borderRadius: borderRadius, clipper: clipper, clipBehavior: clipBehavior);
RenderClipRRect createRenderObject(BuildContext context) {
assert(clipBehavior != Clip.none);
return RenderClipRRect(borderRadius: borderRadius, clipper: clipper, clipBehavior: clipBehavior);
}
@override
void updateRenderObject(BuildContext context, RenderClipRRect renderObject) {
assert(clipBehavior != Clip.none);
renderObject
..borderRadius = borderRadius
..clipBehavior = clipBehavior
@ -693,7 +711,11 @@ class ClipOval extends SingleChildRenderObjectWidget {
///
/// If [clipper] is null, the oval will be inscribed into the layout size and
/// position of the child.
const ClipOval({ Key key, this.clipper, this.clipBehavior = Clip.antiAlias, Widget child }) : super(key: key, child: child);
///
/// The [clipBehavior] argument must not be null or [Clip.none].
const ClipOval({Key key, this.clipper, this.clipBehavior = Clip.antiAlias, Widget child})
: assert(clipBehavior != null),
super(key: key, child: child);
/// If non-null, determines which clip to use.
///
@ -707,13 +729,19 @@ class ClipOval extends SingleChildRenderObjectWidget {
final CustomClipper<Rect> clipper;
/// {@macro flutter.clipper.clipBehavior}
///
/// Defaults to [Clip.antiAlias].
final Clip clipBehavior;
@override
RenderClipOval createRenderObject(BuildContext context) => RenderClipOval(clipper: clipper, clipBehavior: clipBehavior);
RenderClipOval createRenderObject(BuildContext context) {
assert(clipBehavior != Clip.none);
return RenderClipOval(clipper: clipper, clipBehavior: clipBehavior);
}
@override
void updateRenderObject(BuildContext context, RenderClipOval renderObject) {
assert(clipBehavior != Clip.none);
renderObject
..clipper = clipper
..clipBehavior = clipBehavior;
@ -754,12 +782,15 @@ class ClipPath extends SingleChildRenderObjectWidget {
/// size and location of the child. However, rather than use this default,
/// consider using a [ClipRect], which can achieve the same effect more
/// efficiently.
///
/// The [clipBehavior] argument must not be null or [Clip.none].
const ClipPath({
Key key,
this.clipper,
this.clipBehavior = Clip.antiAlias,
Widget child,
}) : super(key: key, child: child);
}) : assert(clipBehavior != null),
super(key: key, child: child);
/// Creates a shape clip.
///
@ -771,6 +802,8 @@ class ClipPath extends SingleChildRenderObjectWidget {
Clip clipBehavior = Clip.antiAlias,
Widget child,
}) {
assert(clipBehavior != null);
assert(clipBehavior != Clip.none);
assert(shape != null);
return Builder(
key: key,
@ -795,13 +828,19 @@ class ClipPath extends SingleChildRenderObjectWidget {
final CustomClipper<Path> clipper;
/// {@macro flutter.clipper.clipBehavior}
///
/// Defaults to [Clip.antiAlias].
final Clip clipBehavior;
@override
RenderClipPath createRenderObject(BuildContext context) => RenderClipPath(clipper: clipper, clipBehavior: clipBehavior);
RenderClipPath createRenderObject(BuildContext context) {
assert(clipBehavior != Clip.none);
return RenderClipPath(clipper: clipper, clipBehavior: clipBehavior);
}
@override
void updateRenderObject(BuildContext context, RenderClipPath renderObject) {
assert(clipBehavior != Clip.none);
renderObject
..clipper = clipper
..clipBehavior = clipBehavior;
@ -838,8 +877,8 @@ class PhysicalModel extends SingleChildRenderObjectWidget {
///
/// The [color] is required; physical things have a color.
///
/// The [shape], [elevation], [color], and [shadowColor] must not be null.
/// Additionally, the [elevation] must be non-negative.
/// The [shape], [elevation], [color], [clipBehavior], and [shadowColor] must
/// not be null. Additionally, the [elevation] must be non-negative.
const PhysicalModel({
Key key,
this.shape = BoxShape.rectangle,
@ -853,12 +892,15 @@ class PhysicalModel extends SingleChildRenderObjectWidget {
assert(elevation != null && elevation >= 0.0),
assert(color != null),
assert(shadowColor != null),
assert(clipBehavior != null),
super(key: key, child: child);
/// The type of shape.
final BoxShape shape;
/// {@macro flutter.widgets.Clip}
///
/// Defaults to [Clip.none].
final Clip clipBehavior;
/// The border radius of the rounded corners.
@ -931,8 +973,8 @@ class PhysicalShape extends SingleChildRenderObjectWidget {
///
/// The [color] is required; physical things have a color.
///
/// The [clipper], [elevation], [color], and [shadowColor] must not be null.
/// Additionally, the [elevation] must be non-negative.
/// The [clipper], [elevation], [color], [clipBehavior], and [shadowColor]
/// must not be null. Additionally, the [elevation] must be non-negative.
const PhysicalShape({
Key key,
@required this.clipper,
@ -956,6 +998,8 @@ class PhysicalShape extends SingleChildRenderObjectWidget {
final CustomClipper<Path> clipper;
/// {@macro flutter.widgets.Clip}
///
/// Defaults to [Clip.none].
final Clip clipBehavior;
/// The z-coordinate relative to the parent at which to place this physical

View file

@ -1450,9 +1450,9 @@ class _AnimatedDefaultTextStyleState extends AnimatedWidgetBaseState<AnimatedDef
class AnimatedPhysicalModel extends ImplicitlyAnimatedWidget {
/// Creates a widget that animates the properties of a [PhysicalModel].
///
/// The [child], [shape], [borderRadius], [elevation], [color], [shadowColor], [curve], and
/// [duration] arguments must not be null. Additionally, [elevation] must be
/// non-negative.
/// The [child], [shape], [borderRadius], [elevation], [color], [shadowColor],
/// [curve], [clipBehavior], and [duration] arguments must not be null.
/// Additionally, [elevation] must be non-negative.
///
/// Animating [color] is optional and is controlled by the [animateColor] flag.
///
@ -1492,6 +1492,8 @@ class AnimatedPhysicalModel extends ImplicitlyAnimatedWidget {
final BoxShape shape;
/// {@macro flutter.widgets.Clip}
///
/// Defaults to [Clip.none].
final Clip clipBehavior;
/// The target border radius of the rounded corners for a rectangle shape.

View file

@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/painting.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
@ -171,4 +172,19 @@ void main() {
await tester.pumpWidget(const Card(clipBehavior: Clip.antiAlias));
expect(tester.widget<Material>(find.byType(Material)).clipBehavior, Clip.antiAlias);
});
testWidgets('Card clipBehavior property defers to theme when null', (WidgetTester tester) async {
await tester.pumpWidget(Builder(builder: (BuildContext context) {
final ThemeData themeData = Theme.of(context);
return Theme(
data: themeData.copyWith(
cardTheme: themeData.cardTheme.copyWith(
clipBehavior: Clip.antiAliasWithSaveLayer,
),
),
child: const Card(clipBehavior: null),
);
}));
expect(tester.widget<Material>(find.byType(Material)).clipBehavior, Clip.antiAliasWithSaveLayer);
});
}

View file

@ -300,6 +300,22 @@ void main() {
expect(find.byKey(materialKey), hasNoImmediateClip);
});
testWidgets('Null clipBehavior asserts', (WidgetTester tester) async {
final GlobalKey materialKey = GlobalKey();
Future<void> doPump() async {
await tester.pumpWidget(
Material(
key: materialKey,
type: MaterialType.transparency,
child: const SizedBox(width: 100.0, height: 100.0),
clipBehavior: null,
)
);
}
expect(() async => doPump(), throwsAssertionError);
});
testWidgets('clips to bounding rect by default given Clip.antiAlias', (WidgetTester tester) async {
final GlobalKey materialKey = GlobalKey();
await tester.pumpWidget(