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

Added ColorScheme, updated ThemeData, ButtonTheme, material buttons (#22013)

Introduce an overall material "color scheme" and to make it possible to redefine how a component's visual properties bind to the color scheme and to the existing material Theme.
parent 46bc911b
...@@ -25,6 +25,9 @@ TextTheme _buildTextTheme(TextTheme base) { ...@@ -25,6 +25,9 @@ TextTheme _buildTextTheme(TextTheme base) {
ThemeData _buildDarkTheme() { ThemeData _buildDarkTheme() {
const Color primaryColor = Color(0xFF0175c2); const Color primaryColor = Color(0xFF0175c2);
final ThemeData base = ThemeData.dark(); final ThemeData base = ThemeData.dark();
final ColorScheme colorScheme = const ColorScheme.dark().copyWith(
primary: primaryColor,
);
return base.copyWith( return base.copyWith(
primaryColor: primaryColor, primaryColor: primaryColor,
buttonColor: primaryColor, buttonColor: primaryColor,
...@@ -34,7 +37,8 @@ ThemeData _buildDarkTheme() { ...@@ -34,7 +37,8 @@ ThemeData _buildDarkTheme() {
scaffoldBackgroundColor: const Color(0xFF202124), scaffoldBackgroundColor: const Color(0xFF202124),
backgroundColor: const Color(0xFF202124), backgroundColor: const Color(0xFF202124),
errorColor: const Color(0xFFB00020), errorColor: const Color(0xFFB00020),
buttonTheme: const ButtonThemeData( buttonTheme: ButtonThemeData(
colorScheme: colorScheme,
textTheme: ButtonTextTheme.primary, textTheme: ButtonTextTheme.primary,
), ),
textTheme: _buildTextTheme(base.textTheme), textTheme: _buildTextTheme(base.textTheme),
...@@ -45,8 +49,12 @@ ThemeData _buildDarkTheme() { ...@@ -45,8 +49,12 @@ ThemeData _buildDarkTheme() {
ThemeData _buildLightTheme() { ThemeData _buildLightTheme() {
const Color primaryColor = Color(0xFF0175c2); const Color primaryColor = Color(0xFF0175c2);
final ColorScheme colorScheme = const ColorScheme.light().copyWith(
primary: primaryColor,
);
final ThemeData base = ThemeData.light(); final ThemeData base = ThemeData.light();
return base.copyWith( return base.copyWith(
colorScheme: colorScheme,
primaryColor: primaryColor, primaryColor: primaryColor,
buttonColor: primaryColor, buttonColor: primaryColor,
indicatorColor: Colors.white, indicatorColor: Colors.white,
...@@ -57,7 +65,8 @@ ThemeData _buildLightTheme() { ...@@ -57,7 +65,8 @@ ThemeData _buildLightTheme() {
scaffoldBackgroundColor: Colors.white, scaffoldBackgroundColor: Colors.white,
backgroundColor: Colors.white, backgroundColor: Colors.white,
errorColor: const Color(0xFFB00020), errorColor: const Color(0xFFB00020),
buttonTheme: const ButtonThemeData( buttonTheme: ButtonThemeData(
colorScheme: colorScheme,
textTheme: ButtonTextTheme.primary, textTheme: ButtonTextTheme.primary,
), ),
textTheme: _buildTextTheme(base.textTheme), textTheme: _buildTextTheme(base.textTheme),
......
...@@ -380,12 +380,12 @@ void main() { ...@@ -380,12 +380,12 @@ void main() {
handle.dispose(); handle.dispose();
}); });
testWidgets('overscroll_demo $themeName', (WidgetTester tester) async { testWidgets('overscroll_demo', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics(); final SemanticsHandle handle = tester.ensureSemantics();
await tester.pumpWidget(MaterialApp(theme: theme, home: const OverscrollDemo())); await tester.pumpWidget(const MaterialApp(home: OverscrollDemo()));
await expectLater(tester, meetsGuideline(textContrastGuideline)); await expectLater(tester, meetsGuideline(textContrastGuideline));
handle.dispose(); handle.dispose();
}); });
testWidgets('page_selector_demo $themeName', (WidgetTester tester) async { testWidgets('page_selector_demo $themeName', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics(); final SemanticsHandle handle = tester.ensureSemantics();
......
...@@ -32,6 +32,7 @@ export 'src/material/checkbox_list_tile.dart'; ...@@ -32,6 +32,7 @@ export 'src/material/checkbox_list_tile.dart';
export 'src/material/chip.dart'; export 'src/material/chip.dart';
export 'src/material/chip_theme.dart'; export 'src/material/chip_theme.dart';
export 'src/material/circle_avatar.dart'; export 'src/material/circle_avatar.dart';
export 'src/material/color_scheme.dart';
export 'src/material/colors.dart'; export 'src/material/colors.dart';
export 'src/material/constants.dart'; export 'src/material/constants.dart';
export 'src/material/data_table.dart'; export 'src/material/data_table.dart';
...@@ -65,6 +66,7 @@ export 'src/material/input_border.dart'; ...@@ -65,6 +66,7 @@ export 'src/material/input_border.dart';
export 'src/material/input_decorator.dart'; export 'src/material/input_decorator.dart';
export 'src/material/list_tile.dart'; export 'src/material/list_tile.dart';
export 'src/material/material.dart'; export 'src/material/material.dart';
export 'src/material/material_button.dart';
export 'src/material/material_localizations.dart'; export 'src/material/material_localizations.dart';
export 'src/material/mergeable_material.dart'; export 'src/material/mergeable_material.dart';
export 'src/material/outline_button.dart'; export 'src/material/outline_button.dart';
......
...@@ -9,7 +9,6 @@ import 'package:flutter/rendering.dart'; ...@@ -9,7 +9,6 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'button_theme.dart'; import 'button_theme.dart';
import 'colors.dart';
import 'constants.dart'; import 'constants.dart';
import 'ink_well.dart'; import 'ink_well.dart';
import 'material.dart'; import 'material.dart';
...@@ -225,238 +224,6 @@ class _RawMaterialButtonState extends State<RawMaterialButton> { ...@@ -225,238 +224,6 @@ class _RawMaterialButtonState extends State<RawMaterialButton> {
} }
} }
/// A utility class for building Material buttons that depend on the
/// ambient [ButtonTheme] and [Theme].
///
/// The button's size will expand to fit the child widget, if necessary.
///
/// MaterialButtons whose [onPressed] handler is null will be disabled. To have
/// an enabled button, make sure to pass a non-null value for onPressed.
///
/// Rather than using this class directly, consider using [FlatButton] or
/// [RaisedButton], which configure this class with appropriate defaults that
/// match the material design specification.
///
/// To create a button directly, without inheriting theme defaults, use
/// [RawMaterialButton].
///
/// If you want an ink-splash effect for taps, but don't want to use a button,
/// consider using [InkWell] directly.
///
/// See also:
///
/// * [IconButton], to create buttons that contain icons rather than text.
class MaterialButton extends StatelessWidget {
/// Creates a material button.
///
/// Rather than creating a material button directly, consider using
/// [FlatButton] or [RaisedButton]. To create a custom Material button
/// consider using [RawMaterialButton].
///
/// The [clipBehavior] argument must not be null.
const MaterialButton({
Key key,
this.colorBrightness,
this.textTheme,
this.textColor,
this.color,
this.highlightColor,
this.splashColor,
this.elevation,
this.highlightElevation,
this.minWidth,
this.height,
this.padding,
this.materialTapTargetSize,
this.clipBehavior = Clip.none,
@required this.onPressed,
this.child
}) : assert(clipBehavior != null), super(key: key);
/// The theme brightness to use for this button.
///
/// Defaults to the brightness from [ThemeData.brightness].
final Brightness colorBrightness;
/// Defines the button's base colors, and the defaults for the button's minimum
/// size, internal padding, and shape.
final ButtonTextTheme textTheme;
/// The color to use for this button's text.
final Color textColor;
/// The button's fill color, displayed by its [Material], while the button
/// is in its default (unpressed, enabled) state.
///
/// Defaults to null, meaning that the color is automatically derived from the [Theme].
///
/// Typically, a material design color will be used, as follows:
///
/// ```dart
/// MaterialButton(
/// color: Colors.blue[500],
/// onPressed: _handleTap,
/// child: Text('DEMO'),
/// ),
/// ```
final Color color;
/// The primary color of the button when the button is in the down (pressed)
/// state.
///
/// The splash is represented as a circular overlay that appears above the
/// [highlightColor] overlay. The splash overlay has a center point that
/// matches the hit point of the user touch event. The splash overlay will
/// expand to fill the button area if the touch is held for long enough time.
/// If the splash color has transparency then the highlight and button color
/// will show through.
///
/// Defaults to the Theme's splash color, [ThemeData.splashColor].
final Color splashColor;
/// The secondary color of the button when the button is in the down (pressed)
/// state.
///
/// The highlight color is represented as a solid color that is overlaid over
/// the button color (if any). If the highlight color has transparency, the
/// button color will show through. The highlight fades in quickly as the
/// button is held down.
///
/// Defaults to the Theme's highlight color, [ThemeData.highlightColor].
final Color highlightColor;
/// The z-coordinate at which to place this button. This controls the size of
/// the shadow below the button.
///
/// Defaults to 0.
///
/// See also:
///
/// * [FlatButton], a material button specialized for the case where the
/// elevation is zero.
/// * [RaisedButton], a material button specialized for the case where the
/// elevation is non-zero.
final double elevation;
/// The z-coordinate at which to place this button when highlighted. This
/// controls the size of the shadow below the button.
///
/// Defaults to 0.
///
/// See also:
///
/// * [elevation], the default elevation.
final double highlightElevation;
/// The smallest horizontal extent that the button will occupy.
///
/// Defaults to the value from the current [ButtonTheme].
final double minWidth;
/// The vertical extent of the button.
///
/// Defaults to the value from the current [ButtonTheme].
final double height;
/// The internal padding for the button's [child].
///
/// Defaults to the value from the current [ButtonTheme],
/// [ButtonThemeData.padding].
final EdgeInsetsGeometry padding;
/// The callback that is called when the button is tapped or otherwise activated.
///
/// If this is set to null, the button will be disabled.
final VoidCallback onPressed;
/// The widget below this widget in the tree.
///
/// {@macro flutter.widgets.child}
final Widget child;
/// Configures the minimum size of the tap target.
///
/// Defaults to [ThemeData.materialTapTargetSize].
///
/// See also:
///
/// * [MaterialTapTargetSize], for a description of how this affects tap targets.
final MaterialTapTargetSize materialTapTargetSize;
/// {@macro flutter.widgets.Clip}
final Clip clipBehavior;
/// Whether the button is enabled or disabled. Buttons are disabled by default. To
/// enable a button, set its [onPressed] property to a non-null value.
bool get enabled => onPressed != null;
Brightness _getBrightness(ThemeData theme) {
return colorBrightness ?? theme.brightness;
}
ButtonTextTheme _getTextTheme(ButtonThemeData buttonTheme) {
return textTheme ?? buttonTheme.textTheme;
}
Color _getTextColor(ThemeData theme, ButtonThemeData buttonTheme, Color fillColor) {
if (textColor != null)
return textColor;
final bool themeIsDark = _getBrightness(theme) == Brightness.dark;
final bool fillIsDark = fillColor != null
? ThemeData.estimateBrightnessForColor(fillColor) == Brightness.dark
: themeIsDark;
switch (_getTextTheme(buttonTheme)) {
case ButtonTextTheme.normal:
return enabled
? (themeIsDark ? Colors.white : Colors.black87)
: theme.disabledColor;
case ButtonTextTheme.accent:
return enabled
? theme.accentColor
: theme.disabledColor;
case ButtonTextTheme.primary:
return enabled
? (fillIsDark ? Colors.white : Colors.black)
: (themeIsDark ? Colors.white30 : Colors.black38);
}
return null;
}
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
final ButtonThemeData buttonTheme = ButtonTheme.of(context);
final Color textColor = _getTextColor(theme, buttonTheme, color);
return RawMaterialButton(
onPressed: onPressed,
fillColor: color,
textStyle: theme.textTheme.button.copyWith(color: textColor),
highlightColor: highlightColor ?? theme.highlightColor,
splashColor: splashColor ?? theme.splashColor,
elevation: elevation ?? 2.0,
highlightElevation: highlightElevation ?? 8.0,
padding: padding ?? buttonTheme.padding,
constraints: buttonTheme.constraints.copyWith(
minWidth: minWidth,
minHeight: height,
),
shape: buttonTheme.shape,
child: child,
materialTapTargetSize: materialTapTargetSize ?? theme.materialTapTargetSize,
clipBehavior: clipBehavior,
);
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(FlagProperty('enabled', value: enabled, ifFalse: 'disabled'));
}
}
/// A widget to pad the area around a [MaterialButton]'s inner [Material]. /// A widget to pad the area around a [MaterialButton]'s inner [Material].
/// ///
/// Redirect taps that occur in the padded area around the child to the center /// Redirect taps that occur in the padded area around the child to the center
......
...@@ -5,7 +5,15 @@ ...@@ -5,7 +5,15 @@
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'color_scheme.dart';
import 'colors.dart';
import 'constants.dart';
import 'flat_button.dart';
import 'material_button.dart';
import 'outline_button.dart';
import 'raised_button.dart';
import 'theme.dart'; import 'theme.dart';
import 'theme_data.dart' show MaterialTapTargetSize;
/// Used with [ButtonTheme] and [ButtonThemeData] to define a button's base /// Used with [ButtonTheme] and [ButtonThemeData] to define a button's base
/// colors, and the defaults for the button's minimum size, internal padding, /// colors, and the defaults for the button's minimum size, internal padding,
...@@ -13,8 +21,8 @@ import 'theme.dart'; ...@@ -13,8 +21,8 @@ import 'theme.dart';
/// ///
/// See also: /// See also:
/// ///
/// * [RaisedButton], which styles itself based on the ambient [ButtonTheme]. /// * [RaisedButton], [FlatButton], [OutlineButton], which are configured
/// * [FlatButton], which styles itself based on the ambient [ButtonTheme]. /// based on the ambient [ButtonTheme].
enum ButtonTextTheme { enum ButtonTextTheme {
/// Button text is black or white depending on [ThemeData.brightness]. /// Button text is black or white depending on [ThemeData.brightness].
normal, normal,
...@@ -46,30 +54,19 @@ enum ButtonBarLayoutBehavior { ...@@ -46,30 +54,19 @@ enum ButtonBarLayoutBehavior {
/// can be overridden with [ButtonTheme]. /// can be overridden with [ButtonTheme].
/// ///
/// The actual appearance of buttons depends on the button theme, the /// The actual appearance of buttons depends on the button theme, the
/// button's enabled state, its elevation (if any) and the overall Material /// button's enabled state, its elevation (if any), and the overall [Theme].
/// theme.
/// ///
/// See also: /// See also:
/// ///
/// * [FlatButton] and [RaisedButton], which are styled based on the /// * [FlatButton] [RaisedButton], and [OutlineButton], which are styled
/// ambient button theme. /// based on the ambient button theme.
/// * [ThemeData.textTheme], `button` is the default text style for button labels.
/// * [ThemeData.buttonColor], the fill color for [RaisedButton]s unless the
/// button theme's text theme is [ButtonTextTheme.primary].
/// * [ThemeData.primaryColor], the fill or text color if a button theme's text
/// theme is [ButtonTextTheme.primary].
/// * [ThemeData.accentColor], the text color for buttons when button theme's
/// text theme is [ButtonTextTheme.accent].
/// * [ThemeData.disabled], the default text color for disabled buttons.
/// * [ThemeData.brightness], used to select contrasting text and fill colors.
/// * [ThemeData.highlightColor], a button [InkWell]'s default highlight color.
/// * [ThemeData.splashColor], a button [InkWell]'s default splash color.
/// * [RawMaterialButton], which can be used to configure a button that doesn't /// * [RawMaterialButton], which can be used to configure a button that doesn't
/// depend on any inherited themes. /// depend on any inherited themes.
class ButtonTheme extends InheritedWidget { class ButtonTheme extends InheritedWidget {
/// Creates a button theme. /// Creates a button theme.
/// ///
/// The [textTheme], [minWidth], and [height] arguments must not be null. /// The [textTheme], [minWidth], [height], and [colorScheme] arguments
/// must not be null.
ButtonTheme({ ButtonTheme({
Key key, Key key,
ButtonTextTheme textTheme = ButtonTextTheme.normal, ButtonTextTheme textTheme = ButtonTextTheme.normal,
...@@ -79,12 +76,19 @@ class ButtonTheme extends InheritedWidget { ...@@ -79,12 +76,19 @@ class ButtonTheme extends InheritedWidget {
EdgeInsetsGeometry padding, EdgeInsetsGeometry padding,
ShapeBorder shape, ShapeBorder shape,
bool alignedDropdown = false, bool alignedDropdown = false,
Color buttonColor,
Color disabledColor,
Color highlightColor,
Color splashColor,
ColorScheme colorScheme = const ColorScheme.light(),
MaterialTapTargetSize materialTapTargetSize,
Widget child, Widget child,
}) : assert(textTheme != null), }) : assert(textTheme != null),
assert(minWidth != null && minWidth >= 0.0), assert(minWidth != null && minWidth >= 0.0),
assert(height != null && height >= 0.0), assert(height != null && height >= 0.0),
assert(alignedDropdown != null), assert(alignedDropdown != null),
assert(layoutBehavior != null), assert(layoutBehavior != null),
assert(colorScheme != null),
data = ButtonThemeData( data = ButtonThemeData(
textTheme: textTheme, textTheme: textTheme,
minWidth: minWidth, minWidth: minWidth,
...@@ -93,6 +97,12 @@ class ButtonTheme extends InheritedWidget { ...@@ -93,6 +97,12 @@ class ButtonTheme extends InheritedWidget {
shape: shape, shape: shape,
alignedDropdown: alignedDropdown, alignedDropdown: alignedDropdown,
layoutBehavior: layoutBehavior, layoutBehavior: layoutBehavior,
buttonColor: buttonColor,
disabledColor: disabledColor,
highlightColor: highlightColor,
splashColor: splashColor,
colorScheme: colorScheme,
materialTapTargetSize: materialTapTargetSize,
), ),
super(key: key, child: child); super(key: key, child: child);
...@@ -128,12 +138,18 @@ class ButtonTheme extends InheritedWidget { ...@@ -128,12 +138,18 @@ class ButtonTheme extends InheritedWidget {
EdgeInsetsGeometry padding = const EdgeInsets.symmetric(horizontal: 8.0), EdgeInsetsGeometry padding = const EdgeInsets.symmetric(horizontal: 8.0),
ShapeBorder shape, ShapeBorder shape,
bool alignedDropdown = false, bool alignedDropdown = false,
Color buttonColor,
Color disabledColor,
Color highlightColor,
Color splashColor,
ColorScheme colorScheme = const ColorScheme.light(),
Widget child, Widget child,
ButtonBarLayoutBehavior layoutBehavior = ButtonBarLayoutBehavior.padded, ButtonBarLayoutBehavior layoutBehavior = ButtonBarLayoutBehavior.padded,
}) : assert(textTheme != null), }) : assert(textTheme != null),
assert(minWidth != null && minWidth >= 0.0), assert(minWidth != null && minWidth >= 0.0),
assert(height != null && height >= 0.0), assert(height != null && height >= 0.0),
assert(alignedDropdown != null), assert(alignedDropdown != null),
assert(colorScheme != null),
data = ButtonThemeData( data = ButtonThemeData(
textTheme: textTheme, textTheme: textTheme,
minWidth: minWidth, minWidth: minWidth,
...@@ -142,6 +158,11 @@ class ButtonTheme extends InheritedWidget { ...@@ -142,6 +158,11 @@ class ButtonTheme extends InheritedWidget {
shape: shape, shape: shape,
alignedDropdown: alignedDropdown, alignedDropdown: alignedDropdown,
layoutBehavior: layoutBehavior, layoutBehavior: layoutBehavior,
buttonColor: buttonColor,
disabledColor: disabledColor,
highlightColor: highlightColor,
splashColor: splashColor,
colorScheme: colorScheme,
), ),
super(key: key, child: child); super(key: key, child: child);
...@@ -174,6 +195,10 @@ class ButtonThemeData extends Diagnosticable { ...@@ -174,6 +195,10 @@ class ButtonThemeData extends Diagnosticable {
/// or [ThemeData]. /// or [ThemeData].
/// ///
/// The [textTheme], [minWidth], and [height] parameters must not be null. /// The [textTheme], [minWidth], and [height] parameters must not be null.
///
/// The ButtonTheme's methods that have a [MaterialButton] parameter and
/// have a name with a `get` prefix are used by [RaisedButton],
/// [OutlineButton], and [FlatButton] to configure a [RawMaterialButton].
const ButtonThemeData({ const ButtonThemeData({
this.textTheme = ButtonTextTheme.normal, this.textTheme = ButtonTextTheme.normal,
this.minWidth = 88.0, this.minWidth = 88.0,
...@@ -182,13 +207,25 @@ class ButtonThemeData extends Diagnosticable { ...@@ -182,13 +207,25 @@ class ButtonThemeData extends Diagnosticable {
ShapeBorder shape, ShapeBorder shape,
this.layoutBehavior = ButtonBarLayoutBehavior.padded, this.layoutBehavior = ButtonBarLayoutBehavior.padded,
this.alignedDropdown = false, this.alignedDropdown = false,
Color buttonColor,
Color disabledColor,
Color highlightColor,
Color splashColor,
this.colorScheme = const ColorScheme.light(),
MaterialTapTargetSize materialTapTargetSize,
}) : assert(textTheme != null), }) : assert(textTheme != null),
assert(minWidth != null && minWidth >= 0.0), assert(minWidth != null && minWidth >= 0.0),
assert(height != null && height >= 0.0), assert(height != null && height >= 0.0),
assert(alignedDropdown != null), assert(alignedDropdown != null),
assert(layoutBehavior != null), assert(layoutBehavior != null),
assert(colorScheme != null),
_buttonColor = buttonColor,
_disabledColor = disabledColor,
_highlightColor = highlightColor,
_splashColor = splashColor,
_padding = padding, _padding = padding,
_shape = shape; _shape = shape,
_materialTapTargetSize = materialTapTargetSize;
/// The minimum width for buttons. /// The minimum width for buttons.
/// ///
...@@ -205,10 +242,13 @@ class ButtonThemeData extends Diagnosticable { ...@@ -205,10 +242,13 @@ class ButtonThemeData extends Diagnosticable {
/// Defines a button's base colors, and the defaults for the button's minimum /// Defines a button's base colors, and the defaults for the button's minimum
/// size, internal padding, and shape. /// size, internal padding, and shape.
///
/// Despite the name, this property is not a [TextTheme], its value is not a
/// collection of [TextStyle]s.
final ButtonTextTheme textTheme; final ButtonTextTheme textTheme;
/// Defines whether a button bar should size itself with a minimum size /// Defines whether a [ButtonBar] should size itself with a minimum size
/// constraint or padding. /// constraint or with padding.
/// ///
/// Defaults to [ButtonBarLayoutBehavior.padded]. /// Defaults to [ButtonBarLayoutBehavior.padded].
final ButtonBarLayoutBehavior layoutBehavior; final ButtonBarLayoutBehavior layoutBehavior;
...@@ -232,16 +272,22 @@ class ButtonThemeData extends Diagnosticable { ...@@ -232,16 +272,22 @@ class ButtonThemeData extends Diagnosticable {
/// ///
/// Defaults to 24.0 on the left and right if [textTheme] is /// Defaults to 24.0 on the left and right if [textTheme] is
/// [ButtonTextTheme.primary], 16.0 on the left and right otherwise. /// [ButtonTextTheme.primary], 16.0 on the left and right otherwise.
///
/// See also:
///
/// * [getPadding], which is used by [RaisedButton], [OutlineButton]
/// and [FlatButton].
EdgeInsetsGeometry get padding { EdgeInsetsGeometry get padding {
if (_padding != null) if (_padding != null)
return _padding; return _padding;
switch (textTheme) { switch (textTheme) {
case ButtonTextTheme.normal: case ButtonTextTheme.normal:
case ButtonTextTheme.accent: case ButtonTextTheme.accent:
return const EdgeInsets.symmetric(horizontal: 16.0); return const EdgeInsets.symmetric(horizontal: 16.0);
case ButtonTextTheme.primary: case ButtonTextTheme.primary:
return const EdgeInsets.symmetric(horizontal: 24.0); return const EdgeInsets.symmetric(horizontal: 24.0);
} }
assert(false);
return EdgeInsets.zero; return EdgeInsets.zero;
} }
final EdgeInsetsGeometry _padding; final EdgeInsetsGeometry _padding;
...@@ -255,6 +301,11 @@ class ButtonThemeData extends Diagnosticable { ...@@ -255,6 +301,11 @@ class ButtonThemeData extends Diagnosticable {
/// Defaults to a rounded rectangle with circular corner radii of 4.0 if /// Defaults to a rounded rectangle with circular corner radii of 4.0 if
/// [textTheme] is [ButtonTextTheme.primary], a rounded rectangle with /// [textTheme] is [ButtonTextTheme.primary], a rounded rectangle with
/// circular corner radii of 2.0 otherwise. /// circular corner radii of 2.0 otherwise.
///
/// See also:
///
/// * [getShape], which is used by [RaisedButton], [OutlineButton]
/// and [FlatButton].
ShapeBorder get shape { ShapeBorder get shape {
if (_shape != null) if (_shape != null)
return _shape; return _shape;
...@@ -284,6 +335,386 @@ class ButtonThemeData extends Diagnosticable { ...@@ -284,6 +335,386 @@ class ButtonThemeData extends Diagnosticable {
/// This property only affects [DropdownButton] and its menu. /// This property only affects [DropdownButton] and its menu.
final bool alignedDropdown; final bool alignedDropdown;
/// The background fill color for [RaisedButton]s.
///
/// This property is null by default.
///
/// See also:
///
/// * [getFillColor], which is used by [RaisedButton] to compute its
/// background fill color.
final Color _buttonColor;
/// The background fill color for disabled [RaisedButton]s.
///
/// This property is null by default.
///
/// See also:
///
/// * [getDisabledFillColor], which is used by [RaisedButton] to compute its
/// background fill color.
final Color _disabledColor;
/// The color of the overlay that appears when a button is pressed.
///
/// This property is null by default.
///
/// See also:
///
/// * [getHighlightColor], which is used by [RaisedButton], [OutlineButton]
/// and [FlatButton].
final Color _highlightColor;
/// The color of the ink "splash" overlay that appears when a button is tapped.
///
/// This property is null by default.
///
/// See also:
///
/// * [getSplashColor], which is used by [RaisedButton], [OutlineButton]
/// and [FlatButton].
final Color _splashColor;
/// A set of thirteen colors that can be used to derive the button theme's
/// colors.
///
/// This property was added much later than the theme's set of highly
/// specific colors, like [ThemeData.buttonColor], [ThemeData.highlightColor],
/// [ThemeData.splashColor] etc.
///
/// The colors for new button classes can be defined exclusively in terms
/// of [colorScheme]. When it's possible, the existing buttons will
/// (continue to) gradually migrate to it.
final ColorScheme colorScheme;
// The minimum size of a button's tap target.
//
// This property is null by default.
//
// See also:
//
// * [getMaterialTargetTapSize], which is used by [RaisedButton],
// [OutlineButton] and [FlatButton].
final MaterialTapTargetSize _materialTapTargetSize;
/// The [button]'s overall brightness.
///
/// Returns the button's [MaterialButton.colorBrightness] it it is non-null,
/// otherwise the color scheme's [ColorScheme.brightness] is returned.
Brightness getBrightness(MaterialButton button) {
return button.colorBrightness ?? colorScheme.brightness;
}
/// Defines the [button]'s base colors, and the defaults for the button's
/// minimum size, internal padding, and shape.
///
/// Despite the name, this property is not the [TextTheme] whose
/// [TextTheme.button] is used as the button text's [TextStyle].
ButtonTextTheme getTextTheme(MaterialButton button) {
return button.textTheme ?? textTheme;
}
Color _getDisabledColor(MaterialButton button) {
return getBrightness(button) == Brightness.dark
? colorScheme.onSurface.withOpacity(0.30) // default == Colors.white30
: colorScheme.onSurface.withOpacity(0.38); // default == Colors.black38;
}
/// The foreground color of the [button]'s text and icon when
/// [MaterialButton.onPressed] is null (when MaterialButton.enabled is false).
///
/// Returns the button's [MaterialButton.disabledColor] if it is non-null.
/// Otherwise the color scheme's [ColorScheme.onSurface] color is returned
/// with its opacity set to 0.30 if [getBrightness] is dark, 0.38 otherwise.
Color getDisabledTextColor(MaterialButton button) {
if (button.disabledTextColor != null)
return button.disabledTextColor;
return _getDisabledColor(button);
}
/// The [button]'s background color when [MaterialButton.onPressed] is null
/// (when MaterialButton.enabled is false).
///
/// Returns the button's [MaterialButton.disabledColor] if it is non-null.
///
/// Otherwise the the value of the `disabledColor` constructor parameter
/// is returned, if it is non-null.
///
/// Otherwise the color scheme's [ColorScheme.onSurface] color is returned
/// with its opacity set to 0.3 if [getBrightness] is dark, 0.38 otherwise.
Color getDisabledFillColor(MaterialButton button) {
if (button.disabledColor != null)
return button.disabledColor;
if (_disabledColor != null)
return _disabledColor;
return _getDisabledColor(button);
}
/// The button's background fill color or null for buttons that don't have
/// a background color.
///
/// Returns [MaterialButton.color] if it is non-null and the button
/// is enabled.
///
/// Otherwise, returns [MaterialButton.disabledColor] if it is non-null and
/// the button is disabled.
///
/// Otherwise, if button is a [FlatButton] or an [OutlineButton] then null is
/// returned.
///
/// Otherwise, if button is a [RaisedButton], returns the `buttonColor`
/// constructor parameter if it was non-null and the button is enabled.
///
/// Otherwise the fill color depends on the value of [getTextTheme].
///
/// * [ButtonTextTheme.normal] or [ButtonTextTheme.accent], the
/// color scheme's [ColorScheme.primary] color if the [button] is enabled
/// the value of [getDisabledFillColor] otherwise.
/// * [ButtonTextTheme.primary], if the [button] is enabled then the value
/// of the `buttonColor` constructor parameter if it is non-null,
/// otherwise the color scheme's ColorScheme.primary color. If the button
/// is not enabled then the colorScheme's [ColorScheme.onSurface] color
/// with opacity 0.12.
Color getFillColor(MaterialButton button) {
final Color fillColor = button.enabled ? button.color : button.disabledColor;
if (fillColor != null)
return fillColor;
if (button is FlatButton || button is OutlineButton)
return null;
if (button.enabled && button is RaisedButton && _buttonColor != null)
return _buttonColor;
switch (getTextTheme(button)) {
case ButtonTextTheme.normal:
case ButtonTextTheme.accent:
return button.enabled ? colorScheme.primary : getDisabledFillColor(button);
case ButtonTextTheme.primary:
return button.enabled
? _buttonColor ?? colorScheme.primary
: colorScheme.onSurface.withOpacity(0.12);
}
assert(false);
return null;
}
/// The foreground color of the [button]'s text and icon.
///
/// If [button] is not [MaterialButton.enabled], the value of
/// [getDisabledTextColor] is returned. If the button is enabled and
/// [buttonTextColor] is non-null, then [buttonTextColor] is returned.
///
/// Otherwise the text color depends on the value of [getTextTheme]
/// and [getBrightness].
///
/// * [ButtonTextTheme.normal], [Colors.white] if [getBrightness] is dark,
/// otherwise [Colors.black87].
/// * ButtonTextTheme.accent], [colorScheme.secondary].
/// * [ButtonTextTheme.primary], if [getFillColor] is dark then [Colors.white],
/// otherwise if [button] is a [FlatButton] or an [OutlineButton] then
/// [colorScheme.primary], otherwise [Colors.black].
Color getTextColor(MaterialButton button) {
if (!button.enabled)
return getDisabledTextColor(button);
if (button.textColor != null)
return button.textColor;
switch (getTextTheme(button)) {
case ButtonTextTheme.normal:
return getBrightness(button) == Brightness.dark ? Colors.white : Colors.black87;
case ButtonTextTheme.accent:
return colorScheme.secondary;
case ButtonTextTheme.primary: {
final Color fillColor = getFillColor(button);
final bool fillIsDark = fillColor != null
? ThemeData.estimateBrightnessForColor(fillColor) == Brightness.dark
: getBrightness(button) == Brightness.dark;
if (fillIsDark)
return Colors.white;
if (button is FlatButton || button is OutlineButton)
return colorScheme.primary;
return Colors.black;
}
}
assert(false);
return null;
}
/// The color of the ink "splash" overlay that appears when the (enabled)
/// [button] is tapped.
///
/// Returns the button's [MaterialButton.splashColor] if it is non-null.
///
/// Otherwise, returns the value of the `splashColor` constructor parameter
/// it is non-null and [button] is a [RaisedButton] or an [OutlineButton].
///
/// Otherwise, returns the value of the `splashColor` constructor parameter
/// if it is non-null and [button] is a [FlatButton] and
/// [getTextTheme] is not [ButtonTextTheme.primary]
///
/// Otherwise, returns [getTextColor] with an opacity of 0.12.
Color getSplashColor(MaterialButton button) {
if (button.splashColor != null)
return button.splashColor;
if (_splashColor != null && (button is RaisedButton || button is OutlineButton))
return _splashColor;
if (_splashColor != null && button is FlatButton) {
switch (getTextTheme(button)) {
case ButtonTextTheme.normal:
case ButtonTextTheme.accent:
return _splashColor;
case ButtonTextTheme.primary:
break;
}
}
return getTextColor(button).withOpacity(0.12);
}
/// The color of the overlay that appears when the [button] is pressed.
///
/// Returns the button's [MaterialButton.highlightColor] if it is non-null.
/// Otherwise the highlight color depends on [getTextTheme]:
///
/// * [ButtonTextTheme.normal], [ButtonTextTheme.accent]: returns the
/// value of the `highlightColor` constructor parameter if it is non-null,
/// otherwise the value of [getTextColor] with opacity 0.16.
/// * [ButtonTextTheme.primary], returns [Colors.transparent].
Color getHighlightColor(MaterialButton button) {
if (button.highlightColor != null)
return button.highlightColor;
switch (getTextTheme(button)) {
case ButtonTextTheme.normal:
case ButtonTextTheme.accent:
return _highlightColor ?? getTextColor(button).withOpacity(0.16);
case ButtonTextTheme.primary:
return Colors.transparent;
}
assert(false);
return Colors.transparent;
}
/// The [button]'s elevation when it is enabled and has not been pressed.
///
/// Returns the button's [MaterialButton.elevation] if it is non-null.
///
/// If button is a [FlatButton] then elevation is 0.0, otherwise it is 2.0.
double getElevation(MaterialButton button) {
if (button.elevation != null)
return button.elevation;
if (button is FlatButton)
return 0.0;
return 2.0;
}
/// The [button]'s elevation when it is enabled and has been pressed.
///
/// 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
/// a [OutlineButton] then the highlight elevation is 2.0, otherise the
/// highlight elevation is 8.0.
double getHighlightElevation(MaterialButton button) {
if (button.highlightElevation != null)
return button.highlightElevation;
if (button is FlatButton)
return 0.0;
if (button is OutlineButton)
return 2.0;
return 8.0;
}
/// The [button]'s elevation when [MaterialButton.onPressed] is null (when
/// MaterialButton.enabled is false).
///
/// Returns the button's [MaterialButton.elevation] if it is non-null.
///
/// Otheriwse the disabled elevation is 0.0.
double getDisabledElevation(MaterialButton button) {
if (button.disabledElevation != null)
return button.disabledElevation;
return 0.0;
}
/// Padding for the [button]'s child (typically the button's label).
///
/// Returns the button's [MaterialButton.padding] if it is non-null.
///
/// If this is a button constructed with [RaisedButton.icon] or
/// [FlatButton.icon] or [OutlineButton.icon] then the padding is:
/// `EdgeInsetsDirectional.only(start: 12.0, end: 16.0)`.
///
/// Otherwise, returns [padding] if it is non-null.
///
/// Otherwise, returns horizontal padding of 24.0 on the left and right if
/// [getTextTheme] is [ButtonTextTheme.primary], 16.0 on the left and right
/// otherwise.
EdgeInsetsGeometry getPadding(MaterialButton button) {
if (button.padding != null)
return button.padding;
if (button is MaterialButtonWithIconMixin)
return const EdgeInsetsDirectional.only(start: 12.0, end: 16.0);
if (_padding != null)
return _padding;
switch (getTextTheme(button)) {
case ButtonTextTheme.normal:
case ButtonTextTheme.accent:
return const EdgeInsets.symmetric(horizontal: 16.0);
case ButtonTextTheme.primary:
return const EdgeInsets.symmetric(horizontal: 24.0);
}
assert(false);
return EdgeInsets.zero;
}
/// The shape of the [button]'s [Material].
///
/// Returns the button's [MaterialButton.shape] if it is non-null, otherwise
/// [shape] is returned.
ShapeBorder getShape(MaterialButton button) {
return button.shape ?? shape;
}
/// The duration of the [button]'s highlight animation.
///
/// Returns the button's [MaterialButton.animationDuration] it if is non-null,
/// otherwise 200ms.
Duration getAnimationDuration(MaterialButton button) {
return button.animationDuration ?? kThemeChangeDuration;
}
/// The [BoxConstraints] that the define the [button]'s size.
///
/// By default this method just returns [constraints]. Subclasses
/// could override this method to return a value that was,
/// for example, based on the button's type.
BoxConstraints getConstraints(MaterialButton button) => constraints;
/// The minimum size of the [button]'s tap target.
///
/// Returns the button's [MaterialButton.tapTargetSize] if it is non-null.
///
/// Otherwise the value of the [materialTapTargetSize] constructor
/// parameter is returned if that's non-null.
///
/// Otherwise [MaterialTapTargetSize.padded] is returned.
MaterialTapTargetSize getMaterialTapTargetSize(MaterialButton button) {
return button.materialTapTargetSize ?? _materialTapTargetSize ?? MaterialTapTargetSize.padded;
}
/// Creates a copy of this button theme data object with the matching fields /// Creates a copy of this button theme data object with the matching fields
/// replaced with the non-null parameter values. /// replaced with the non-null parameter values.
ButtonThemeData copyWith({ ButtonThemeData copyWith({
...@@ -293,6 +724,12 @@ class ButtonThemeData extends Diagnosticable { ...@@ -293,6 +724,12 @@ class ButtonThemeData extends Diagnosticable {
EdgeInsetsGeometry padding, EdgeInsetsGeometry padding,
ShapeBorder shape, ShapeBorder shape,
bool alignedDropdown, bool alignedDropdown,
Color buttonColor,
Color disabledColor,
Color highlightColor,
Color splashColor,
ColorScheme colorScheme,
MaterialTapTargetSize materialTapTargetSize,
}) { }) {
return ButtonThemeData( return ButtonThemeData(
textTheme: textTheme ?? this.textTheme, textTheme: textTheme ?? this.textTheme,
...@@ -301,6 +738,12 @@ class ButtonThemeData extends Diagnosticable { ...@@ -301,6 +738,12 @@ class ButtonThemeData extends Diagnosticable {
padding: padding ?? this.padding, padding: padding ?? this.padding,
shape: shape ?? this.shape, shape: shape ?? this.shape,
alignedDropdown: alignedDropdown ?? this.alignedDropdown, alignedDropdown: alignedDropdown ?? this.alignedDropdown,
buttonColor: buttonColor ?? _buttonColor,
disabledColor: disabledColor ?? _disabledColor,
highlightColor: highlightColor ?? _highlightColor,
splashColor: splashColor ?? _splashColor,
colorScheme: colorScheme ?? this.colorScheme,
materialTapTargetSize: materialTapTargetSize ?? _materialTapTargetSize,
); );
} }
...@@ -314,7 +757,13 @@ class ButtonThemeData extends Diagnosticable { ...@@ -314,7 +757,13 @@ class ButtonThemeData extends Diagnosticable {
&& height == typedOther.height && height == typedOther.height
&& padding == typedOther.padding && padding == typedOther.padding
&& shape == typedOther.shape && shape == typedOther.shape
&& alignedDropdown == typedOther.alignedDropdown; && alignedDropdown == typedOther.alignedDropdown
&& _buttonColor == typedOther._buttonColor
&& _disabledColor == typedOther._disabledColor
&& _highlightColor == typedOther._highlightColor
&& _splashColor == typedOther._splashColor
&& colorScheme == typedOther.colorScheme
&& _materialTapTargetSize == typedOther._materialTapTargetSize;
} }
@override @override
...@@ -326,6 +775,12 @@ class ButtonThemeData extends Diagnosticable { ...@@ -326,6 +775,12 @@ class ButtonThemeData extends Diagnosticable {
padding, padding,
shape, shape,
alignedDropdown, alignedDropdown,
_buttonColor,
_disabledColor,
_highlightColor,
_splashColor,
colorScheme,
_materialTapTargetSize,
); );
} }
...@@ -343,5 +798,11 @@ class ButtonThemeData extends Diagnosticable { ...@@ -343,5 +798,11 @@ class ButtonThemeData extends Diagnosticable {
defaultValue: defaultTheme.alignedDropdown, defaultValue: defaultTheme.alignedDropdown,
ifTrue: 'dropdown width matches button', ifTrue: 'dropdown width matches button',
)); ));
properties.add(DiagnosticsProperty<Color>('buttonColor', _buttonColor, defaultValue: null));
properties.add(DiagnosticsProperty<Color>('disabledColor', _disabledColor, defaultValue: null));
properties.add(DiagnosticsProperty<Color>('highlightColor', _highlightColor, defaultValue: null));
properties.add(DiagnosticsProperty<Color>('splashColor', _splashColor, defaultValue: null));
properties.add(DiagnosticsProperty<ColorScheme>('colorScheme', colorScheme, defaultValue: defaultTheme.colorScheme));
properties.add(DiagnosticsProperty<MaterialTapTargetSize>('materialTapTargetSize', _materialTapTargetSize, defaultValue: null));
} }
} }
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart' show Brightness;
import 'package:flutter/widgets.dart';
import 'colors.dart';
import 'theme_data.dart';
/// A set of twelve colors based on the
/// [Material spec](https://material.io/design/color/the-color-system.html)
/// that can be used to configure the color properties of most components.
///
/// The [Theme] has a color scheme, [ThemeData.colorScheme], which is constructed
/// with [ColorScheme.fromSwatch].
@immutable
class ColorScheme extends Diagnosticable {
const ColorScheme({
@required this.primary,
@required this.primaryVariant,
@required this.secondary,
@required this.secondaryVariant,
@required this.surface,
@required this.background,
@required this.error,
@required this.onPrimary,
@required this.onSecondary,
@required this.onSurface,
@required this.onBackground,
@required this.onError,
@required this.brightness,
}) : assert(primary != null),
assert(primaryVariant != null),
assert(secondary != null),
assert(secondaryVariant != null),
assert(surface != null),
assert(background != null),
assert(error != null),
assert(onPrimary != null),
assert(onSecondary != null),
assert(onSurface != null),
assert(onBackground != null),
assert(onError != null),
assert(brightness != null);
const ColorScheme.light({
this.primary = const Color(0xff6200ee),
this.primaryVariant = const Color(0xff3700b3),
this.secondary = const Color(0xff03dac6),
this.secondaryVariant = const Color(0xff018786),
this.surface = Colors.white,
this.background = Colors.white,
this.error = const Color(0xffb00020),
this.onPrimary = Colors.white,
this.onSecondary = Colors.black,
this.onSurface = Colors.black,
this.onBackground = Colors.black,
this.onError = Colors.white,
this.brightness = Brightness.light,
}) : assert(primary != null),
assert(primaryVariant != null),
assert(secondary != null),
assert(secondaryVariant != null),
assert(surface != null),
assert(background != null),
assert(error != null),
assert(onPrimary != null),
assert(onSecondary != null),
assert(onSurface != null),
assert(onBackground != null),
assert(onError != null),
assert(brightness != null);
const ColorScheme.dark({
this.primary = const Color(0xffbb86fc),
this.primaryVariant = const Color(0xff4b01d0),
this.secondary = const Color(0xff03dac6),
this.secondaryVariant = const Color(0xff03dac6),
this.surface = Colors.black,
this.background = Colors.black,
this.error = const Color(0xffb00020),
this.onPrimary = Colors.black,
this.onSecondary = Colors.black,
this.onSurface = Colors.white,
this.onBackground = Colors.white,
this.onError = Colors.black,
this.brightness = Brightness.dark,
}) : assert(primary != null),
assert(primaryVariant != null),
assert(secondary != null),
assert(secondaryVariant != null),
assert(surface != null),
assert(background != null),
assert(error != null),
assert(onPrimary != null),
assert(onSecondary != null),
assert(onSurface != null),
assert(onBackground != null),
assert(onError != null),
assert(brightness != null);
factory ColorScheme.fromSwatch({
MaterialColor primarySwatch = Colors.blue,
Color primaryColorDark,
Color accentColor,
Color cardColor,
Color backgroundColor,
Color errorColor,
Brightness brightness = Brightness.light,
}) {
assert(primarySwatch != null);
assert(brightness != null);
final bool isDark = brightness == Brightness.dark;
final bool primaryIsDark = _brightnessFor(primarySwatch) == Brightness.dark;
final Color secondary = accentColor ?? (isDark ? Colors.tealAccent[200] : primarySwatch);
final bool secondaryIsDark = _brightnessFor(secondary) == Brightness.dark;
return ColorScheme(
primary: primarySwatch,
primaryVariant: primaryColorDark ?? (isDark ? Colors.black : primarySwatch[700]),
secondary: secondary,
secondaryVariant: isDark ? Colors.tealAccent[700] : primarySwatch[700],
surface: cardColor ?? (isDark ? Colors.grey[800] : Colors.white),
background: backgroundColor ?? (isDark ? Colors.grey[700] : primarySwatch[200]),
error: errorColor ?? Colors.red[700],
onPrimary: primaryIsDark ? Colors.white : Colors.black,
onSecondary: secondaryIsDark ? Colors.white : Colors.black,
onSurface: isDark ? Colors.white : Colors.black,
onBackground: primaryIsDark ? Colors.white : Colors.black,
onError: isDark ? Colors.black : Colors.white,
brightness: brightness,
);
}
static Brightness _brightnessFor(Color color) => ThemeData.estimateBrightnessForColor(color);
final Color primary;
final Color primaryVariant;
final Color secondary;
final Color secondaryVariant;
final Color background;
final Color error;
final Color surface;
final Color onPrimary;
final Color onSecondary;
final Color onSurface;
final Color onError;
final Color onBackground;
final Brightness brightness;
ColorScheme copyWith({
Color primary,
Color primaryVariant,
Color secondary,
Color secondaryVariant,
Color surface,
Color background,
Color error,
Color onPrimary,
Color onSecondary,
Color onSurface,
Color onBackground,
Color onError,
Brightness brightness,
}) {
return ColorScheme(
primary: primary ?? this.primary,
primaryVariant: primaryVariant ?? this.primaryVariant,
secondary: secondary ?? this.secondary,
secondaryVariant: secondaryVariant ?? this.secondaryVariant,
surface: surface ?? this.surface,
background: background ?? this.background,
error: error ?? this.error,
onPrimary: onPrimary ?? this.onPrimary,
onSecondary: onSecondary ?? this.onSecondary,
onSurface: onSurface ?? this.onSurface,
onBackground: onBackground ?? this.onBackground,
onError: onError ?? this.onError,
brightness: brightness ?? this.brightness,
);
}
static ColorScheme lerp(ColorScheme a, ColorScheme b, double t) {
return ColorScheme(
primary: Color.lerp(a.primary, b.primary, t),
primaryVariant: Color.lerp(a.primaryVariant, b.primaryVariant, t),
secondary: Color.lerp(a.secondary, b.secondary, t),
secondaryVariant: Color.lerp(a.secondaryVariant, b.secondaryVariant, t),
surface: Color.lerp(a.surface, b.surface, t),
background: Color.lerp(a.background, b.background, t),
error: Color.lerp(a.error, b.error, t),
onPrimary: Color.lerp(a.onPrimary, b.onPrimary, t),
onSecondary: Color.lerp(a.onSecondary, b.onSecondary, t),
onSurface: Color.lerp(a.onSurface, b.onSurface, t),
onBackground: Color.lerp(a.onBackground, b.onBackground, t),
onError: Color.lerp(a.onError, b.onError, t),
brightness: t < 0.5 ? a.brightness : b.brightness,
);
}
@override
bool operator ==(Object other) {
if (identical(this, other))
return true;
if (other.runtimeType != runtimeType)
return false;
final ColorScheme otherScheme = other;
return otherScheme.primary == primary
&& otherScheme.primaryVariant == primaryVariant
&& otherScheme.secondary == secondary
&& otherScheme.secondaryVariant == secondaryVariant
&& otherScheme.surface == surface
&& otherScheme.background == background
&& otherScheme.error == error
&& otherScheme.onPrimary == onPrimary
&& otherScheme.onSecondary == onSecondary
&& otherScheme.onSurface == onSurface
&& otherScheme.onBackground == onBackground
&& otherScheme.onError == onError
&& otherScheme.brightness == brightness;
}
@override
int get hashCode {
return hashValues(
primary,
primaryVariant,
secondary,
secondaryVariant,
surface,
background,
error,
onPrimary,
onSecondary,
onSurface,
onBackground,
onError,
brightness,
);
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
const ColorScheme defaultScheme = ColorScheme.light();
properties.add(DiagnosticsProperty<Color>('primary', primary, defaultValue: defaultScheme.primary));
properties.add(DiagnosticsProperty<Color>('primaryVariant', primaryVariant, defaultValue: defaultScheme.primaryVariant));
properties.add(DiagnosticsProperty<Color>('secondary', secondary, defaultValue: defaultScheme.secondary));
properties.add(DiagnosticsProperty<Color>('secondaryVariant', secondaryVariant, defaultValue: defaultScheme.secondaryVariant));
properties.add(DiagnosticsProperty<Color>('surface', surface, defaultValue: defaultScheme.surface));
properties.add(DiagnosticsProperty<Color>('background', background, defaultValue: defaultScheme.background));
properties.add(DiagnosticsProperty<Color>('error', error, defaultValue: defaultScheme.error));
properties.add(DiagnosticsProperty<Color>('onPrimary', onPrimary, defaultValue: defaultScheme.onPrimary));
properties.add(DiagnosticsProperty<Color>('onSecondary', onSecondary, defaultValue: defaultScheme.onSecondary));
properties.add(DiagnosticsProperty<Color>('onSurface', onSurface, defaultValue: defaultScheme.onSurface));
properties.add(DiagnosticsProperty<Color>('onBackground', onBackground, defaultValue: defaultScheme.onBackground));
properties.add(DiagnosticsProperty<Color>('onError', onError, defaultValue: defaultScheme.onError));
properties.add(DiagnosticsProperty<Brightness>('brightness', brightness, defaultValue: defaultScheme.brightness));
}
}
...@@ -7,7 +7,7 @@ import 'package:flutter/widgets.dart'; ...@@ -7,7 +7,7 @@ import 'package:flutter/widgets.dart';
import 'button.dart'; import 'button.dart';
import 'button_theme.dart'; import 'button_theme.dart';
import 'colors.dart'; import 'material_button.dart';
import 'theme.dart'; import 'theme.dart';
import 'theme_data.dart'; import 'theme_data.dart';
...@@ -49,26 +49,43 @@ import 'theme_data.dart'; ...@@ -49,26 +49,43 @@ import 'theme_data.dart';
/// * [InkWell], which implements the ink splash part of a flat button. /// * [InkWell], which implements the ink splash part of a flat button.
/// * [RawMaterialButton], the widget this widget is based on. /// * [RawMaterialButton], the widget this widget is based on.
/// * <https://material.google.com/components/buttons.html> /// * <https://material.google.com/components/buttons.html>
class FlatButton extends StatelessWidget { class FlatButton extends MaterialButton {
/// Create a simple text button. /// Create a simple text button.
const FlatButton({ const FlatButton({
Key key, Key key,
@required this.onPressed, @required VoidCallback onPressed,
this.onHighlightChanged, ValueChanged<bool> onHighlightChanged,
this.textTheme, ButtonTextTheme textTheme,
this.textColor, Color textColor,
this.disabledTextColor, Color disabledTextColor,
this.color, Color color,
this.disabledColor, Color disabledColor,
this.highlightColor, Color highlightColor,
this.splashColor, Color splashColor,
this.colorBrightness, Brightness colorBrightness,
this.padding, EdgeInsetsGeometry padding,
this.shape, ShapeBorder shape,
this.clipBehavior = Clip.none, Clip clipBehavior = Clip.none,
this.materialTapTargetSize, MaterialTapTargetSize materialTapTargetSize,
@required this.child, @required Widget child,
}) : assert(clipBehavior != null), super(key: key); }) : super(
key: key,
onPressed: onPressed,
onHighlightChanged: onHighlightChanged,
textTheme: textTheme,
textColor: textColor,
disabledTextColor: disabledTextColor,
color: color,
disabledColor: disabledColor,
highlightColor: highlightColor,
splashColor: splashColor,
colorBrightness: colorBrightness,
padding: padding,
shape: shape,
clipBehavior: clipBehavior,
materialTapTargetSize: materialTapTargetSize,
child: child,
);
/// Create a text button from a pair of widgets that serve as the button's /// Create a text button from a pair of widgets that serve as the button's
/// [icon] and [label]. /// [icon] and [label].
...@@ -77,244 +94,47 @@ class FlatButton extends StatelessWidget { ...@@ -77,244 +94,47 @@ class FlatButton extends StatelessWidget {
/// 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 [icon], [label], and [clipBehavior] arguments must not be null. /// The [icon], [label], and [clipBehavior] arguments must not be null.
FlatButton.icon({ factory FlatButton.icon({
Key key, Key key,
@required this.onPressed, @required VoidCallback onPressed,
this.onHighlightChanged, ValueChanged<bool> onHighlightChanged,
this.textTheme, ButtonTextTheme textTheme,
this.textColor, Color textColor,
this.disabledTextColor, Color disabledTextColor,
this.color, Color color,
this.disabledColor, Color disabledColor,
this.highlightColor, Color highlightColor,
this.splashColor, Color splashColor,
this.colorBrightness, Brightness colorBrightness,
this.shape, EdgeInsetsGeometry padding,
this.clipBehavior = Clip.none, ShapeBorder shape,
this.materialTapTargetSize, Clip clipBehavior,
MaterialTapTargetSize materialTapTargetSize,
@required Widget icon, @required Widget icon,
@required Widget label, @required Widget label,
}) : assert(icon != null), }) = _FlatButtonWithIcon;
assert(label != null),
assert(clipBehavior != null),
padding = const EdgeInsetsDirectional.only(start: 12.0, end: 16.0),
child = Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
icon,
const SizedBox(width: 8.0),
label,
],
),
super(key: key);
/// Called when the button is tapped or otherwise activated.
///
/// If this is set to null, the button will be disabled, see [enabled].
final VoidCallback onPressed;
/// Called by the underlying [InkWell] widget's [InkWell.onHighlightChanged]
/// callback.
final ValueChanged<bool> onHighlightChanged;
/// Defines the button's base colors, and the defaults for the button's minimum
/// size, internal padding, and shape.
///
/// Defaults to `ButtonTheme.of(context).textTheme`.
final ButtonTextTheme textTheme;
/// The color to use for this button's text.
///
/// The button's [Material.textStyle] will be the current theme's button
/// text style, [ThemeData.textTheme.button], configured with this color.
///
/// The default text color depends on the button theme's text theme,
/// [ButtonThemeData.textTheme].
///
/// See also:
/// * [disabledTextColor], the text color to use when the button has been
/// disabled.
final Color textColor;
/// The color to use for this button's text when the button is disabled.
///
/// The button's [Material.textStyle] will be the current theme's button
/// text style, [ThemeData.textTheme.button], configured with this color.
///
/// The default value is the theme's disabled color,
/// [ThemeData.disabledColor].
///
/// See also:
/// * [textColor] - The color to use for this button's text when the button is [enabled].
final Color disabledTextColor;
/// The button's fill color, displayed by its [Material], while it
/// is in its default (unpressed, enabled) state.
///
/// Typically not specified for [FlatButton]s.
///
/// The default is null.
final Color color;
/// The fill color of the button when the button is disabled.
///
/// Typically not specified for [FlatButton]s.
///
/// The default is null.
final Color disabledColor;
/// The splash color of the button's [InkWell].
///
/// The ink splash indicates that the button has been touched. It
/// appears on top of the button's child and spreads in an expanding
/// circle beginning where the touch occurred.
///
/// If [textTheme] is [ButtonTextTheme.primary], the default splash color is
/// is based on the theme's primary color [ThemeData.primaryColor],
/// otherwise it's the current theme's splash color, [ThemeData.splashColor].
///
/// The appearance of the splash can be configured with the theme's splash
/// factory, [ThemeData.splashFactory].
final Color splashColor;
/// The highlight color of the button's [InkWell].
///
/// The highlight indicates that the button is actively being pressed. It
/// appears on top of the button's child and quickly spreads to fill
/// the button, and then fades out.
///
/// If [textTheme] is [ButtonTextTheme.primary], the default highlight color is
/// transparent (in other words the highlight doesn't appear). Otherwise it's
/// the current theme's highlight color, [ThemeData.highlightColor].
final Color highlightColor;
/// The theme brightness to use for this button.
///
/// Defaults to the theme's brightness, [ThemeData.brightness].
final Brightness colorBrightness;
/// Configures the minimum size of the tap target.
///
/// Defaults to [ThemeData.materialTapTargetSize].
///
/// See also:
///
/// * [MaterialTapTargetSize], for a description of how this affects tap targets.
final MaterialTapTargetSize materialTapTargetSize;
/// The widget below this widget in the tree.
///
/// Typically a [Text] widget in all caps.
final Widget child;
/// Whether the button is enabled or disabled.
///
/// Buttons are disabled by default. To enable a button, set its [onPressed]
/// property to a non-null value.
bool get enabled => onPressed != null;
/// The internal padding for the button's [child].
///
/// Defaults to the value from the current [ButtonTheme],
/// [ButtonThemeData.padding].
final EdgeInsetsGeometry padding;
/// The shape of the button's [Material].
///
/// 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 as well.
final ShapeBorder shape;
/// {@macro flutter.widgets.Clip}
final Clip clipBehavior;
Brightness _getBrightness(ThemeData theme) {
return colorBrightness ?? theme.brightness;
}
ButtonTextTheme _getTextTheme(ButtonThemeData buttonTheme) {
return textTheme ?? buttonTheme.textTheme;
}
Color _getTextColor(ThemeData theme, ButtonThemeData buttonTheme, Color fillColor) {
final Color color = enabled ? textColor : disabledTextColor;
if (color != null)
return color;
final bool themeIsDark = _getBrightness(theme) == Brightness.dark;
final bool fillIsDark = fillColor == null
? themeIsDark
: ThemeData.estimateBrightnessForColor(fillColor) == Brightness.dark;
switch (_getTextTheme(buttonTheme)) {
case ButtonTextTheme.normal:
return enabled
? (themeIsDark ? Colors.white : Colors.black87)
: theme.disabledColor;
case ButtonTextTheme.accent:
return enabled
? theme.accentColor
: theme.disabledColor;
case ButtonTextTheme.primary:
return enabled
? (fillIsDark ? Colors.white : theme.primaryColor)
: (themeIsDark ? Colors.white30 : Colors.black38);
}
return null;
}
Color _getSplashColor(ThemeData theme, ButtonThemeData buttonTheme) {
if (splashColor != null)
return splashColor;
switch (_getTextTheme(buttonTheme)) {
case ButtonTextTheme.normal:
case ButtonTextTheme.accent:
return theme.splashColor;
case ButtonTextTheme.primary:
return _getBrightness(theme) == Brightness.dark
? Colors.white12
: theme.primaryColor.withOpacity(0.12);
}
return Colors.transparent;
}
Color _getHighlightColor(ThemeData theme, ButtonThemeData buttonTheme) {
if (highlightColor != null)
return highlightColor;
switch (_getTextTheme(buttonTheme)) {
case ButtonTextTheme.normal:
case ButtonTextTheme.accent:
return theme.highlightColor;
case ButtonTextTheme.primary:
return Colors.transparent;
}
return Colors.transparent;
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context); final ThemeData theme = Theme.of(context);
final ButtonThemeData buttonTheme = ButtonTheme.of(context); final ButtonThemeData buttonTheme = ButtonTheme.of(context);
final Color fillColor = enabled ? color : disabledColor;
final Color textColor = _getTextColor(theme, buttonTheme, fillColor);
return RawMaterialButton( return RawMaterialButton(
onPressed: onPressed, onPressed: onPressed,
onHighlightChanged: onHighlightChanged, onHighlightChanged: onHighlightChanged,
fillColor: fillColor, clipBehavior: clipBehavior ?? Clip.none,
textStyle: theme.textTheme.button.copyWith(color: textColor), fillColor: buttonTheme.getFillColor(this),
highlightColor: _getHighlightColor(theme, buttonTheme), textStyle: theme.textTheme.button.copyWith(color: buttonTheme.getTextColor(this)),
splashColor: _getSplashColor(theme, buttonTheme), highlightColor: buttonTheme.getHighlightColor(this),
elevation: 0.0, splashColor: buttonTheme.getSplashColor(this),
highlightElevation: 0.0, elevation: buttonTheme.getElevation(this),
materialTapTargetSize: materialTapTargetSize ?? theme.materialTapTargetSize, highlightElevation: buttonTheme.getHighlightElevation(this),
padding: padding ?? buttonTheme.padding, disabledElevation: buttonTheme.getDisabledElevation(this),
constraints: buttonTheme.constraints, padding: buttonTheme.getPadding(this),
shape: shape ?? buttonTheme.shape, constraints: buttonTheme.getConstraints(this),
clipBehavior: clipBehavior, shape: buttonTheme.getShape(this),
animationDuration: buttonTheme.getAnimationDuration(this),
materialTapTargetSize: buttonTheme.getMaterialTapTargetSize(this),
child: child, child: child,
); );
} }
...@@ -336,3 +156,56 @@ class FlatButton extends StatelessWidget { ...@@ -336,3 +156,56 @@ class FlatButton extends StatelessWidget {
properties.add(DiagnosticsProperty<MaterialTapTargetSize>('materialTapTargetSize', materialTapTargetSize, defaultValue: null)); properties.add(DiagnosticsProperty<MaterialTapTargetSize>('materialTapTargetSize', materialTapTargetSize, defaultValue: null));
} }
} }
/// The type of of FlatButtons created with [FlatButton.icon].
///
/// This class only exists to give FlatButtons created with [FlatButton.icon]
/// a distinct class for the sake of [ButtonTheme]. It can not be instantiated.
class _FlatButtonWithIcon extends FlatButton with MaterialButtonWithIconMixin {
_FlatButtonWithIcon({
Key key,
@required VoidCallback onPressed,
ValueChanged<bool> onHighlightChanged,
ButtonTextTheme textTheme,
Color textColor,
Color disabledTextColor,
Color color,
Color disabledColor,
Color highlightColor,
Color splashColor,
Brightness colorBrightness,
EdgeInsetsGeometry padding,
ShapeBorder shape,
Clip clipBehavior,
MaterialTapTargetSize materialTapTargetSize,
@required Widget icon,
@required Widget label,
}) : assert(icon != null),
assert(label != null),
super(
key: key,
onPressed: onPressed,
onHighlightChanged: onHighlightChanged,
textTheme: textTheme,
textColor: textColor,
disabledTextColor: disabledTextColor,
color: color,
disabledColor: disabledColor,
highlightColor: highlightColor,
splashColor: splashColor,
colorBrightness: colorBrightness,
padding: padding,
shape: shape,
clipBehavior: clipBehavior,
materialTapTargetSize: materialTapTargetSize,
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
icon,
const SizedBox(width: 8.0),
label,
],
),
);
}
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'button.dart';
import 'button_theme.dart';
import 'constants.dart';
import 'ink_well.dart';
import 'material.dart';
import 'theme.dart';
import 'theme_data.dart';
/// A utility class for building Material buttons that depend on the
/// ambient [ButtonTheme] and [Theme].
///
/// The button's size will expand to fit the child widget, if necessary.
///
/// MaterialButtons whose [onPressed] handler is null will be disabled. To have
/// an enabled button, make sure to pass a non-null value for onPressed.
///
/// Rather than using this class directly, consider using [FlatButton],
/// OutlineButton, or [RaisedButton], which configure this class with
/// appropriate defaults that match the material design specification.
///
/// To create a button directly, without inheriting theme defaults, use
/// [RawMaterialButton].
///
/// If you want an ink-splash effect for taps, but don't want to use a button,
/// consider using [InkWell] directly.
///
/// See also:
///
/// * [IconButton], to create buttons that contain icons rather than text.
class MaterialButton extends StatelessWidget {
/// Creates a material button.
///
/// Rather than creating a material button directly, consider using
/// [FlatButton] or [RaisedButton]. To create a custom Material button
/// consider using [RawMaterialButton].
///
/// The [clipBehavior] argument must not be null.
const MaterialButton({
Key key,
@required this.onPressed,
this.onHighlightChanged,
this.textTheme,
this.textColor,
this.disabledTextColor,
this.color,
this.disabledColor,
this.highlightColor,
this.splashColor,
this.colorBrightness,
this.elevation,
this.highlightElevation,
this.disabledElevation,
this.padding,
this.shape,
this.clipBehavior = Clip.none,
this.materialTapTargetSize,
this.animationDuration,
this.minWidth,
this.height,
this.child,
}) : super(key: key);
/// The callback that is called when the button is tapped or otherwise activated.
///
/// If this is set to null, the button will be disabled.
final VoidCallback onPressed;
/// Called by the underlying [InkWell] widget's [InkWell.onHighlightChanged]
/// callback.
final ValueChanged<bool> onHighlightChanged;
/// Defines the button's base colors, and the defaults for the button's minimum
/// size, internal padding, and shape.
///
/// Defaults to `ButtonTheme.of(context).textTheme`.
final ButtonTextTheme textTheme;
/// The color to use for this button's text.
///
/// The button's [Material.textStyle] will be the current theme's button
/// text style, [ThemeData.textTheme.button], configured with this color.
///
/// The default text color depends on the button theme's text theme,
/// [ButtonThemeData.textTheme].
///
/// See also:
/// * [disabledTextColor], the text color to use when the button has been
/// disabled.
final Color textColor;
/// The color to use for this button's text when the button is disabled.
///
/// The button's [Material.textStyle] will be the current theme's button
/// text style, [ThemeData.textTheme.button], configured with this color.
///
/// The default value is the theme's disabled color,
/// [ThemeData.disabledColor].
///
/// See also:
/// * [textColor] - The color to use for this button's text when the button is [enabled].
final Color disabledTextColor;
/// The button's fill color, displayed by its [Material], while it
/// is in its default (unpressed, [enabled]) state.
///
/// The default fill color is the theme's button color, [ThemeData.buttonColor].
///
/// See also:
/// * [disabledColor] - the fill color of the button when the button is disabled.
final Color color;
/// The fill color of the button when the button is disabled.
///
/// The default value of this color is the theme's disabled color,
/// [ThemeData.disabledColor].
///
/// See also:
/// * [color] - the fill color of the button when the button is [enabled].
final Color disabledColor;
/// The splash color of the button's [InkWell].
///
/// The ink splash indicates that the button has been touched. It
/// appears on top of the button's child and spreads in an expanding
/// circle beginning where the touch occurred.
///
/// The default splash color is the current theme's splash color,
/// [ThemeData.splashColor].
///
/// The appearance of the splash can be configured with the theme's splash
/// factory, [ThemeData.splashFactory].
final Color splashColor;
/// The highlight color of the button's [InkWell].
///
/// The highlight indicates that the button is actively being pressed. It
/// appears on top of the button's child and quickly spreads to fill
/// the button, and then fades out.
///
/// If [textTheme] is [ButtonTextTheme.primary], the default highlight color is
/// transparent (in other words the highlight doesn't appear). Otherwise it's
/// the current theme's highlight color, [ThemeData.highlightColor].
final Color highlightColor;
/// The z-coordinate at which to place this button. This controls the size of
/// the shadow below the raised button.
///
/// Defaults to 2, the appropriate elevation for raised buttons.
///
/// See also:
///
/// * [FlatButton], a button with no elevation or fill color.
/// * [disabledElevation], the elevation when the button is disabled.
/// * [highlightElevation], the elevation when the button is pressed.
final double elevation;
/// The elevation for the button's [Material] when the button
/// is [enabled] and pressed.
///
/// This controls the size of the shadow below the button. When a tap
/// down gesture occurs within the button, its [InkWell] displays a
/// [highlightColor] "highlight".
///
/// Defaults to 8.0.
///
/// See also:
///
/// * [elevation], the default elevation.
/// * [disabledElevation], the elevation when the button is disabled.
final double highlightElevation;
/// The elevation for the button's [Material] when the button
/// is not [enabled].
///
/// Defaults to 0.0.
///
/// See also:
///
/// * [elevation], the default elevation.
/// * [highlightElevation], the elevation when the button is pressed.
final double disabledElevation;
/// The theme brightness to use for this button.
///
/// Defaults to the theme's brightness, [ThemeData.brightness].
final Brightness colorBrightness;
/// The button's label.
///
/// Often a [Text] widget in all caps.
final Widget child;
/// Whether the button is enabled or disabled.
///
/// Buttons are disabled by default. To enable a button, set its [onPressed]
/// property to a non-null value.
bool get enabled => onPressed != null;
/// The internal padding for the button's [child].
///
/// Defaults to the value from the current [ButtonTheme],
/// [ButtonThemeData.padding].
final EdgeInsetsGeometry padding;
/// The shape of the button's [Material].
///
/// 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 as well.
final ShapeBorder shape;
/// {@macro flutter.widgets.Clip}
final Clip clipBehavior;
/// Defines the duration of animated changes for [shape] and [elevation].
///
/// The default value is [kThemeChangeDuration].
final Duration animationDuration;
/// Configures the minimum size of the tap target.
///
/// Defaults to [ThemeData.materialTapTargetSize].
///
/// See also:
///
/// * [MaterialTapTargetSize], for a description of how this affects tap targets.
final MaterialTapTargetSize materialTapTargetSize;
/// The smallest horizontal extent that the button will occupy.
///
/// Defaults to the value from the current [ButtonTheme].
final double minWidth;
/// The vertical extent of the button.
///
/// Defaults to the value from the current [ButtonTheme].
final double height;
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
final ButtonThemeData buttonTheme = ButtonTheme.of(context);
return RawMaterialButton(
onPressed: onPressed,
fillColor: color,
textStyle: theme.textTheme.button.copyWith(color: buttonTheme.getTextColor(this)),
highlightColor: highlightColor ?? theme.highlightColor,
splashColor: splashColor ?? theme.splashColor,
elevation: buttonTheme.getElevation(this),
highlightElevation: buttonTheme.getHighlightElevation(this),
padding: buttonTheme.getPadding(this),
constraints: buttonTheme.constraints,
shape: buttonTheme.shape,
clipBehavior: clipBehavior ?? Clip.none,
animationDuration: buttonTheme.getAnimationDuration(this),
child: child,
materialTapTargetSize: materialTapTargetSize ?? theme.materialTapTargetSize,
);
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(FlagProperty('enabled', value: enabled, ifFalse: 'disabled'));
}
}
/// The type of [MaterialButton]s created with [RaisedButton.icon], [FlatButton.icon],
/// and [OutlineButton.icon].
///
/// This mixin only exists to give the "label and icon" button widgets a distinct
/// type for the sake of [ButtonTheme].
mixin MaterialButtonWithIconMixin { }
...@@ -7,6 +7,7 @@ import 'package:flutter/widgets.dart'; ...@@ -7,6 +7,7 @@ import 'package:flutter/widgets.dart';
import 'button_theme.dart'; import 'button_theme.dart';
import 'colors.dart'; import 'colors.dart';
import 'material_button.dart';
import 'raised_button.dart'; import 'raised_button.dart';
import 'theme.dart'; import 'theme.dart';
...@@ -48,31 +49,44 @@ const Duration _kElevationDuration = Duration(milliseconds: 75); ...@@ -48,31 +49,44 @@ const Duration _kElevationDuration = Duration(milliseconds: 75);
/// * [IconButton], to create buttons that just contain icons. /// * [IconButton], to create buttons that just contain icons.
/// * [InkWell], which implements the ink splash part of a flat button. /// * [InkWell], which implements the ink splash part of a flat button.
/// * <https://material.google.com/components/buttons.html> /// * <https://material.google.com/components/buttons.html>
class OutlineButton extends StatefulWidget { class OutlineButton extends MaterialButton {
/// Create a filled button. /// Create a filled button.
/// ///
/// The [highlightElevation], [borderWidth], and [clipBehavior] /// The [highlightElevation], [borderWidth], and [clipBehavior]
/// arguments must not be null. /// arguments must not be null.
const OutlineButton({ const OutlineButton({
Key key, Key key,
@required this.onPressed, @required VoidCallback onPressed,
this.textTheme, ButtonTextTheme textTheme,
this.textColor, Color textColor,
this.disabledTextColor, Color disabledTextColor,
this.color, Color color,
this.highlightColor, Color highlightColor,
this.splashColor, Color splashColor,
this.highlightElevation = 2.0, double highlightElevation,
this.borderSide, this.borderSide,
this.disabledBorderColor, this.disabledBorderColor,
this.highlightedBorderColor, this.highlightedBorderColor,
this.padding, EdgeInsetsGeometry padding,
this.shape, ShapeBorder shape,
this.clipBehavior = Clip.none, Clip clipBehavior = Clip.none,
this.child, Widget child,
}) : assert(highlightElevation != null && highlightElevation >= 0.0), }) : assert(highlightElevation == null || highlightElevation >= 0.0),
assert(clipBehavior != null), super(
super(key: key); key: key,
onPressed: onPressed,
textTheme: textTheme,
textColor: textColor,
disabledTextColor: disabledTextColor,
color: color,
highlightColor: highlightColor,
splashColor: splashColor,
highlightElevation: highlightElevation,
padding: padding,
shape: shape,
clipBehavior: clipBehavior,
child: child,
);
/// Create an outline button from a pair of widgets that serve as the button's /// Create an outline button from a pair of widgets that serve as the button's
/// [icon] and [label]. /// [icon] and [label].
...@@ -82,125 +96,25 @@ class OutlineButton extends StatefulWidget { ...@@ -82,125 +96,25 @@ class OutlineButton extends StatefulWidget {
/// ///
/// The [highlightElevation], [icon], [label], and [clipBehavior] must not be /// The [highlightElevation], [icon], [label], and [clipBehavior] must not be
/// null. /// null.
OutlineButton.icon({ factory OutlineButton.icon({
Key key, Key key,
@required this.onPressed, @required VoidCallback onPressed,
this.textTheme, ButtonTextTheme textTheme,
this.textColor, Color textColor,
this.disabledTextColor, Color disabledTextColor,
this.color, Color color,
this.highlightColor, Color highlightColor,
this.splashColor, Color splashColor,
this.highlightElevation = 2.0, double highlightElevation,
this.borderSide, Color highlightedBorderColor,
this.disabledBorderColor, Color disabledBorderColor,
this.highlightedBorderColor, BorderSide borderSide,
this.shape, EdgeInsetsGeometry padding,
this.clipBehavior = Clip.none, ShapeBorder shape,
Clip clipBehavior,
@required Widget icon, @required Widget icon,
@required Widget label, @required Widget label,
}) : assert(highlightElevation != null && highlightElevation >= 0.0), }) = _OutlineButtonWithIcon;
assert(icon != null),
assert(label != null),
assert(clipBehavior != null),
padding = const EdgeInsetsDirectional.only(start: 12.0, end: 16.0),
child = Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
icon,
const SizedBox(width: 8.0),
label,
],
),
super(key: key);
/// Called when the button is tapped or otherwise activated.
///
/// If this is set to null, the button will be disabled, see [enabled].
final VoidCallback onPressed;
/// Defines the button's base colors, and the defaults for the button's minimum
/// size, internal padding, and shape.
///
/// Defaults to `ButtonTheme.of(context).textTheme`.
final ButtonTextTheme textTheme;
/// The color to use for this button's text.
///
/// The button's [Material.textStyle] will be the current theme's button
/// text style, [ThemeData.textTheme.button], configured with this color.
///
/// The default text color depends on the button theme's text theme,
/// [ButtonThemeData.textTheme].
///
/// See also:
///
/// * [disabledTextColor], the text color to use when the button has been
/// disabled.
final Color textColor;
/// The color to use for this button's text when the button is disabled.
///
/// The button's [Material.textStyle] will be the current theme's button
/// text style, [ThemeData.textTheme.button], configured with this color.
///
/// The default value is the theme's disabled color,
/// [ThemeData.disabledColor].
///
/// See also:
///
/// * [textColor], which specifies the color to use for this button's text
/// when the button is [enabled].
final Color disabledTextColor;
/// The button's opaque fill color when it's [enabled] and has been pressed.
///
/// If null this value defaults to white for light themes (see
/// [ThemeData.brightness]), and black for dark themes.
final Color color;
/// The splash color of the button's [InkWell].
///
/// The ink splash indicates that the button has been touched. It
/// appears on top of the button's child and spreads in an expanding
/// circle beginning where the touch occurred.
///
/// If [textTheme] is [ButtonTextTheme.primary], the default splash color is
/// is based on the theme's primary color [ThemeData.primaryColor],
/// otherwise it's the current theme's splash color, [ThemeData.splashColor].
///
/// The appearance of the splash can be configured with the theme's splash
/// factory, [ThemeData.splashFactory].
final Color splashColor;
/// The highlight color of the button's [InkWell].
///
/// The highlight indicates that the button is actively being pressed. It
/// appears on top of the button's child and quickly spreads to fill
/// the button, and then fades out.
///
/// If [textTheme] is [ButtonTextTheme.primary], the default highlight color is
/// transparent (in other words the highlight doesn't appear). Otherwise it's
/// the current theme's highlight color, [ThemeData.highlightColor].
final Color highlightColor;
/// The elevation of the button when it's [enabled] and has been pressed.
///
/// If null, this value defaults to 2.0.
///
/// The elevation of an outline button is always 0.0 unless its enabled
/// and has been pressed.
final double highlightElevation;
/// Defines the color of the border when the button is enabled but not
/// pressed, and the border outline's width and style in general.
///
/// If the border side's [BorderSide.style] is [BorderStyle.none], then
/// an outline is not drawn.
///
/// 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.
final BorderSide borderSide;
/// The outline border's color when the button is [enabled] and pressed. /// The outline border's color when the button is [enabled] and pressed.
/// ///
...@@ -215,36 +129,38 @@ class OutlineButton extends StatefulWidget { ...@@ -215,36 +129,38 @@ class OutlineButton extends StatefulWidget {
/// dark themes. /// dark themes.
final Color disabledBorderColor; final Color disabledBorderColor;
/// The internal padding for the button's [child]. /// Defines the color of the border when the button is enabled but not
/// /// pressed, and the border outline's width and style in general.
/// Defaults to the value from the current [ButtonTheme],
/// [ButtonThemeData.padding].
final EdgeInsetsGeometry padding;
/// The shape of the button's [Material] and its outline.
///
/// The button's highlight and splash are clipped to this shape. If the
/// button has a [highlightElevation], then its drop shadow is defined by this
/// shape as well.
final ShapeBorder shape;
/// {@macro flutter.widgets.Clip}
final Clip clipBehavior;
/// The button's label.
/// ///
/// Often a [Text] widget in all caps. /// If the border side's [BorderSide.style] is [BorderStyle.none], then
final Widget child; /// an outline is not drawn.
/// Whether the button is enabled or disabled.
/// ///
/// Buttons are disabled by default. To enable a button, set its [onPressed] /// If null the default border's style is [BorderStyle.solid], its
/// property to a non-null value. /// [BorderSide.width] is 2.0, and its color is a light shade of grey.
bool get enabled => onPressed != null; final BorderSide borderSide;
@override @override
_OutlineButtonState createState() => _OutlineButtonState(); Widget build(BuildContext context) {
final ButtonThemeData buttonTheme = ButtonTheme.of(context);
return _OutlineButton(
onPressed: onPressed,
brightness: buttonTheme.getBrightness(this),
textTheme: textTheme,
textColor: buttonTheme.getTextColor(this),
disabledTextColor: buttonTheme.getDisabledTextColor(this),
color: color,
highlightColor: buttonTheme.getHighlightColor(this),
splashColor: buttonTheme.getSplashColor(this),
highlightElevation: buttonTheme.getHighlightElevation(this),
borderSide: borderSide,
disabledBorderColor: disabledBorderColor,
highlightedBorderColor: highlightedBorderColor ?? buttonTheme.colorScheme.primary,
padding: buttonTheme.getPadding(this),
shape: buttonTheme.getShape(this),
clipBehavior: clipBehavior,
child: child,
);
}
@override @override
void debugFillProperties(DiagnosticPropertiesBuilder properties) { void debugFillProperties(DiagnosticPropertiesBuilder properties) {
...@@ -256,7 +172,7 @@ class OutlineButton extends StatefulWidget { ...@@ -256,7 +172,7 @@ class OutlineButton extends StatefulWidget {
properties.add(DiagnosticsProperty<Color>('color', color, defaultValue: null)); properties.add(DiagnosticsProperty<Color>('color', color, defaultValue: null));
properties.add(DiagnosticsProperty<Color>('highlightColor', highlightColor, defaultValue: null)); properties.add(DiagnosticsProperty<Color>('highlightColor', highlightColor, defaultValue: null));
properties.add(DiagnosticsProperty<Color>('splashColor', splashColor, defaultValue: null)); properties.add(DiagnosticsProperty<Color>('splashColor', splashColor, defaultValue: null));
properties.add(DiagnosticsProperty<double>('highlightElevation', highlightElevation, defaultValue: 2.0)); properties.add(DiagnosticsProperty<double>('highlightElevation', highlightElevation, defaultValue: null));
properties.add(DiagnosticsProperty<BorderSide>('borderSide', borderSide, defaultValue: null)); properties.add(DiagnosticsProperty<BorderSide>('borderSide', borderSide, defaultValue: null));
properties.add(DiagnosticsProperty<Color>('disabledBorderColor', disabledBorderColor, defaultValue: null)); properties.add(DiagnosticsProperty<Color>('disabledBorderColor', disabledBorderColor, defaultValue: null));
properties.add(DiagnosticsProperty<Color>('highlightedBorderColor', highlightedBorderColor, defaultValue: null)); properties.add(DiagnosticsProperty<Color>('highlightedBorderColor', highlightedBorderColor, defaultValue: null));
...@@ -265,7 +181,107 @@ class OutlineButton extends StatefulWidget { ...@@ -265,7 +181,107 @@ class OutlineButton extends StatefulWidget {
} }
} }
class _OutlineButtonState extends State<OutlineButton> with SingleTickerProviderStateMixin { // The type of of OutlineButtons created with [OutlineButton.icon].
//
// This class only exists to give RaisedButtons created with [RaisedButton.icon]
// a distinct class for the sake of [ButtonTheme]. It can not be instantiated.
class _OutlineButtonWithIcon extends OutlineButton with MaterialButtonWithIconMixin {
_OutlineButtonWithIcon({
Key key,
@required VoidCallback onPressed,
ButtonTextTheme textTheme,
Color textColor,
Color disabledTextColor,
Color color,
Color highlightColor,
Color splashColor,
double highlightElevation,
Color highlightedBorderColor,
Color disabledBorderColor,
BorderSide borderSide,
EdgeInsetsGeometry padding,
ShapeBorder shape,
Clip clipBehavior,
@required Widget icon,
@required Widget label,
}) : assert(highlightElevation == null || highlightElevation >= 0.0),
assert(icon != null),
assert(label != null),
super(
key: key,
onPressed: onPressed,
textTheme: textTheme,
textColor: textColor,
disabledTextColor: disabledTextColor,
color: color,
highlightColor: highlightColor,
splashColor: splashColor,
highlightElevation: highlightElevation,
disabledBorderColor: disabledBorderColor,
highlightedBorderColor: highlightedBorderColor,
borderSide: borderSide,
padding: padding,
shape: shape,
clipBehavior: clipBehavior,
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
icon,
const SizedBox(width: 8.0),
label,
],
),
);
}
class _OutlineButton extends StatefulWidget {
const _OutlineButton({
Key key,
@required this.onPressed,
this.brightness,
this.textTheme,
this.textColor,
this.disabledTextColor,
this.color,
this.highlightColor,
this.splashColor,
@required this.highlightElevation,
this.borderSide,
this.disabledBorderColor,
@required this.highlightedBorderColor,
this.padding,
this.shape,
this.clipBehavior,
this.child,
}) : assert(highlightElevation != null && highlightElevation >= 0.0),
assert(highlightedBorderColor != null),
super(key: key);
final VoidCallback onPressed;
final Brightness brightness;
final ButtonTextTheme textTheme;
final Color textColor;
final Color disabledTextColor;
final Color color;
final Color splashColor;
final Color highlightColor;
final double highlightElevation;
final BorderSide borderSide;
final Color disabledBorderColor;
final Color highlightedBorderColor;
final EdgeInsetsGeometry padding;
final ShapeBorder shape;
final Clip clipBehavior;
final Widget child;
bool get enabled => onPressed != null;
@override
_OutlineButtonState createState() => _OutlineButtonState();
}
class _OutlineButtonState extends State<_OutlineButton> with SingleTickerProviderStateMixin {
AnimationController _controller; AnimationController _controller;
Animation<double> _fillAnimation; Animation<double> _fillAnimation;
Animation<double> _elevationAnimation; Animation<double> _elevationAnimation;
...@@ -305,36 +321,8 @@ class _OutlineButtonState extends State<OutlineButton> with SingleTickerProvider ...@@ -305,36 +321,8 @@ class _OutlineButtonState extends State<OutlineButton> with SingleTickerProvider
super.dispose(); super.dispose();
} }
ButtonTextTheme _getTextTheme(ButtonThemeData buttonTheme) { Color _getFillColor() {
return widget.textTheme ?? buttonTheme.textTheme; final bool themeIsDark = widget.brightness == Brightness.dark;
}
// TODO(hmuller): this method is the same as FlatButton
Color _getTextColor(ThemeData theme, ButtonThemeData buttonTheme) {
final Color color = widget.enabled ? widget.textColor : widget.disabledTextColor;
if (color != null)
return color;
final bool themeIsDark = theme.brightness == Brightness.dark;
switch (_getTextTheme(buttonTheme)) {
case ButtonTextTheme.normal:
return widget.enabled
? (themeIsDark ? Colors.white : Colors.black87)
: theme.disabledColor;
case ButtonTextTheme.accent:
return widget.enabled
? theme.accentColor
: theme.disabledColor;
case ButtonTextTheme.primary:
return widget.enabled
? theme.buttonColor
: (themeIsDark ? Colors.white30 : Colors.black38);
}
return null;
}
Color _getFillColor(ThemeData theme) {
final bool themeIsDark = theme.brightness == Brightness.dark;
final Color color = widget.color ?? (themeIsDark final Color color = widget.color ?? (themeIsDark
? const Color(0x00000000) ? const Color(0x00000000)
: const Color(0x00FFFFFF)); : const Color(0x00FFFFFF));
...@@ -345,35 +333,18 @@ class _OutlineButtonState extends State<OutlineButton> with SingleTickerProvider ...@@ -345,35 +333,18 @@ class _OutlineButtonState extends State<OutlineButton> with SingleTickerProvider
return colorTween.evaluate(_fillAnimation); return colorTween.evaluate(_fillAnimation);
} }
// TODO(hmuller): this method is the same as FlatButton BorderSide _getOutline() {
Color _getSplashColor(ThemeData theme, ButtonThemeData buttonTheme) { final bool isDark = widget.brightness == Brightness.dark;
if (widget.splashColor != null)
return widget.splashColor;
switch (_getTextTheme(buttonTheme)) {
case ButtonTextTheme.normal:
case ButtonTextTheme.accent:
return theme.splashColor;
case ButtonTextTheme.primary:
return theme.brightness == Brightness.dark
? Colors.white12
: theme.primaryColor.withOpacity(0.12);
}
return Colors.transparent;
}
BorderSide _getOutline(ThemeData theme, ButtonThemeData buttonTheme) {
final bool themeIsDark = theme.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 color = widget.enabled
? (_pressed ? (_pressed
? widget.highlightedBorderColor ?? theme.primaryColor ? widget.highlightedBorderColor
: (widget.borderSide?.color ?? : (widget.borderSide?.color ??
(themeIsDark ? Colors.grey[600] : Colors.grey[200]))) (isDark ? Colors.grey[600] : Colors.grey[200])))
: (widget.disabledBorderColor ?? : (widget.disabledBorderColor ??
(themeIsDark ? Colors.grey[800] : Colors.grey[100])); (isDark ? Colors.grey[800] : Colors.grey[100]));
return BorderSide( return BorderSide(
color: color, color: color,
...@@ -390,19 +361,14 @@ class _OutlineButtonState extends State<OutlineButton> with SingleTickerProvider ...@@ -390,19 +361,14 @@ class _OutlineButtonState extends State<OutlineButton> with SingleTickerProvider
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
final ButtonThemeData buttonTheme = ButtonTheme.of(context);
final Color textColor = _getTextColor(theme, buttonTheme);
final Color splashColor = _getSplashColor(theme, buttonTheme);
return AnimatedBuilder( return AnimatedBuilder(
animation: _controller, animation: _controller,
builder: (BuildContext context, Widget child) { builder: (BuildContext context, Widget child) {
return RaisedButton( return RaisedButton(
textColor: textColor, textColor: widget.textColor,
disabledTextColor: widget.disabledTextColor, disabledTextColor: widget.disabledTextColor,
color: _getFillColor(theme), color: _getFillColor(),
splashColor: splashColor, splashColor: widget.splashColor,
highlightColor: widget.highlightColor, highlightColor: widget.highlightColor,
disabledColor: Colors.transparent, disabledColor: Colors.transparent,
onPressed: widget.onPressed, onPressed: widget.onPressed,
...@@ -420,8 +386,8 @@ class _OutlineButtonState extends State<OutlineButton> with SingleTickerProvider ...@@ -420,8 +386,8 @@ class _OutlineButtonState extends State<OutlineButton> with SingleTickerProvider
}, },
padding: widget.padding, padding: widget.padding,
shape: _OutlineBorder( shape: _OutlineBorder(
shape: widget.shape ?? buttonTheme.shape, shape: widget.shape,
side: _getOutline(theme, buttonTheme), side: _getOutline(),
), ),
clipBehavior: widget.clipBehavior, clipBehavior: widget.clipBehavior,
animationDuration: _kElevationDuration, animationDuration: _kElevationDuration,
......
...@@ -7,8 +7,7 @@ import 'package:flutter/widgets.dart'; ...@@ -7,8 +7,7 @@ import 'package:flutter/widgets.dart';
import 'button.dart'; import 'button.dart';
import 'button_theme.dart'; import 'button_theme.dart';
import 'colors.dart'; import 'material_button.dart';
import 'constants.dart';
import 'theme.dart'; import 'theme.dart';
import 'theme_data.dart'; import 'theme_data.dart';
...@@ -41,38 +40,57 @@ import 'theme_data.dart'; ...@@ -41,38 +40,57 @@ import 'theme_data.dart';
/// * [InkWell], which implements the ink splash part of a flat button. /// * [InkWell], which implements the ink splash part of a flat button.
/// * [RawMaterialButton], the widget this widget is based on. /// * [RawMaterialButton], the widget this widget is based on.
/// * <https://material.google.com/components/buttons.html> /// * <https://material.google.com/components/buttons.html>
class RaisedButton extends StatelessWidget { class RaisedButton extends MaterialButton {
/// Create a filled button. /// Create a filled button.
/// ///
/// The [elevation], [highlightElevation], [disabledElevation], and /// The [elevation], [highlightElevation], [disabledElevation], and
/// [clipBehavior] arguments must not be null. /// [clipBehavior] arguments must not be null.
const RaisedButton({ const RaisedButton({
Key key, Key key,
@required this.onPressed, @required VoidCallback onPressed,
this.onHighlightChanged, ValueChanged<bool> onHighlightChanged,
this.textTheme, ButtonTextTheme textTheme,
this.textColor, Color textColor,
this.disabledTextColor, Color disabledTextColor,
this.color, Color color,
this.disabledColor, Color disabledColor,
this.highlightColor, Color highlightColor,
this.splashColor, Color splashColor,
this.colorBrightness, Brightness colorBrightness,
this.elevation = 2.0, double elevation,
this.highlightElevation = 8.0, double highlightElevation,
this.disabledElevation = 0.0, double disabledElevation,
this.padding, EdgeInsetsGeometry padding,
this.shape, ShapeBorder shape,
this.clipBehavior = Clip.none, Clip clipBehavior = Clip.none,
this.materialTapTargetSize, MaterialTapTargetSize materialTapTargetSize,
this.animationDuration = kThemeChangeDuration, Duration animationDuration,
this.child, Widget child,
}) : assert(elevation != null), }) : assert(elevation == null || elevation >= 0.0),
assert(highlightElevation != null), assert(highlightElevation == null || highlightElevation >= 0.0),
assert(disabledElevation != null), assert(disabledElevation == null || disabledElevation >= 0.0),
assert(animationDuration != null), super(
assert(clipBehavior != null), key: key,
super(key: key); onPressed: onPressed,
onHighlightChanged: onHighlightChanged,
textTheme: textTheme,
textColor: textColor,
disabledTextColor: disabledTextColor,
color: color,
disabledColor: disabledColor,
highlightColor: highlightColor,
splashColor: splashColor,
colorBrightness: colorBrightness,
elevation: elevation,
highlightElevation: highlightElevation,
disabledElevation: disabledElevation,
padding: padding,
shape: shape,
clipBehavior: clipBehavior,
materialTapTargetSize: materialTapTargetSize,
animationDuration: animationDuration,
child: child,
);
/// Create a filled button from a pair of widgets that serve as the button's /// Create a filled button from a pair of widgets that serve as the button's
/// [icon] and [label]. /// [icon] and [label].
...@@ -82,325 +100,51 @@ class RaisedButton extends StatelessWidget { ...@@ -82,325 +100,51 @@ class RaisedButton extends StatelessWidget {
/// ///
/// The [elevation], [highlightElevation], [disabledElevation], [icon], /// The [elevation], [highlightElevation], [disabledElevation], [icon],
/// [label], and [clipBehavior] arguments must not be null. /// [label], and [clipBehavior] arguments must not be null.
RaisedButton.icon({ factory RaisedButton.icon({
Key key, Key key,
@required this.onPressed, @required VoidCallback onPressed,
this.onHighlightChanged, ValueChanged<bool> onHighlightChanged,
this.textTheme, ButtonTextTheme textTheme,
this.textColor, Color textColor,
this.disabledTextColor, Color disabledTextColor,
this.color, Color color,
this.disabledColor, Color disabledColor,
this.highlightColor, Color highlightColor,
this.splashColor, Color splashColor,
this.colorBrightness, Brightness colorBrightness,
this.elevation = 2.0, double elevation,
this.highlightElevation = 8.0, double highlightElevation,
this.disabledElevation = 0.0, double disabledElevation,
this.shape, ShapeBorder shape,
this.clipBehavior = Clip.none, Clip clipBehavior,
this.materialTapTargetSize, MaterialTapTargetSize materialTapTargetSize,
this.animationDuration = kThemeChangeDuration, Duration animationDuration,
@required Widget icon, @required Widget icon,
@required Widget label, @required Widget label,
}) : assert(elevation != null), }) = _RaisedButtonWithIcon;
assert(highlightElevation != null),
assert(disabledElevation != null),
assert(icon != null),
assert(label != null),
assert(animationDuration != null),
assert(clipBehavior != null),
padding = const EdgeInsetsDirectional.only(start: 12.0, end: 16.0),
child = Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
icon,
const SizedBox(width: 8.0),
label,
],
),
super(key: key);
/// Called when the button is tapped or otherwise activated.
///
/// If this is set to null, the button will be disabled, see [enabled].
final VoidCallback onPressed;
/// Called by the underlying [InkWell] widget's [InkWell.onHighlightChanged]
/// callback.
final ValueChanged<bool> onHighlightChanged;
/// Defines the button's base colors, and the defaults for the button's minimum
/// size, internal padding, and shape.
///
/// Defaults to `ButtonTheme.of(context).textTheme`.
final ButtonTextTheme textTheme;
/// The color to use for this button's text.
///
/// The button's [Material.textStyle] will be the current theme's button
/// text style, [ThemeData.textTheme.button], configured with this color.
///
/// The default text color depends on the button theme's text theme,
/// [ButtonThemeData.textTheme].
///
/// See also:
/// * [disabledTextColor], the text color to use when the button has been
/// disabled.
final Color textColor;
/// The color to use for this button's text when the button is disabled.
///
/// The button's [Material.textStyle] will be the current theme's button
/// text style, [ThemeData.textTheme.button], configured with this color.
///
/// The default value is the theme's disabled color,
/// [ThemeData.disabledColor].
///
/// See also:
/// * [textColor] - The color to use for this button's text when the button is [enabled].
final Color disabledTextColor;
/// The button's fill color, displayed by its [Material], while it
/// is in its default (unpressed, [enabled]) state.
///
/// The default fill color is the theme's button color, [ThemeData.buttonColor].
///
/// Typically the default color will be overidden with a Material color,
/// for example:
///
/// ```dart
/// RaisedButton(
/// color: Colors.blue,
/// onPressed: _handleTap,
/// child: Text('DEMO'),
/// ),
/// ```
///
/// See also:
/// * [disabledColor] - the fill color of the button when the button is disabled.
final Color color;
/// The fill color of the button when the button is disabled.
///
/// The default value of this color is the theme's disabled color,
/// [ThemeData.disabledColor].
///
/// See also:
/// * [color] - the fill color of the button when the button is [enabled].
final Color disabledColor;
/// The splash color of the button's [InkWell].
///
/// The ink splash indicates that the button has been touched. It
/// appears on top of the button's child and spreads in an expanding
/// circle beginning where the touch occurred.
///
/// The default splash color is the current theme's splash color,
/// [ThemeData.splashColor].
///
/// The appearance of the splash can be configured with the theme's splash
/// factory, [ThemeData.splashFactory].
final Color splashColor;
/// The highlight color of the button's [InkWell].
///
/// The highlight indicates that the button is actively being pressed. It
/// appears on top of the button's child and quickly spreads to fill
/// the button, and then fades out.
///
/// If [textTheme] is [ButtonTextTheme.primary], the default highlight color is
/// transparent (in other words the highlight doesn't appear). Otherwise it's
/// the current theme's highlight color, [ThemeData.highlightColor].
final Color highlightColor;
/// The z-coordinate at which to place this button. This controls the size of
/// the shadow below the raised button.
///
/// Defaults to 2, the appropriate elevation for raised buttons.
///
/// See also:
///
/// * [FlatButton], a button with no elevation or fill color.
/// * [disabledElevation], the elevation when the button is disabled.
/// * [highlightElevation], the elevation when the button is pressed.
final double elevation;
/// The elevation for the button's [Material] when the button
/// is [enabled] but not pressed.
///
/// Defaults to 2.0.
///
/// See also:
///
/// * [highlightElevation], the default elevation.
/// * [disabledElevation], the elevation when the button is disabled.
/// The elevation for the button's [Material] when the button @override
/// is [enabled] and pressed.
///
/// This controls the size of the shadow below the button. When a tap
/// down gesture occurs within the button, its [InkWell] displays a
/// [highlightColor] "highlight".
///
/// Defaults to 8.0.
///
/// See also:
///
/// * [elevation], the default elevation.
/// * [disabledElevation], the elevation when the button is disabled.
final double highlightElevation;
/// The elevation for the button's [Material] when the button
/// is not [enabled].
///
/// Defaults to 0.0.
///
/// See also:
///
/// * [elevation], the default elevation.
/// * [highlightElevation], the elevation when the button is pressed.
final double disabledElevation;
/// The theme brightness to use for this button.
///
/// Defaults to the theme's brightness, [ThemeData.brightness].
final Brightness colorBrightness;
/// The button's label.
///
/// Often a [Text] widget in all caps.
final Widget child;
/// Whether the button is enabled or disabled.
///
/// Buttons are disabled by default. To enable a button, set its [onPressed]
/// property to a non-null value.
bool get enabled => onPressed != null;
/// The internal padding for the button's [child].
///
/// Defaults to the value from the current [ButtonTheme],
/// [ButtonThemeData.padding].
final EdgeInsetsGeometry padding;
/// The shape of the button's [Material].
///
/// 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 as well.
final ShapeBorder shape;
/// {@macro flutter.widgets.Clip}
final Clip clipBehavior;
/// Defines the duration of animated changes for [shape] and [elevation].
///
/// The default value is [kThemeChangeDuration].
final Duration animationDuration;
/// Configures the minimum size of the tap target.
///
/// Defaults to [ThemeData.materialTapTargetSize].
///
/// See also:
///
/// * [MaterialTapTargetSize], for a description of how this affects tap targets.
final MaterialTapTargetSize materialTapTargetSize;
Brightness _getBrightness(ThemeData theme) {
return colorBrightness ?? theme.brightness;
}
ButtonTextTheme _getTextTheme(ButtonThemeData buttonTheme) {
return textTheme ?? buttonTheme.textTheme;
}
Color _getFillColor(ThemeData theme, ButtonThemeData buttonTheme) {
final Color fillColor = enabled ? color : disabledColor;
if (fillColor != null)
return fillColor;
final bool themeIsDark = _getBrightness(theme) == Brightness.dark;
switch (_getTextTheme(buttonTheme)) {
case ButtonTextTheme.normal:
case ButtonTextTheme.accent:
return enabled
? theme.buttonColor
: theme.disabledColor;
case ButtonTextTheme.primary:
return enabled
? theme.buttonColor
: (themeIsDark ? Colors.white12 : Colors.black12);
}
return null;
}
Color _getTextColor(ThemeData theme, ButtonThemeData buttonTheme, Color fillColor) {
final Color color = enabled ? textColor : disabledTextColor;
if (color != null)
return color;
final bool themeIsDark = _getBrightness(theme) == Brightness.dark;
final bool fillIsDark = fillColor != null
? ThemeData.estimateBrightnessForColor(fillColor) == Brightness.dark
: themeIsDark;
switch (_getTextTheme(buttonTheme)) {
case ButtonTextTheme.normal:
return enabled
? (themeIsDark ? Colors.white : Colors.black87)
: theme.disabledColor;
case ButtonTextTheme.accent:
return enabled
? theme.accentColor
: theme.disabledColor;
case ButtonTextTheme.primary:
return enabled
? (fillIsDark ? Colors.white : Colors.black)
: (themeIsDark ? Colors.white30 : Colors.black38);
}
return null;
}
Color _getHighlightColor(ThemeData theme, ButtonThemeData buttonTheme) {
if (highlightColor != null)
return highlightColor;
switch (_getTextTheme(buttonTheme)) {
case ButtonTextTheme.normal:
case ButtonTextTheme.accent:
return theme.highlightColor;
case ButtonTextTheme.primary:
return Colors.transparent;
}
return Colors.transparent;
}
@override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context); final ThemeData theme = Theme.of(context);
final ButtonThemeData buttonTheme = ButtonTheme.of(context); final ButtonThemeData buttonTheme = ButtonTheme.of(context);
final Color fillColor = _getFillColor(theme, buttonTheme);
final Color textColor = _getTextColor(theme, buttonTheme, fillColor);
return RawMaterialButton( return RawMaterialButton(
onPressed: onPressed, onPressed: onPressed,
onHighlightChanged: onHighlightChanged, onHighlightChanged: onHighlightChanged,
fillColor: fillColor, clipBehavior: clipBehavior ?? Clip.none,
textStyle: theme.textTheme.button.copyWith(color: textColor), fillColor: buttonTheme.getFillColor(this),
highlightColor: _getHighlightColor(theme, buttonTheme), textStyle: theme.textTheme.button.copyWith(color: buttonTheme.getTextColor(this)),
splashColor: splashColor ?? theme.splashColor, highlightColor: buttonTheme.getHighlightColor(this),
elevation: elevation, splashColor: buttonTheme.getSplashColor(this),
highlightElevation: highlightElevation, elevation: buttonTheme.getElevation(this),
disabledElevation: disabledElevation, highlightElevation: buttonTheme.getHighlightElevation(this),
padding: padding ?? buttonTheme.padding, disabledElevation: buttonTheme.getDisabledElevation(this),
constraints: buttonTheme.constraints, padding: buttonTheme.getPadding(this),
shape: shape ?? buttonTheme.shape, constraints: buttonTheme.getConstraints(this),
clipBehavior: clipBehavior, shape: buttonTheme.getShape(this),
animationDuration: animationDuration, animationDuration: buttonTheme.getAnimationDuration(this),
materialTapTargetSize: buttonTheme.getMaterialTapTargetSize(this),
child: child, child: child,
materialTapTargetSize: materialTapTargetSize ?? theme.materialTapTargetSize,
); );
} }
...@@ -422,3 +166,64 @@ class RaisedButton extends StatelessWidget { ...@@ -422,3 +166,64 @@ class RaisedButton extends StatelessWidget {
properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null)); properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
} }
} }
/// The type of of RaisedButtons created with [RaisedButton.icon].
///
/// This class only exists to give RaisedButtons created with [RaisedButton.icon]
/// a distinct class for the sake of [ButtonTheme]. It can not be instantiated.
class _RaisedButtonWithIcon extends RaisedButton with MaterialButtonWithIconMixin {
_RaisedButtonWithIcon({
Key key,
@required VoidCallback onPressed,
ValueChanged<bool> onHighlightChanged,
ButtonTextTheme textTheme,
Color textColor,
Color disabledTextColor,
Color color,
Color disabledColor,
Color highlightColor,
Color splashColor,
Brightness colorBrightness,
double elevation,
double highlightElevation,
double disabledElevation,
ShapeBorder shape,
Clip clipBehavior = Clip.none,
MaterialTapTargetSize materialTapTargetSize,
Duration animationDuration,
@required Widget icon,
@required Widget label,
}) : assert(elevation == null || elevation >= 0.0),
assert(highlightElevation == null || highlightElevation >= 0.0),
assert(disabledElevation == null || disabledElevation >= 0.0),
assert(icon != null),
assert(label != null),
super(
key: key,
onPressed: onPressed,
onHighlightChanged: onHighlightChanged,
textTheme: textTheme,
textColor: textColor,
disabledTextColor: disabledTextColor,
color: color,
disabledColor: disabledColor,
highlightColor: highlightColor,
splashColor: splashColor,
colorBrightness: colorBrightness,
elevation: elevation,
highlightElevation: highlightElevation,
disabledElevation: disabledElevation,
shape: shape,
clipBehavior: clipBehavior,
materialTapTargetSize: materialTapTargetSize,
animationDuration: animationDuration,
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
icon,
const SizedBox(width: 8.0),
label,
],
),
);
}
...@@ -10,6 +10,7 @@ import 'package:flutter/widgets.dart'; ...@@ -10,6 +10,7 @@ import 'package:flutter/widgets.dart';
import 'button_theme.dart'; import 'button_theme.dart';
import 'chip_theme.dart'; import 'chip_theme.dart';
import 'color_scheme.dart';
import 'colors.dart'; import 'colors.dart';
import 'ink_splash.dart'; import 'ink_splash.dart';
import 'ink_well.dart' show InteractiveInkFeatureFactory; import 'ink_well.dart' show InteractiveInkFeatureFactory;
...@@ -147,8 +148,8 @@ class ThemeData extends Diagnosticable { ...@@ -147,8 +148,8 @@ class ThemeData extends Diagnosticable {
TargetPlatform platform, TargetPlatform platform,
MaterialTapTargetSize materialTapTargetSize, MaterialTapTargetSize materialTapTargetSize,
PageTransitionsTheme pageTransitionsTheme, PageTransitionsTheme pageTransitionsTheme,
ColorScheme colorScheme,
}) { }) {
materialTapTargetSize ??= MaterialTapTargetSize.padded;
brightness ??= Brightness.light; brightness ??= Brightness.light;
final bool isDark = brightness == Brightness.dark; final bool isDark = brightness == Brightness.dark;
primarySwatch ??= Colors.blue; primarySwatch ??= Colors.blue;
...@@ -166,14 +167,22 @@ class ThemeData extends Diagnosticable { ...@@ -166,14 +167,22 @@ class ThemeData extends Diagnosticable {
bottomAppBarColor ??= isDark ? Colors.grey[800] : Colors.white; bottomAppBarColor ??= isDark ? Colors.grey[800] : Colors.white;
cardColor ??= isDark ? Colors.grey[800] : Colors.white; cardColor ??= isDark ? Colors.grey[800] : Colors.white;
dividerColor ??= isDark ? const Color(0x1FFFFFFF) : const Color(0x1F000000); dividerColor ??= isDark ? const Color(0x1FFFFFFF) : const Color(0x1F000000);
highlightColor ??= isDark ? _kDarkThemeHighlightColor : _kLightThemeHighlightColor;
splashColor ??= isDark ? _kDarkThemeSplashColor : _kLightThemeSplashColor; // Create a ColorScheme that is backwards compatible as possible
// with the existing default ThemeData color values.
colorScheme ??= ColorScheme.fromSwatch(
primarySwatch: primarySwatch,
primaryColorDark: primaryColorDark,
accentColor: accentColor,
cardColor: cardColor,
backgroundColor: backgroundColor,
errorColor: errorColor,
brightness: brightness,
);
splashFactory ??= InkSplash.splashFactory; splashFactory ??= InkSplash.splashFactory;
selectedRowColor ??= Colors.grey[100]; selectedRowColor ??= Colors.grey[100];
unselectedWidgetColor ??= isDark ? Colors.white70 : Colors.black54; unselectedWidgetColor ??= isDark ? Colors.white70 : Colors.black54;
disabledColor ??= isDark ? Colors.white30 : Colors.black38;
buttonColor ??= isDark ? primarySwatch[600] : Colors.grey[300];
buttonTheme ??= const ButtonThemeData();
// Spec doesn't specify a dark theme secondaryHeaderColor, this is a guess. // Spec doesn't specify a dark theme secondaryHeaderColor, this is a guess.
secondaryHeaderColor ??= isDark ? Colors.grey[700] : primarySwatch[50]; secondaryHeaderColor ??= isDark ? Colors.grey[700] : primarySwatch[50];
textSelectionColor ??= isDark ? accentColor : primarySwatch[200]; textSelectionColor ??= isDark ? accentColor : primarySwatch[200];
...@@ -198,11 +207,28 @@ class ThemeData extends Diagnosticable { ...@@ -198,11 +207,28 @@ class ThemeData extends Diagnosticable {
primaryTextTheme = defaultPrimaryTextTheme.merge(primaryTextTheme); primaryTextTheme = defaultPrimaryTextTheme.merge(primaryTextTheme);
final TextTheme defaultAccentTextTheme = accentIsDark ? typography.white : typography.black; final TextTheme defaultAccentTextTheme = accentIsDark ? typography.white : typography.black;
accentTextTheme = defaultAccentTextTheme.merge(accentTextTheme); accentTextTheme = defaultAccentTextTheme.merge(accentTextTheme);
materialTapTargetSize ??= MaterialTapTargetSize.padded;
if (fontFamily != null) { if (fontFamily != null) {
textTheme = textTheme.apply(fontFamily: fontFamily); textTheme = textTheme.apply(fontFamily: fontFamily);
primaryTextTheme = primaryTextTheme.apply(fontFamily: fontFamily); primaryTextTheme = primaryTextTheme.apply(fontFamily: fontFamily);
accentTextTheme = accentTextTheme.apply(fontFamily: fontFamily); accentTextTheme = accentTextTheme.apply(fontFamily: fontFamily);
} }
// Used as the default color (fill color) for RaisedButtons. Computing the
// default for ButtonThemeData for the sake of backwards compatibility.
buttonColor ??= isDark ? primarySwatch[600] : Colors.grey[300];
buttonTheme ??= ButtonThemeData(
colorScheme: colorScheme,
buttonColor: buttonColor,
disabledColor: disabledColor,
highlightColor: highlightColor,
splashColor: splashColor,
materialTapTargetSize: materialTapTargetSize,
);
disabledColor ??= isDark ? Colors.white30 : Colors.black38;
highlightColor ??= isDark ? _kDarkThemeHighlightColor : _kLightThemeHighlightColor;
splashColor ??= isDark ? _kDarkThemeSplashColor : _kLightThemeSplashColor;
sliderTheme ??= SliderThemeData.fromPrimaryColors( sliderTheme ??= SliderThemeData.fromPrimaryColors(
primaryColor: primaryColor, primaryColor: primaryColor,
primaryColorLight: primaryColorLight, primaryColorLight: primaryColorLight,
...@@ -235,9 +261,9 @@ class ThemeData extends Diagnosticable { ...@@ -235,9 +261,9 @@ class ThemeData extends Diagnosticable {
selectedRowColor: selectedRowColor, selectedRowColor: selectedRowColor,
unselectedWidgetColor: unselectedWidgetColor, unselectedWidgetColor: unselectedWidgetColor,
disabledColor: disabledColor, disabledColor: disabledColor,
buttonTheme: buttonTheme,
buttonColor: buttonColor, buttonColor: buttonColor,
toggleableActiveColor: toggleableActiveColor, toggleableActiveColor: toggleableActiveColor,
buttonTheme: buttonTheme,
secondaryHeaderColor: secondaryHeaderColor, secondaryHeaderColor: secondaryHeaderColor,
textSelectionColor: textSelectionColor, textSelectionColor: textSelectionColor,
cursorColor: cursorColor, cursorColor: cursorColor,
...@@ -260,6 +286,7 @@ class ThemeData extends Diagnosticable { ...@@ -260,6 +286,7 @@ class ThemeData extends Diagnosticable {
platform: platform, platform: platform,
materialTapTargetSize: materialTapTargetSize, materialTapTargetSize: materialTapTargetSize,
pageTransitionsTheme: pageTransitionsTheme, pageTransitionsTheme: pageTransitionsTheme,
colorScheme: colorScheme,
); );
} }
...@@ -278,9 +305,9 @@ class ThemeData extends Diagnosticable { ...@@ -278,9 +305,9 @@ class ThemeData extends Diagnosticable {
@required this.primaryColorBrightness, @required this.primaryColorBrightness,
@required this.primaryColorLight, @required this.primaryColorLight,
@required this.primaryColorDark, @required this.primaryColorDark,
@required this.canvasColor,
@required this.accentColor, @required this.accentColor,
@required this.accentColorBrightness, @required this.accentColorBrightness,
@required this.canvasColor,
@required this.scaffoldBackgroundColor, @required this.scaffoldBackgroundColor,
@required this.bottomAppBarColor, @required this.bottomAppBarColor,
@required this.cardColor, @required this.cardColor,
...@@ -291,8 +318,8 @@ class ThemeData extends Diagnosticable { ...@@ -291,8 +318,8 @@ class ThemeData extends Diagnosticable {
@required this.selectedRowColor, @required this.selectedRowColor,
@required this.unselectedWidgetColor, @required this.unselectedWidgetColor,
@required this.disabledColor, @required this.disabledColor,
@required this.buttonColor,
@required this.buttonTheme, @required this.buttonTheme,
@required this.buttonColor,
@required this.secondaryHeaderColor, @required this.secondaryHeaderColor,
@required this.textSelectionColor, @required this.textSelectionColor,
@required this.cursorColor, @required this.cursorColor,
...@@ -316,6 +343,7 @@ class ThemeData extends Diagnosticable { ...@@ -316,6 +343,7 @@ class ThemeData extends Diagnosticable {
@required this.platform, @required this.platform,
@required this.materialTapTargetSize, @required this.materialTapTargetSize,
@required this.pageTransitionsTheme, @required this.pageTransitionsTheme,
@required this.colorScheme,
}) : assert(brightness != null), }) : assert(brightness != null),
assert(primaryColor != null), assert(primaryColor != null),
assert(primaryColorBrightness != null), assert(primaryColorBrightness != null),
...@@ -357,7 +385,12 @@ class ThemeData extends Diagnosticable { ...@@ -357,7 +385,12 @@ class ThemeData extends Diagnosticable {
assert(chipTheme != null), assert(chipTheme != null),
assert(platform != null), assert(platform != null),
assert(materialTapTargetSize != null), assert(materialTapTargetSize != null),
assert(pageTransitionsTheme != null); assert(pageTransitionsTheme != null),
assert(colorScheme != null);
// Warning: make sure these properties are in the exact same order as in
// hashValues() and in the raw constructor and in the order of fields in
// the class and in the lerp() method.
/// A default light blue theme. /// A default light blue theme.
/// ///
...@@ -407,6 +440,9 @@ class ThemeData extends Diagnosticable { ...@@ -407,6 +440,9 @@ class ThemeData extends Diagnosticable {
/// A darker version of the [primaryColor]. /// A darker version of the [primaryColor].
final Color primaryColorDark; final Color primaryColorDark;
/// The default color of [MaterialType.canvas] [Material].
final Color canvasColor;
/// The foreground color for widgets (knobs, text, overscroll edge effect, etc). /// The foreground color for widgets (knobs, text, overscroll edge effect, etc).
final Color accentColor; final Color accentColor;
...@@ -415,9 +451,6 @@ class ThemeData extends Diagnosticable { ...@@ -415,9 +451,6 @@ class ThemeData extends Diagnosticable {
/// action button). /// action button).
final Brightness accentColorBrightness; final Brightness accentColorBrightness;
/// The default color of [MaterialType.canvas] [Material].
final Color canvasColor;
/// The default color of the [Material] that underlies the [Scaffold]. The /// The default color of the [Material] that underlies the [Scaffold]. The
/// background color for a typical material app or a page within the app. /// background color for a typical material app or a page within the app.
final Color scaffoldBackgroundColor; final Color scaffoldBackgroundColor;
...@@ -467,17 +500,13 @@ class ThemeData extends Diagnosticable { ...@@ -467,17 +500,13 @@ class ThemeData extends Diagnosticable {
/// checked or unchecked). /// checked or unchecked).
final Color disabledColor; final Color disabledColor;
/// The default fill color of the [Material] used in [RaisedButton]s.
final Color buttonColor;
/// The color used to highlight the active states of toggleable widgets like
/// [Switch], [Radio], and [Checkbox].
final Color toggleableActiveColor;
/// Defines the default configuration of button widgets, like [RaisedButton] /// Defines the default configuration of button widgets, like [RaisedButton]
/// and [FlatButton]. /// and [FlatButton].
final ButtonThemeData buttonTheme; final ButtonThemeData buttonTheme;
/// The default fill color of the [Material] used in [RaisedButton]s.
final Color buttonColor;
/// The color of the header of a [PaginatedDataTable] when there are selected rows. /// The color of the header of a [PaginatedDataTable] when there are selected rows.
// According to the spec for data tables: // According to the spec for data tables:
// https://material.google.com/components/data-tables.html#data-tables-tables-within-cards // https://material.google.com/components/data-tables.html#data-tables-tables-within-cards
...@@ -510,6 +539,10 @@ class ThemeData extends Diagnosticable { ...@@ -510,6 +539,10 @@ class ThemeData extends Diagnosticable {
/// The color to use for input validation errors, e.g. in [TextField] fields. /// The color to use for input validation errors, e.g. in [TextField] fields.
final Color errorColor; final Color errorColor;
/// The color used to highlight the active states of toggleable widgets like
/// [Switch], [Radio], and [Checkbox].
final Color toggleableActiveColor;
/// Text with a color that contrasts with the card and canvas colors. /// Text with a color that contrasts with the card and canvas colors.
final TextTheme textTheme; final TextTheme textTheme;
...@@ -569,6 +602,16 @@ class ThemeData extends Diagnosticable { ...@@ -569,6 +602,16 @@ class ThemeData extends Diagnosticable {
/// builder is not found, a builder whose platform is null is used. /// builder is not found, a builder whose platform is null is used.
final PageTransitionsTheme pageTransitionsTheme; final PageTransitionsTheme pageTransitionsTheme;
/// A set of thirteen colors that can be used to configure the
/// color properties of most components.
///
/// This property was added much later than the theme's set of highly
/// specific colors, like [cardColor], [buttonColor], [canvasColor] etc.
/// New components can be defined exclusively in terms of [colorScheme].
/// Existing components will gradually migrate to it, to the extent
/// that is possible without significant backwards compatibility breaks.
final ColorScheme colorScheme;
/// Creates a copy of this theme but with the given fields replaced with the new values. /// Creates a copy of this theme but with the given fields replaced with the new values.
ThemeData copyWith({ ThemeData copyWith({
Brightness brightness, Brightness brightness,
...@@ -589,8 +632,8 @@ class ThemeData extends Diagnosticable { ...@@ -589,8 +632,8 @@ class ThemeData extends Diagnosticable {
Color selectedRowColor, Color selectedRowColor,
Color unselectedWidgetColor, Color unselectedWidgetColor,
Color disabledColor, Color disabledColor,
Color buttonColor,
ButtonThemeData buttonTheme, ButtonThemeData buttonTheme,
Color buttonColor,
Color secondaryHeaderColor, Color secondaryHeaderColor,
Color textSelectionColor, Color textSelectionColor,
Color cursorColor, Color cursorColor,
...@@ -614,6 +657,7 @@ class ThemeData extends Diagnosticable { ...@@ -614,6 +657,7 @@ class ThemeData extends Diagnosticable {
TargetPlatform platform, TargetPlatform platform,
MaterialTapTargetSize materialTapTargetSize, MaterialTapTargetSize materialTapTargetSize,
PageTransitionsTheme pageTransitionsTheme, PageTransitionsTheme pageTransitionsTheme,
ColorScheme colorScheme,
}) { }) {
return ThemeData.raw( return ThemeData.raw(
brightness: brightness ?? this.brightness, brightness: brightness ?? this.brightness,
...@@ -659,6 +703,7 @@ class ThemeData extends Diagnosticable { ...@@ -659,6 +703,7 @@ class ThemeData extends Diagnosticable {
platform: platform ?? this.platform, platform: platform ?? this.platform,
materialTapTargetSize: materialTapTargetSize ?? this.materialTapTargetSize, materialTapTargetSize: materialTapTargetSize ?? this.materialTapTargetSize,
pageTransitionsTheme: pageTransitionsTheme ?? this.pageTransitionsTheme, pageTransitionsTheme: pageTransitionsTheme ?? this.pageTransitionsTheme,
colorScheme: colorScheme ?? this.colorScheme,
); );
} }
...@@ -746,6 +791,9 @@ class ThemeData extends Diagnosticable { ...@@ -746,6 +791,9 @@ class ThemeData extends Diagnosticable {
assert(a != null); assert(a != null);
assert(b != null); assert(b != null);
assert(t != null); assert(t != null);
// Warning: make sure these properties are in the exact same order as in
// hashValues() and in the raw constructor and in the order of fields in
// the class and in the lerp() method.
return ThemeData.raw( return ThemeData.raw(
brightness: t < 0.5 ? a.brightness : b.brightness, brightness: t < 0.5 ? a.brightness : b.brightness,
primaryColor: Color.lerp(a.primaryColor, b.primaryColor, t), primaryColor: Color.lerp(a.primaryColor, b.primaryColor, t),
...@@ -765,9 +813,8 @@ class ThemeData extends Diagnosticable { ...@@ -765,9 +813,8 @@ class ThemeData extends Diagnosticable {
selectedRowColor: Color.lerp(a.selectedRowColor, b.selectedRowColor, t), selectedRowColor: Color.lerp(a.selectedRowColor, b.selectedRowColor, t),
unselectedWidgetColor: Color.lerp(a.unselectedWidgetColor, b.unselectedWidgetColor, t), unselectedWidgetColor: Color.lerp(a.unselectedWidgetColor, b.unselectedWidgetColor, t),
disabledColor: Color.lerp(a.disabledColor, b.disabledColor, t), disabledColor: Color.lerp(a.disabledColor, b.disabledColor, t),
buttonColor: Color.lerp(a.buttonColor, b.buttonColor, t),
toggleableActiveColor: Color.lerp(a.toggleableActiveColor, b.toggleableActiveColor, t),
buttonTheme: t < 0.5 ? a.buttonTheme : b.buttonTheme, buttonTheme: t < 0.5 ? a.buttonTheme : b.buttonTheme,
buttonColor: Color.lerp(a.buttonColor, b.buttonColor, t),
secondaryHeaderColor: Color.lerp(a.secondaryHeaderColor, b.secondaryHeaderColor, t), secondaryHeaderColor: Color.lerp(a.secondaryHeaderColor, b.secondaryHeaderColor, t),
textSelectionColor: Color.lerp(a.textSelectionColor, b.textSelectionColor, t), textSelectionColor: Color.lerp(a.textSelectionColor, b.textSelectionColor, t),
cursorColor: Color.lerp(a.cursorColor, b.cursorColor, t), cursorColor: Color.lerp(a.cursorColor, b.cursorColor, t),
...@@ -777,6 +824,7 @@ class ThemeData extends Diagnosticable { ...@@ -777,6 +824,7 @@ class ThemeData extends Diagnosticable {
indicatorColor: Color.lerp(a.indicatorColor, b.indicatorColor, t), indicatorColor: Color.lerp(a.indicatorColor, b.indicatorColor, t),
hintColor: Color.lerp(a.hintColor, b.hintColor, t), hintColor: Color.lerp(a.hintColor, b.hintColor, t),
errorColor: Color.lerp(a.errorColor, b.errorColor, t), errorColor: Color.lerp(a.errorColor, b.errorColor, t),
toggleableActiveColor: Color.lerp(a.toggleableActiveColor, b.toggleableActiveColor, t),
textTheme: TextTheme.lerp(a.textTheme, b.textTheme, t), textTheme: TextTheme.lerp(a.textTheme, b.textTheme, t),
primaryTextTheme: TextTheme.lerp(a.primaryTextTheme, b.primaryTextTheme, t), primaryTextTheme: TextTheme.lerp(a.primaryTextTheme, b.primaryTextTheme, t),
accentTextTheme: TextTheme.lerp(a.accentTextTheme, b.accentTextTheme, t), accentTextTheme: TextTheme.lerp(a.accentTextTheme, b.accentTextTheme, t),
...@@ -790,6 +838,7 @@ class ThemeData extends Diagnosticable { ...@@ -790,6 +838,7 @@ class ThemeData extends Diagnosticable {
platform: t < 0.5 ? a.platform : b.platform, platform: t < 0.5 ? a.platform : b.platform,
materialTapTargetSize: t < 0.5 ? a.materialTapTargetSize : b.materialTapTargetSize, materialTapTargetSize: t < 0.5 ? a.materialTapTargetSize : b.materialTapTargetSize,
pageTransitionsTheme: t < 0.5 ? a.pageTransitionsTheme : b.pageTransitionsTheme, pageTransitionsTheme: t < 0.5 ? a.pageTransitionsTheme : b.pageTransitionsTheme,
colorScheme: ColorScheme.lerp(a.colorScheme, b.colorScheme, t),
); );
} }
...@@ -819,9 +868,8 @@ class ThemeData extends Diagnosticable { ...@@ -819,9 +868,8 @@ class ThemeData extends Diagnosticable {
(otherData.selectedRowColor == selectedRowColor) && (otherData.selectedRowColor == selectedRowColor) &&
(otherData.unselectedWidgetColor == unselectedWidgetColor) && (otherData.unselectedWidgetColor == unselectedWidgetColor) &&
(otherData.disabledColor == disabledColor) && (otherData.disabledColor == disabledColor) &&
(otherData.buttonColor == buttonColor) &&
(otherData.toggleableActiveColor == toggleableActiveColor) &&
(otherData.buttonTheme == buttonTheme) && (otherData.buttonTheme == buttonTheme) &&
(otherData.buttonColor == buttonColor) &&
(otherData.secondaryHeaderColor == secondaryHeaderColor) && (otherData.secondaryHeaderColor == secondaryHeaderColor) &&
(otherData.textSelectionColor == textSelectionColor) && (otherData.textSelectionColor == textSelectionColor) &&
(otherData.cursorColor == cursorColor) && (otherData.cursorColor == cursorColor) &&
...@@ -831,6 +879,7 @@ class ThemeData extends Diagnosticable { ...@@ -831,6 +879,7 @@ class ThemeData extends Diagnosticable {
(otherData.indicatorColor == indicatorColor) && (otherData.indicatorColor == indicatorColor) &&
(otherData.hintColor == hintColor) && (otherData.hintColor == hintColor) &&
(otherData.errorColor == errorColor) && (otherData.errorColor == errorColor) &&
(otherData.toggleableActiveColor == toggleableActiveColor) &&
(otherData.textTheme == textTheme) && (otherData.textTheme == textTheme) &&
(otherData.primaryTextTheme == primaryTextTheme) && (otherData.primaryTextTheme == primaryTextTheme) &&
(otherData.accentTextTheme == accentTextTheme) && (otherData.accentTextTheme == accentTextTheme) &&
...@@ -843,7 +892,8 @@ class ThemeData extends Diagnosticable { ...@@ -843,7 +892,8 @@ class ThemeData extends Diagnosticable {
(otherData.chipTheme == chipTheme) && (otherData.chipTheme == chipTheme) &&
(otherData.platform == platform) && (otherData.platform == platform) &&
(otherData.materialTapTargetSize == materialTapTargetSize) && (otherData.materialTapTargetSize == materialTapTargetSize) &&
(otherData.pageTransitionsTheme == pageTransitionsTheme); (otherData.pageTransitionsTheme == pageTransitionsTheme) &&
(otherData.colorScheme == colorScheme);
} }
@override @override
...@@ -870,9 +920,11 @@ class ThemeData extends Diagnosticable { ...@@ -870,9 +920,11 @@ class ThemeData extends Diagnosticable {
splashFactory, splashFactory,
selectedRowColor, selectedRowColor,
unselectedWidgetColor, unselectedWidgetColor,
buttonColor, disabledColor,
buttonTheme, buttonTheme,
hashValues( hashValues(
buttonColor,
toggleableActiveColor,
secondaryHeaderColor, secondaryHeaderColor,
textSelectionColor, textSelectionColor,
cursorColor, cursorColor,
...@@ -882,7 +934,6 @@ class ThemeData extends Diagnosticable { ...@@ -882,7 +934,6 @@ class ThemeData extends Diagnosticable {
indicatorColor, indicatorColor,
hintColor, hintColor,
errorColor, errorColor,
toggleableActiveColor,
textTheme, textTheme,
primaryTextTheme, primaryTextTheme,
accentTextTheme, accentTextTheme,
...@@ -891,12 +942,13 @@ class ThemeData extends Diagnosticable { ...@@ -891,12 +942,13 @@ class ThemeData extends Diagnosticable {
primaryIconTheme, primaryIconTheme,
accentIconTheme, accentIconTheme,
sliderTheme, sliderTheme,
tabBarTheme,
hashValues( hashValues(
tabBarTheme,
chipTheme, chipTheme,
platform, platform,
materialTapTargetSize, materialTapTargetSize,
pageTransitionsTheme, pageTransitionsTheme,
colorScheme,
), ),
), ),
); );
...@@ -946,6 +998,7 @@ class ThemeData extends Diagnosticable { ...@@ -946,6 +998,7 @@ class ThemeData extends Diagnosticable {
properties.add(DiagnosticsProperty<ChipThemeData>('chipTheme', chipTheme)); properties.add(DiagnosticsProperty<ChipThemeData>('chipTheme', chipTheme));
properties.add(DiagnosticsProperty<MaterialTapTargetSize>('materialTapTargetSize', materialTapTargetSize)); properties.add(DiagnosticsProperty<MaterialTapTargetSize>('materialTapTargetSize', materialTapTargetSize));
properties.add(DiagnosticsProperty<PageTransitionsTheme>('pageTransitionsTheme', pageTransitionsTheme)); properties.add(DiagnosticsProperty<PageTransitionsTheme>('pageTransitionsTheme', pageTransitionsTheme));
properties.add(DiagnosticsProperty<ColorScheme>('colorScheme', colorScheme, defaultValue: defaultData.colorScheme));
} }
} }
......
...@@ -107,10 +107,12 @@ void main() { ...@@ -107,10 +107,12 @@ void main() {
EdgeInsets padding; EdgeInsets padding;
ShapeBorder shape; ShapeBorder shape;
const Color disabledColor = Color(0xFF00FF00);
await tester.pumpWidget( await tester.pumpWidget(
Theme( Theme(
data: lightTheme.copyWith( data: lightTheme.copyWith(
disabledColor: const Color(0xFF00FF00), // disabled RaisedButton fill color disabledColor: disabledColor, // disabled RaisedButton fill color
buttonTheme: const ButtonThemeData(disabledColor: disabledColor),
textTheme: lightTheme.textTheme.copyWith( textTheme: lightTheme.textTheme.copyWith(
button: lightTheme.textTheme.button.copyWith( button: lightTheme.textTheme.button.copyWith(
// The button's height will match because there's no // The button's height will match because there's no
...@@ -149,7 +151,7 @@ void main() { ...@@ -149,7 +151,7 @@ void main() {
)); ));
expect(tester.widget<Material>(find.byType(Material)).shape, shape); expect(tester.widget<Material>(find.byType(Material)).shape, shape);
expect(tester.widget<Material>(find.byType(Material)).color, const Color(0xFF00FF00)); expect(tester.widget<Material>(find.byType(Material)).color, disabledColor);
expect(tester.getSize(find.byType(Material)), const Size(88.0, 48.0)); expect(tester.getSize(find.byType(Material)), const Size(88.0, 48.0));
}); });
...@@ -169,6 +171,7 @@ void main() { ...@@ -169,6 +171,7 @@ void main() {
minWidth: 100.0, minWidth: 100.0,
height: 200.0, height: 200.0,
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
buttonColor: const Color(0xFF00FF00), // enabled RaisedButton fill color
shape: const RoundedRectangleBorder(), shape: const RoundedRectangleBorder(),
child: Builder( child: Builder(
builder: (BuildContext context) { builder: (BuildContext context) {
......
...@@ -89,7 +89,7 @@ void main() { ...@@ -89,7 +89,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, outlineButton, //find.byType(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));
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
const ShapeBorder defaultButtonShape = RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(2.0)));
const EdgeInsets defaultButtonPadding = EdgeInsets.only(left: 16.0, right: 16.0);
const BoxConstraints defaultButtonConstraints = BoxConstraints(minWidth: 88.0, minHeight: 36.0);
const Duration defaultButtonDuration = Duration(milliseconds: 200);
void main() {
group('RaisedButton', () {
testWidgets('theme: ThemeData.light(), enabled: true', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData.light(),
home: Center(
child: RaisedButton(
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, const Color(0xffe0e0e0));
expect(raw.highlightColor, const Color(0x29000000)); // Was Color(0x66bcbcbc)
expect(raw.splashColor, const Color(0x1f000000)); // Was Color(0x66c8c8c8)
expect(raw.elevation, 2.0);
expect(raw.highlightElevation, 8.0);
expect(raw.disabledElevation, 0.0);
expect(raw.constraints, defaultButtonConstraints);
expect(raw.padding, defaultButtonPadding);
expect(raw.shape, defaultButtonShape);
expect(raw.animationDuration, defaultButtonDuration);
expect(raw.materialTapTargetSize, MaterialTapTargetSize.padded);
});
testWidgets('theme: ThemeData.light(), enabled: false', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData.light(),
home: const Center(
child: RaisedButton(
onPressed: null, // button.enabled == false
child: Text('button'),
)
),
),
);
final RawMaterialButton raw = tester.widget<RawMaterialButton>(find.byType(RawMaterialButton));
expect(raw.textStyle.color, const Color(0x61000000));
expect(raw.fillColor, const Color(0x61000000));
// highlightColor, disabled button can't be pressed
// splashColor, disabled button doesn't splash
expect(raw.elevation, 2.0);
expect(raw.highlightElevation, 8.0);
expect(raw.disabledElevation, 0.0);
expect(raw.constraints, defaultButtonConstraints);
expect(raw.padding, defaultButtonPadding);
expect(raw.shape, defaultButtonShape);
expect(raw.animationDuration, defaultButtonDuration);
expect(raw.materialTapTargetSize, MaterialTapTargetSize.padded);
});
});
group('FlatButton', () {
testWidgets('theme: ThemeData.light(), enabled: true', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData.light(),
home: Center(
child: FlatButton(
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, null);
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);
expect(raw.shape, defaultButtonShape);
expect(raw.animationDuration, defaultButtonDuration);
expect(raw.materialTapTargetSize, MaterialTapTargetSize.padded);
});
testWidgets('theme: ThemeData.light(), enabled: false', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData.light(),
home: const Center(
child: FlatButton(
onPressed: null, // button.enabled == false
child: Text('button'),
)
),
),
);
final RawMaterialButton raw = tester.widget<RawMaterialButton>(find.byType(RawMaterialButton));
expect(raw.textStyle.color, const Color(0x61000000));
expect(raw.fillColor, null);
// highlightColor, disabled button can't be pressed
// splashColor, disabled button doesn't splash
expect(raw.elevation, 0.0);
expect(raw.highlightElevation, 0.0);
expect(raw.disabledElevation, 0.0);
expect(raw.constraints, defaultButtonConstraints);
expect(raw.padding, defaultButtonPadding);
expect(raw.shape, defaultButtonShape);
expect(raw.animationDuration, const Duration(milliseconds: 200));
expect(raw.materialTapTargetSize, MaterialTapTargetSize.padded);
});
});
group('OutlineButton', () {
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, const Color(0x00ffffff));
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 {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData.light(),
home: const Center(
child: OutlineButton(
onPressed: null, // button.enabled == false
child: Text('button'),
)
),
),
);
final RawMaterialButton raw = tester.widget<RawMaterialButton>(find.byType(RawMaterialButton));
expect(raw.textStyle.color, const Color(0x61000000));
expect(raw.fillColor, const Color(0x00000000));
// highlightColor, disabled button can't be pressed
// splashColor, disabled button doesn't splash
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);
});
});
}
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