diff --git a/packages/flutter/lib/src/material/button.dart b/packages/flutter/lib/src/material/button.dart index b9a15b25bf..aea49ce2b8 100644 --- a/packages/flutter/lib/src/material/button.dart +++ b/packages/flutter/lib/src/material/button.dart @@ -87,7 +87,7 @@ class RawMaterialButton extends StatefulWidget { /// Defines the default text style, with [Material.textStyle], for the /// button's [child]. /// - /// If [textStyle.color] is a [MaterialStateProperty], [MaterialStateProperty.resolve] + /// If [textStyle.color] is a [MaterialStateColor], [MaterialStateColor.resolveColor] /// is used for the following [MaterialState]s: /// /// * [MaterialState.pressed]. @@ -199,14 +199,6 @@ class RawMaterialButton extends StatefulWidget { /// /// The button's highlight and splash are clipped to this shape. If the /// button has an elevation, then its drop shadow is defined by this shape. - /// - /// If [shape] is a [MaterialStateProperty], [MaterialStateProperty.resolve] - /// is used for the following [MaterialState]s: - /// - /// * [MaterialState.pressed]. - /// * [MaterialState.hovered]. - /// * [MaterialState.focused]. - /// * [MaterialState.disabled]. final ShapeBorder shape; /// Defines the duration of animated changes for [shape] and [elevation]. @@ -325,8 +317,7 @@ class _RawMaterialButtonState extends State { @override Widget build(BuildContext context) { - final Color effectiveTextColor = MaterialStateProperty.resolveAs(widget.textStyle?.color, _states); - final ShapeBorder effectiveShape = MaterialStateProperty.resolveAs(widget.shape, _states); + final Color effectiveTextColor = MaterialStateColor.resolveColor(widget.textStyle?.color, _states); final Widget result = Focus( focusNode: widget.focusNode, @@ -336,7 +327,7 @@ class _RawMaterialButtonState extends State { child: Material( elevation: _effectiveElevation, textStyle: widget.textStyle?.copyWith(color: effectiveTextColor), - shape: effectiveShape, + shape: widget.shape, color: widget.fillColor, type: widget.fillColor == null ? MaterialType.transparency : MaterialType.button, animationDuration: widget.animationDuration, @@ -349,7 +340,7 @@ class _RawMaterialButtonState extends State { hoverColor: widget.hoverColor, onHover: _handleHoveredChanged, onTap: widget.onPressed, - customBorder: effectiveShape, + customBorder: widget.shape, child: IconTheme.merge( data: IconThemeData(color: effectiveTextColor), child: Container( diff --git a/packages/flutter/lib/src/material/button_theme.dart b/packages/flutter/lib/src/material/button_theme.dart index 742f9e2b9c..9aa59d74e0 100644 --- a/packages/flutter/lib/src/material/button_theme.dart +++ b/packages/flutter/lib/src/material/button_theme.dart @@ -481,10 +481,11 @@ class ButtonThemeData extends Diagnosticable { /// Otherwise the color scheme's [ColorScheme.onSurface] color is returned /// with its opacity set to 0.30 if [getBrightness] is dark, 0.38 otherwise. /// - /// If [MaterialButton.textColor] is a [MaterialStateProperty], it will be - /// used as the `disabledTextColor`. It will be resolved in the [MaterialState.disabled] state. + /// If [MaterialButton.textColor] is a [MaterialStateColor], it will be used + /// as the `disabledTextColor`. It will be resolved in the + /// [MaterialState.disabled] state. Color getDisabledTextColor(MaterialButton button) { - if (button.textColor is MaterialStateProperty) + if (button.textColor is MaterialStateColor) return button.textColor; if (button.disabledTextColor != null) return button.disabledTextColor; diff --git a/packages/flutter/lib/src/material/material_button.dart b/packages/flutter/lib/src/material/material_button.dart index d17e9ddc5f..7612f63eb0 100644 --- a/packages/flutter/lib/src/material/material_button.dart +++ b/packages/flutter/lib/src/material/material_button.dart @@ -100,8 +100,8 @@ class MaterialButton extends StatelessWidget { /// The default text color depends on the button theme's text theme, /// [ButtonThemeData.textTheme]. /// - /// If [textColor] is a [MaterialStateProperty], [disabledTextColor] - /// will be ignored. + /// If [textColor] is a [MaterialStateColor], [disabledTextColor] will be + /// ignored. /// /// See also: /// @@ -117,8 +117,8 @@ class MaterialButton extends StatelessWidget { /// The default value is the theme's disabled color, /// [ThemeData.disabledColor]. /// - /// If [textColor] is a [MaterialStateProperty], [disabledTextColor] - /// will be ignored. + /// If [textColor] is a [MaterialStateColor], [disabledTextColor] will be + /// ignored. /// /// See also: /// diff --git a/packages/flutter/lib/src/material/material_state.dart b/packages/flutter/lib/src/material/material_state.dart index 0a6dd438d6..0cadb2fa9a 100644 --- a/packages/flutter/lib/src/material/material_state.dart +++ b/packages/flutter/lib/src/material/material_state.dart @@ -62,9 +62,8 @@ enum MaterialState { error, } -/// Signature for the function that returns a value of type `T` based on a given -/// set of states. -typedef MaterialPropertyResolver = T Function(Set states); +/// Signature for the function that returns a color based on a given set of states. +typedef MaterialStateColorResolver = Color Function(Set states); /// Defines a [Color] whose value depends on a set of [MaterialState]s which /// represent the interactive state of a component. @@ -110,7 +109,7 @@ typedef MaterialPropertyResolver = T Function(Set states); /// ), /// ``` /// {@end-tool} -abstract class MaterialStateColor extends Color implements MaterialStateProperty { +abstract class MaterialStateColor extends Color { /// Creates a [MaterialStateColor]. /// /// If you want a `const` [MaterialStateColor], you'll need to extend @@ -142,24 +141,33 @@ abstract class MaterialStateColor extends Color implements MaterialStateProperty /// {@end-tool} const MaterialStateColor(int defaultValue) : super(defaultValue); - /// Creates a [MaterialStateColor] from a [MaterialPropertyResolver] - /// callback function. + /// Creates a [MaterialStateColor] from a [MaterialStateColorResolver] callback function. /// /// If used as a regular color, the color resolved in the default state (the /// empty set of states) will be used. /// /// The given callback parameter must return a non-null color in the default /// state. - static MaterialStateColor resolveWith(MaterialPropertyResolver callback) => _MaterialStateColor(callback); + factory MaterialStateColor.resolveWith(MaterialStateColorResolver callback) => _MaterialStateColor(callback); /// Returns a [Color] that's to be used when a Material component is in the /// specified state. - @override Color resolve(Set states); + + /// Returns the color for the given set of states if `color` is a + /// [MaterialStateColor], otherwise returns the color itself. + /// + /// This is useful for widgets that have parameters which can be [Color] or + /// [MaterialStateColor] values. + static Color resolveColor(Color color, Set states) { + if (color is MaterialStateColor) { + return color.resolve(states); + } + return color; + } } -/// A [MaterialStateColor] created from a [MaterialPropertyResolver] -/// callback alone. +/// A [MaterialStateColor] created from a [MaterialStateColorResolver] callback alone. /// /// If used as a regular color, the color resolved in the default state will /// be used. @@ -168,7 +176,7 @@ abstract class MaterialStateColor extends Color implements MaterialStateProperty class _MaterialStateColor extends MaterialStateColor { _MaterialStateColor(this._resolve) : super(_resolve(_defaultStates).value); - final MaterialPropertyResolver _resolve; + final MaterialStateColorResolver _resolve; /// The default state for a Material component, the empty set of interaction states. static const Set _defaultStates = {}; @@ -176,46 +184,3 @@ class _MaterialStateColor extends MaterialStateColor { @override Color resolve(Set states) => _resolve(states); } - -/// Interface for classes that can return a value of type `T` based on a set of -/// [MaterialState]s. -/// -/// For example, [MaterialStateColor] implements `MaterialStateProperty` -/// because it has a `resolve` method that returns a different [Color] depending -/// on the given set of [MaterialState]s. -abstract class MaterialStateProperty { - - /// Returns a different value of type `T` depending on the given `states`. - /// - /// Some widgets (such as [RawMaterialButton]) keep track of their set of - /// [MaterialState]s, and will call `resolve` with the current states at build - /// time for specified properties (such as [RawMaterialButton.textStyle]'s color). - T resolve(Set states); - - /// Returns the value resolved in the given set of states if `value` is a - /// [MaterialStateProperty], otherwise returns the value itself. - /// - /// This is useful for widgets that have parameters which can optionally be a - /// [MaterialStateProperty]. For example, [RaisedButton.textColor] can be a - /// [Color] or a [MaterialStateProperty]. - static T resolveAs(T value, Set states) { - if (value is MaterialStateProperty) { - final MaterialStateProperty property = value; - return property.resolve(states); - } - return value; - } - - /// Convenience method for creating a [MaterialStateProperty] from a - /// [MaterialPropertyResolver] function alone. - static MaterialStateProperty resolveWith(MaterialPropertyResolver callback) => _MaterialStateProperty(callback); -} - -class _MaterialStateProperty implements MaterialStateProperty { - _MaterialStateProperty(this._resolve); - - final MaterialPropertyResolver _resolve; - - @override - T resolve(Set states) => _resolve(states); -} diff --git a/packages/flutter/lib/src/material/outline_button.dart b/packages/flutter/lib/src/material/outline_button.dart index decbed5599..58ace4fc77 100644 --- a/packages/flutter/lib/src/material/outline_button.dart +++ b/packages/flutter/lib/src/material/outline_button.dart @@ -8,7 +8,6 @@ import 'package:flutter/widgets.dart'; import 'button_theme.dart'; import 'colors.dart'; import 'material_button.dart'; -import 'material_state.dart'; import 'raised_button.dart'; import 'theme.dart'; @@ -133,16 +132,12 @@ class OutlineButton extends MaterialButton { /// /// By default the border's color does not change when the button /// is pressed. - /// - /// This field is ignored if [borderSide.color] is a [MaterialStateProperty]. final Color highlightedBorderColor; /// The outline border's color when the button is not [enabled]. /// /// By default the outline border's color does not change when the /// button is disabled. - /// - /// This field is ignored if [borderSide.color] is a [MaterialStateProperty]. final Color disabledBorderColor; /// Defines the color of the border when the button is enabled but not @@ -153,10 +148,6 @@ class OutlineButton extends MaterialButton { /// /// If null the default border's style is [BorderStyle.solid], its /// [BorderSide.width] is 1.0, and its color is a light shade of grey. - /// - /// If [borderSide.color] is a [MaterialStateProperty], [MaterialStateProperty.resolve] - /// is used in all states and both [highlightedBorderColor] and [disabledBorderColor] - /// are ignored. final BorderSide borderSide; @override @@ -379,26 +370,18 @@ class _OutlineButtonState extends State<_OutlineButton> with SingleTickerProvide return colorTween.evaluate(_fillAnimation); } - Color get _outlineColor { - // If outline color is a `MaterialStateProperty`, it will be used in all - // states, otherwise we determine the outline color in the current state. - if (widget.borderSide?.color is MaterialStateProperty) - return widget.borderSide.color; - if (!widget.enabled) - return widget.disabledBorderColor; - if (_pressed) - return widget.highlightedBorderColor; - return widget.borderSide?.color; - } - BorderSide _getOutline() { if (widget.borderSide?.style == BorderStyle.none) return widget.borderSide; + final Color specifiedColor = widget.enabled + ? (_pressed ? widget.highlightedBorderColor : null) ?? widget.borderSide?.color + : widget.disabledBorderColor; + final Color themeColor = Theme.of(context).colorScheme.onSurface.withOpacity(0.12); return BorderSide( - color: _outlineColor ?? themeColor, + color: specifiedColor ?? themeColor, width: widget.borderSide?.width ?? 1.0, ); } @@ -450,7 +433,7 @@ class _OutlineButtonState extends State<_OutlineButton> with SingleTickerProvide // Render the button's outline border using using the OutlineButton's // border parameters and the button or buttonTheme's shape. -class _OutlineBorder extends ShapeBorder implements MaterialStateProperty{ +class _OutlineBorder extends ShapeBorder { const _OutlineBorder({ @required this.shape, @required this.side, @@ -529,12 +512,4 @@ class _OutlineBorder extends ShapeBorder implements MaterialStateProperty hashValues(side, shape); - - @override - ShapeBorder resolve(Set states) { - return _OutlineBorder( - shape: shape, - side: side.copyWith(color: MaterialStateProperty.resolveAs(side.color, states), - )); - } } diff --git a/packages/flutter/test/material/outline_button_test.dart b/packages/flutter/test/material/outline_button_test.dart index 33e01dc908..e26a860955 100644 --- a/packages/flutter/test/material/outline_button_test.dart +++ b/packages/flutter/test/material/outline_button_test.dart @@ -318,138 +318,6 @@ void main() { expect(textColor(), isNot(unusedDisabledTextColor)); }); - testWidgets('OutlineButton uses stateful color for border color in different states', (WidgetTester tester) async { - final FocusNode focusNode = FocusNode(); - - const Color pressedColor = Color(1); - const Color hoverColor = Color(2); - const Color focusedColor = Color(3); - const Color defaultColor = Color(4); - - Color getBorderColor(Set states) { - if (states.contains(MaterialState.pressed)) { - return pressedColor; - } - if (states.contains(MaterialState.hovered)) { - return hoverColor; - } - if (states.contains(MaterialState.focused)) { - return focusedColor; - } - return defaultColor; - } - - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: Center( - child: OutlineButton( - child: const Text('OutlineButton'), - onPressed: () {}, - focusNode: focusNode, - borderSide: BorderSide(color: MaterialStateColor.resolveWith(getBorderColor)), - ), - ), - ), - ), - ); - - final Finder outlineButton = find.byType(OutlineButton); - - // Default, not disabled. - expect(outlineButton, paints..path(color: defaultColor)); - - // Focused. - focusNode.requestFocus(); - await tester.pumpAndSettle(); - expect(outlineButton, paints..path(color: focusedColor)); - - // Hovered. - final Offset center = tester.getCenter(find.byType(OutlineButton)); - final TestGesture gesture = await tester.createGesture( - kind: PointerDeviceKind.mouse, - ); - await gesture.addPointer(); - await gesture.moveTo(center); - await tester.pumpAndSettle(); - expect(outlineButton, paints..path(color: hoverColor)); - - // Highlighted (pressed). - await gesture.down(center); - await tester.pumpAndSettle(); - expect(outlineButton, paints..path(color: pressedColor)); - await gesture.removePointer(); - }); - - testWidgets('OutlineButton ignores highlightBorderColor if border color is stateful', (WidgetTester tester) async { - const Color pressedColor = Color(1); - const Color defaultColor = Color(2); - const Color ignoredPressedColor = Color(3); - - Color getBorderColor(Set states) { - if (states.contains(MaterialState.pressed)) { - return pressedColor; - } - return defaultColor; - } - - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: Center( - child: OutlineButton( - child: const Text('OutlineButton'), - onPressed: () {}, - borderSide: BorderSide(color: MaterialStateColor.resolveWith(getBorderColor)), - highlightedBorderColor: ignoredPressedColor, - ), - ), - ), - ), - ); - - final Finder outlineButton = find.byType(OutlineButton); - - // Default, not disabled. - expect(outlineButton, paints..path(color: defaultColor)); - - // Highlighted (pressed). - await tester.press(outlineButton); - await tester.pumpAndSettle(); - expect(outlineButton, paints..path(color: pressedColor)); - }); - - testWidgets('OutlineButton ignores disabledBorderColor if border color is stateful', (WidgetTester tester) async { - const Color disabledColor = Color(1); - const Color defaultColor = Color(2); - const Color ignoredDisabledColor = Color(3); - - Color getBorderColor(Set states) { - if (states.contains(MaterialState.disabled)) { - return disabledColor; - } - return defaultColor; - } - - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: Center( - child: OutlineButton( - child: const Text('OutlineButton'), - onPressed: null, - borderSide: BorderSide(color: MaterialStateColor.resolveWith(getBorderColor)), - highlightedBorderColor: ignoredDisabledColor, - ), - ), - ), - ), - ); - - // Disabled. - expect(find.byType(OutlineButton), paints..path(color: disabledColor)); - }); - testWidgets('Outline button responds to tap when enabled', (WidgetTester tester) async { int pressedCount = 0;