Unverified Commit a9c05a72 authored by Hans Muller's avatar Hans Muller Committed by GitHub

Update OutlineButton default border width and highlight elevation (#27575)

These changes are **backwards incompatible**. Tests that verify OutlineButton visuals, for example golden image tests, will need to be updated.
parent 5b943257
...@@ -632,16 +632,15 @@ class ButtonThemeData extends Diagnosticable { ...@@ -632,16 +632,15 @@ class ButtonThemeData extends Diagnosticable {
/// ///
/// Returns the button's [MaterialButton.highlightElevation] if it is non-null. /// Returns the button's [MaterialButton.highlightElevation] if it is non-null.
/// ///
/// If button is a [FlatButton] then the highlight elevation is 0.0, if it's /// If button is a [FlatButton] or an [OutlineButton] then the highlight
/// a [OutlineButton] then the highlight elevation is 2.0, otherise the /// elevation is 0.0, otherise the highlight elevation is 8.0.
/// highlight elevation is 8.0.
double getHighlightElevation(MaterialButton button) { double getHighlightElevation(MaterialButton button) {
if (button.highlightElevation != null) if (button.highlightElevation != null)
return button.highlightElevation; return button.highlightElevation;
if (button is FlatButton) if (button is FlatButton)
return 0.0; return 0.0;
if (button is OutlineButton) if (button is OutlineButton)
return 2.0; return 0.0;
return 8.0; return 8.0;
} }
......
...@@ -12,28 +12,31 @@ import 'raised_button.dart'; ...@@ -12,28 +12,31 @@ import 'raised_button.dart';
import 'theme.dart'; import 'theme.dart';
// The total time to make the button's fill color opaque and change // The total time to make the button's fill color opaque and change
// its elevation. // its elevation. Only applies when highlightElevation > 0.0.
const Duration _kPressDuration = Duration(milliseconds: 150); const Duration _kPressDuration = Duration(milliseconds: 150);
// Half of _kPressDuration: just the time to change the button's // Half of _kPressDuration: just the time to change the button's
// elevation. // elevation. Only applies when highlightElevation > 0.0.
const Duration _kElevationDuration = Duration(milliseconds: 75); const Duration _kElevationDuration = Duration(milliseconds: 75);
/// A cross between [RaisedButton] and [FlatButton]: a bordered button whose /// Similar to a [FlatButton] with a thin grey rounded rectangle border.
/// elevation increases and whose background becomes opaque when the button
/// is pressed.
/// ///
/// An outline button's elevation is initially 0.0 and its background [color] /// The outline button's border shape is defined by [shape]
/// is transparent. When the button is pressed its background becomes opaque /// and its appearance is defined by [borderSide], [disabledBorderColor],
/// and then its elevation increases to [highlightElevation]. /// and [highlightedBorderColor]. By default the border is a one pixel
/// /// wide grey rounded rectangle that does not change when the button is
/// The outline button has a border whose shape is defined by [shape] /// pressed or disabled. By default the button's background is transparent.
/// and whose appearance is defined by [borderSide], [disabledBorderColor],
/// and [highlightedBorderColor].
/// ///
/// If the [onPressed] callback is null, then the button will be disabled and by /// If the [onPressed] callback is null, then the button will be disabled and by
/// default will resemble a flat button in the [disabledColor]. /// default will resemble a flat button in the [disabledColor].
/// ///
/// The button's [highlightElevation], which defines the size of the
/// drop shadow when the button is pressed, is 0.0 (no shadow) by default.
/// If [highlightElevation] is given a value greater than 0.0 then the button
/// becomes a cross between [RaisedButton] and [FlatButton]: a bordered
/// button whose elevation increases and whose background becomes opaque
/// when the button is pressed.
///
/// If you want an ink-splash effect for taps, but don't want to use a button, /// If you want an ink-splash effect for taps, but don't want to use a button,
/// consider using [InkWell] directly. /// consider using [InkWell] directly.
/// ///
...@@ -50,10 +53,10 @@ const Duration _kElevationDuration = Duration(milliseconds: 75); ...@@ -50,10 +53,10 @@ const Duration _kElevationDuration = Duration(milliseconds: 75);
/// * [InkWell], which implements the ink splash part of a flat button. /// * [InkWell], which implements the ink splash part of a flat button.
/// * <https://material.io/design/components/buttons.html> /// * <https://material.io/design/components/buttons.html>
class OutlineButton extends MaterialButton { class OutlineButton extends MaterialButton {
/// Create a filled button. /// Create an outline button.
/// ///
/// The [highlightElevation], [borderWidth], and [clipBehavior] /// The [highlightElevation] argument must be null or a positive value
/// arguments must not be null. /// and the [clipBehavior] argument must not be null.
const OutlineButton({ const OutlineButton({
Key key, Key key,
@required VoidCallback onPressed, @required VoidCallback onPressed,
...@@ -94,8 +97,8 @@ class OutlineButton extends MaterialButton { ...@@ -94,8 +97,8 @@ class OutlineButton extends MaterialButton {
/// The icon and label are arranged in a row and padded by 12 logical pixels /// The icon and label are arranged in a row and padded by 12 logical pixels
/// at the start, and 16 at the end, with an 8 pixel gap in between. /// at the start, and 16 at the end, with an 8 pixel gap in between.
/// ///
/// The [highlightElevation], [icon], [label], and [clipBehavior] must not be /// The [highlightElevation] argument must be null or a positive value. The
/// null. /// [icon], [label], and [clipBehavior] arguments must not be null.
factory OutlineButton.icon({ factory OutlineButton.icon({
Key key, Key key,
@required VoidCallback onPressed, @required VoidCallback onPressed,
...@@ -118,15 +121,14 @@ class OutlineButton extends MaterialButton { ...@@ -118,15 +121,14 @@ class OutlineButton extends MaterialButton {
/// The outline border's color when the button is [enabled] and pressed. /// The outline border's color when the button is [enabled] and pressed.
/// ///
/// If null this value defaults to the theme's primary color, /// By default the border's color does not change when the button
/// [ThemeData.primaryColor]. /// is pressed.
final Color highlightedBorderColor; final Color highlightedBorderColor;
/// The outline border's color when the button is not [enabled]. /// The outline border's color when the button is not [enabled].
/// ///
/// If null this value defaults to a very light shade of grey for light /// By default the outline border's color does not change when the
/// themes (see [ThemeData.brightness]), and a very dark shade of grey for /// button is disabled.
/// dark themes.
final Color disabledBorderColor; final Color disabledBorderColor;
/// Defines the color of the border when the button is enabled but not /// Defines the color of the border when the button is enabled but not
...@@ -136,7 +138,7 @@ class OutlineButton extends MaterialButton { ...@@ -136,7 +138,7 @@ class OutlineButton extends MaterialButton {
/// an outline is not drawn. /// an outline is not drawn.
/// ///
/// If null the default border's style is [BorderStyle.solid], its /// If null the default border's style is [BorderStyle.solid], its
/// [BorderSide.width] is 2.0, and its color is a light shade of grey. /// [BorderSide.width] is 1.0, and its color is a light shade of grey.
final BorderSide borderSide; final BorderSide borderSide;
@override @override
...@@ -181,10 +183,10 @@ class OutlineButton extends MaterialButton { ...@@ -181,10 +183,10 @@ class OutlineButton extends MaterialButton {
} }
} }
// The type of of OutlineButtons created with [OutlineButton.icon]. // The type of of OutlineButtons created with OutlineButton.icon.
// //
// This class only exists to give RaisedButtons created with [RaisedButton.icon] // This class only exists to give OutlineButtons created with OutlineButton.icon
// a distinct class for the sake of [ButtonTheme]. It can not be instantiated. // a distinct class for the sake of ButtonTheme. It can not be instantiated.
class _OutlineButtonWithIcon extends OutlineButton with MaterialButtonWithIconMixin { class _OutlineButtonWithIcon extends OutlineButton with MaterialButtonWithIconMixin {
_OutlineButtonWithIcon({ _OutlineButtonWithIcon({
Key key, Key key,
...@@ -291,12 +293,13 @@ class _OutlineButtonState extends State<_OutlineButton> with SingleTickerProvide ...@@ -291,12 +293,13 @@ class _OutlineButtonState extends State<_OutlineButton> with SingleTickerProvide
void initState() { void initState() {
super.initState(); super.initState();
// The Material widget animates its shape (which includes the outline // When highlightElevation > 0.0, the Material widget animates its
// border) and elevation over _kElevationDuration. When pressed, the // shape (which includes the outline border) and elevation over
// button makes its fill color opaque white first, and then sets // _kElevationDuration. When pressed, the button makes its fill
// its highlightElevation. We can't change the elevation while the // color opaque white first, and then sets its
// button's fill is translucent, because the shadow fills the interior // highlightElevation. We can't change the elevation while the
// of the button. // button's fill is translucent, because the shadow fills the
// interior of the button.
_controller = AnimationController( _controller = AnimationController(
duration: _kPressDuration, duration: _kPressDuration,
...@@ -343,6 +346,8 @@ class _OutlineButtonState extends State<_OutlineButton> with SingleTickerProvide ...@@ -343,6 +346,8 @@ class _OutlineButtonState extends State<_OutlineButton> with SingleTickerProvide
} }
Color _getFillColor() { Color _getFillColor() {
if (widget.highlightElevation == null || widget.highlightElevation == 0.0)
return Colors.transparent;
final Color color = widget.color ?? Theme.of(context).canvasColor; final Color color = widget.color ?? Theme.of(context).canvasColor;
final Tween<Color> colorTween = ColorTween( final Tween<Color> colorTween = ColorTween(
begin: color.withAlpha(0x00), begin: color.withAlpha(0x00),
...@@ -352,28 +357,27 @@ class _OutlineButtonState extends State<_OutlineButton> with SingleTickerProvide ...@@ -352,28 +357,27 @@ class _OutlineButtonState extends State<_OutlineButton> with SingleTickerProvide
} }
BorderSide _getOutline() { BorderSide _getOutline() {
final bool isDark = widget.brightness == Brightness.dark;
if (widget.borderSide?.style == BorderStyle.none) if (widget.borderSide?.style == BorderStyle.none)
return widget.borderSide; return widget.borderSide;
final Color color = widget.enabled final Color specifiedColor = widget.enabled
? (_pressed ? (_pressed ? widget.highlightedBorderColor : null) ?? widget.borderSide?.color
? widget.highlightedBorderColor : widget.disabledBorderColor;
: (widget.borderSide?.color ??
(isDark ? Colors.grey[600] : Colors.grey[200]))) final Color themeColor = Theme.of(context).colorScheme.onSurface.withOpacity(0.12);
: (widget.disabledBorderColor ??
(isDark ? Colors.grey[800] : Colors.grey[100]));
return BorderSide( return BorderSide(
color: color, color: specifiedColor ?? themeColor,
width: widget.borderSide?.width ?? 2.0, width: widget.borderSide?.width ?? 1.0,
); );
} }
double _getHighlightElevation() { double _getHighlightElevation() {
if (widget.highlightElevation == null || widget.highlightElevation == 0.0)
return 0.0;
return Tween<double>( return Tween<double>(
begin: 0.0, begin: 0.0,
end: widget.highlightElevation ?? 2.0, end: widget.highlightElevation,
).evaluate(_elevationAnimation); ).evaluate(_elevationAnimation);
} }
......
...@@ -81,6 +81,10 @@ void main() { ...@@ -81,6 +81,10 @@ void main() {
shape: const RoundedRectangleBorder(), // default border radius is 0 shape: const RoundedRectangleBorder(), // default border radius is 0
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
color: fillColor, color: fillColor,
// Causes the button to be filled with the theme's canvasColor
// instead of Colors.transparent before the button material's
// elevation is animated to 2.0.
highlightElevation: 2.0,
highlightedBorderColor: highlightedBorderColor, highlightedBorderColor: highlightedBorderColor,
disabledBorderColor: disabledBorderColor, disabledBorderColor: disabledBorderColor,
borderSide: const BorderSide( borderSide: const BorderSide(
...@@ -108,7 +112,7 @@ void main() { ...@@ -108,7 +112,7 @@ void main() {
// Expect that the button is disabled and painted with the disabled border color. // Expect that the button is disabled and painted with the disabled border color.
expect(tester.widget<OutlineButton>(outlineButton).enabled, false); expect(tester.widget<OutlineButton>(outlineButton).enabled, false);
expect( expect(
outlineButton, //find.byType(OutlineButton), outlineButton,
paints paints
..clipPath(pathMatcher: coversSameAreaAs(clipPath, areaToCompare: clipRect.inflate(10.0))) ..clipPath(pathMatcher: coversSameAreaAs(clipPath, areaToCompare: clipRect.inflate(10.0)))
..path(color: disabledBorderColor, strokeWidth: borderWidth)); ..path(color: disabledBorderColor, strokeWidth: borderWidth));
...@@ -323,6 +327,10 @@ void main() { ...@@ -323,6 +327,10 @@ void main() {
body: Center( body: Center(
child: OutlineButton( child: OutlineButton(
onPressed: () {}, onPressed: () {},
// Causes the button to be filled with the theme's canvasColor
// instead of Colors.transparent before the button material's
// elevation is animated to 2.0.
highlightElevation: 2.0,
child: const Text('Hello'), child: const Text('Hello'),
), ),
), ),
......
...@@ -128,13 +128,17 @@ void main() { ...@@ -128,13 +128,17 @@ void main() {
}); });
group('OutlineButton', () { group('OutlineButton', () {
testWidgets('theme: ThemeData.light(), enabled: true', (WidgetTester tester) async { testWidgets('theme: ThemeData.light(), enabled: true, highlightElevation: 2.0', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
theme: ThemeData.light(), theme: ThemeData.light(),
home: Center( home: Center(
child: OutlineButton( child: OutlineButton(
onPressed: () { }, // button.enabled == true onPressed: () { }, // button.enabled == true
// Causes the button to be filled with the theme's canvasColor
// instead of Colors.transparent before the button material's
// elevation is animated to 2.0.
highlightElevation: 2.0,
child: const Text('button'), child: const Text('button'),
) )
), ),
...@@ -155,6 +159,33 @@ void main() { ...@@ -155,6 +159,33 @@ void main() {
expect(raw.materialTapTargetSize, MaterialTapTargetSize.padded); expect(raw.materialTapTargetSize, MaterialTapTargetSize.padded);
}); });
testWidgets('theme: ThemeData.light(), enabled: true', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData.light(),
home: Center(
child: OutlineButton(
onPressed: () { }, // button.enabled == true
child: const Text('button'),
)
),
),
);
final RawMaterialButton raw = tester.widget<RawMaterialButton>(find.byType(RawMaterialButton));
expect(raw.textStyle.color, const Color(0xdd000000));
expect(raw.fillColor, Colors.transparent);
expect(raw.highlightColor, const Color(0x29000000)); // Was Color(0x66bcbcbc)
expect(raw.splashColor, const Color(0x1f000000)); // Was Color(0x66c8c8c8)
expect(raw.elevation, 0.0);
expect(raw.highlightElevation, 0.0);
expect(raw.disabledElevation, 0.0);
expect(raw.constraints, defaultButtonConstraints);
expect(raw.padding, defaultButtonPadding);
// animationDuration can't be configed by the theme/constructor
expect(raw.materialTapTargetSize, MaterialTapTargetSize.padded);
});
testWidgets('theme: ThemeData.light(), enabled: false', (WidgetTester tester) async { testWidgets('theme: ThemeData.light(), enabled: false', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
......
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