diff --git a/bin/internal/goldens.version b/bin/internal/goldens.version index 6b53013689..76a6e57b95 100644 --- a/bin/internal/goldens.version +++ b/bin/internal/goldens.version @@ -1 +1 @@ -b530d67675a5aa9c5458b93019ce91e20ad88758 +46a3d26acbb1b0d72b6b02c30f03b9dbda7d5bdf diff --git a/packages/flutter/lib/src/material/card.dart b/packages/flutter/lib/src/material/card.dart index 4f1495f8b9..003eebafbd 100644 --- a/packages/flutter/lib/src/material/card.dart +++ b/packages/flutter/lib/src/material/card.dart @@ -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, diff --git a/packages/flutter/lib/src/material/material.dart b/packages/flutter/lib/src/material/material.dart index 02e6a6c84a..847c3e6e66 100644 --- a/packages/flutter/lib/src/material/material.dart +++ b/packages/flutter/lib/src/material/material.dart @@ -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('shadowColor', shadowColor, defaultValue: const Color(0xFF000000))); textStyle?.debugFillProperties(properties, prefix: 'textStyle.'); properties.add(DiagnosticsProperty('shape', shape, defaultValue: null)); + properties.add(DiagnosticsProperty('borderOnForeground', borderOnForeground, defaultValue: true)); properties.add(EnumProperty('borderRadius', borderRadius, defaultValue: null)); } @@ -370,6 +380,7 @@ class _MaterialState extends State 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, ); } } diff --git a/packages/flutter/test/material/material_test.dart b/packages/flutter/test/material/material_test.dart index b8dbb5d770..0f3f45882c 100644 --- a/packages/flutter/test/material/material_test.dart +++ b/packages/flutter/test/material/material_test.dart @@ -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: [ + 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: [ + Container( + color: Colors.green, + height: 150, + ) + ], + ), + ), + ), + ) + ) + ), + )); + + await expectLater( + find.byKey(painterKey), + matchesGoldenFile('material.border_paint_below.png'), + skip: !Platform.isLinux, + ); + }); }); }