Unverified Commit 327e3eff authored by rami-a's avatar rami-a Committed by GitHub

Add Material/Card borderOnForeground flag to allow border to be painted behind...

Add Material/Card borderOnForeground flag to allow border to be painted behind the child widget (#27297)

In certain situations, a developer may require the border of a Material to be painted behind its child. For example a Card widget that has a full width image across the top half. In that scenario, the image should ideally be painted above the border with regards to z-position.

This change exposes a flag on Material widget to achieve this behavior. Additionally, the same flag is exposed on Card widget to allow the Card widget to pass this down to its Material.

I added a couple golden tests to verify this new behavior. Goldens are here:
https://github.com/flutter/goldens/commit/46a3d26acbb1b0d72b6b02c30f03b9dbda7d5bdf
parent 3d2f9849
b530d67675a5aa9c5458b93019ce91e20ad88758
46a3d26acbb1b0d72b6b02c30f03b9dbda7d5bdf
......@@ -66,17 +66,20 @@ import 'theme.dart';
class Card extends StatelessWidget {
/// Creates a material design card.
///
/// The [elevation] must be null or non-negative.
/// The [elevation] must be null or non-negative. The [borderOnForeground]
/// must not be null.
const Card({
Key key,
this.color,
this.elevation,
this.shape,
this.borderOnForeground = true,
this.margin,
this.clipBehavior,
this.child,
this.semanticContainer = true,
}) : assert(elevation == null || elevation >= 0.0),
assert(borderOnForeground != null),
super(key: key);
/// The card's background color.
......@@ -105,6 +108,12 @@ class Card extends StatelessWidget {
/// circular corner radius of 4.0.
final ShapeBorder shape;
/// Whether to paint the [shape] border in front of the [child].
///
/// The default value is true.
/// If false, the border will be painted behind the [child].
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].
......@@ -155,6 +164,7 @@ class Card extends StatelessWidget {
shape: shape ?? cardTheme.shape ?? const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(4.0)),
),
borderOnForeground: borderOnForeground,
clipBehavior: clipBehavior ?? cardTheme.clipBehavior ?? _defaultClipBehavior,
child: Semantics(
explicitChildNodes: !semanticContainer,
......
......@@ -155,8 +155,9 @@ abstract class MaterialInkController {
class Material extends StatefulWidget {
/// Creates a piece of material.
///
/// The [type], [elevation], [shadowColor], and [animationDuration] arguments
/// must not be null. Additionally, [elevation] must be non-negative.
/// The [type], [elevation], [shadowColor], [borderOnForeground] 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
......@@ -172,6 +173,7 @@ class Material extends StatefulWidget {
this.textStyle,
this.borderRadius,
this.shape,
this.borderOnForeground = true,
this.clipBehavior = Clip.none,
this.animationDuration = kThemeChangeDuration,
this.child,
......@@ -182,6 +184,7 @@ class Material extends StatefulWidget {
assert(animationDuration != null),
assert(!(identical(type, MaterialType.circle) && (borderRadius != null || shape != null))),
assert(clipBehavior != null),
assert(borderOnForeground != null),
super(key: key);
/// The widget below this widget in the tree.
......@@ -234,6 +237,12 @@ class Material extends StatefulWidget {
/// zero.
final ShapeBorder shape;
/// Whether to paint the [shape] border in front of the [child].
///
/// The default value is true.
/// If false, the border will be painted behind the [child].
final bool borderOnForeground;
/// {@template flutter.widgets.Clip}
/// The content will be clipped (or not) according to this option.
///
......@@ -282,6 +291,7 @@ class Material extends StatefulWidget {
properties.add(DiagnosticsProperty<Color>('shadowColor', shadowColor, defaultValue: const Color(0xFF000000)));
textStyle?.debugFillProperties(properties, prefix: 'textStyle.');
properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
properties.add(DiagnosticsProperty<bool>('borderOnForeground', borderOnForeground, defaultValue: true));
properties.add(EnumProperty<BorderRadius>('borderRadius', borderRadius, defaultValue: null));
}
......@@ -370,6 +380,7 @@ class _MaterialState extends State<Material> with TickerProviderStateMixin {
curve: Curves.fastOutSlowIn,
duration: widget.animationDuration,
shape: shape,
borderOnForeground: widget.borderOnForeground,
clipBehavior: widget.clipBehavior,
elevation: widget.elevation,
color: backgroundColor,
......@@ -617,6 +628,7 @@ class _MaterialInterior extends ImplicitlyAnimatedWidget {
Key key,
@required this.child,
@required this.shape,
this.borderOnForeground = true,
this.clipBehavior = Clip.none,
@required this.elevation,
@required this.color,
......@@ -642,6 +654,12 @@ class _MaterialInterior extends ImplicitlyAnimatedWidget {
/// determines the physical shape.
final ShapeBorder shape;
/// Whether to paint the border in front of the child.
///
/// The default value is true.
/// If false, the border will be painted behind the child.
final bool borderOnForeground;
/// {@macro flutter.widgets.Clip}
final Clip clipBehavior;
......@@ -689,6 +707,7 @@ class _MaterialInteriorState extends AnimatedWidgetBaseState<_MaterialInterior>
child: _ShapeBorderPaint(
child: widget.child,
shape: shape,
borderOnForeground: widget.borderOnForeground,
),
clipper: ShapeBorderClipper(
shape: shape,
......@@ -706,16 +725,19 @@ class _ShapeBorderPaint extends StatelessWidget {
const _ShapeBorderPaint({
@required this.child,
@required this.shape,
this.borderOnForeground = true,
});
final Widget child;
final ShapeBorder shape;
final bool borderOnForeground;
@override
Widget build(BuildContext context) {
return CustomPaint(
child: child,
foregroundPainter: _ShapeBorderPainter(shape, Directionality.of(context)),
painter: borderOnForeground ? null : _ShapeBorderPainter(shape, Directionality.of(context)),
foregroundPainter: borderOnForeground ? _ShapeBorderPainter(shape, Directionality.of(context)) : null,
);
}
}
......
......@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:io' show Platform;
import 'package:flutter/material.dart';
import 'package:flutter/painting.dart';
import 'package:flutter/rendering.dart';
......@@ -543,5 +545,86 @@ void main() {
final RenderBox box = tester.renderObject(find.byKey(materialKey));
expect(box, isNot(paints..circle()));
});
testWidgets('border is painted above child by default', (WidgetTester tester) async {
final Key painterKey = UniqueKey();
await tester.pumpWidget(MaterialApp(
home: Scaffold(
body: RepaintBoundary(
key: painterKey,
child: Card(
child: SizedBox(
width: 200,
height: 300,
child: Material(
clipBehavior: Clip.hardEdge,
elevation: 0,
shape: RoundedRectangleBorder(
side: const BorderSide(color: Colors.grey, width: 6),
borderRadius: BorderRadius.circular(8),
),
child: Column(
children: <Widget>[
Container(
color: Colors.green,
height: 150,
)
],
),
),
),
)
)
),
));
await expectLater(
find.byKey(painterKey),
matchesGoldenFile('material.border_paint_above.png'),
skip: !Platform.isLinux,
);
});
testWidgets('border is painted below child when specified', (WidgetTester tester) async {
final Key painterKey = UniqueKey();
await tester.pumpWidget(MaterialApp(
home: Scaffold(
body: RepaintBoundary(
key: painterKey,
child: Card(
child: SizedBox(
width: 200,
height: 300,
child: Material(
clipBehavior: Clip.hardEdge,
elevation: 0,
shape: RoundedRectangleBorder(
side: const BorderSide(color: Colors.grey, width: 6),
borderRadius: BorderRadius.circular(8),
),
borderOnForeground: false,
child: Column(
children: <Widget>[
Container(
color: Colors.green,
height: 150,
)
],
),
),
),
)
)
),
));
await expectLater(
find.byKey(painterKey),
matchesGoldenFile('material.border_paint_below.png'),
skip: !Platform.isLinux,
);
});
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment