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) {
ThemeData _buildDarkTheme() {
const Color primaryColor = Color(0xFF0175c2);
final ThemeData base = ThemeData.dark();
final ColorScheme colorScheme = const ColorScheme.dark().copyWith(
primary: primaryColor,
);
return base.copyWith(
primaryColor: primaryColor,
buttonColor: primaryColor,
......@@ -34,7 +37,8 @@ ThemeData _buildDarkTheme() {
scaffoldBackgroundColor: const Color(0xFF202124),
backgroundColor: const Color(0xFF202124),
errorColor: const Color(0xFFB00020),
buttonTheme: const ButtonThemeData(
buttonTheme: ButtonThemeData(
colorScheme: colorScheme,
textTheme: ButtonTextTheme.primary,
),
textTheme: _buildTextTheme(base.textTheme),
......@@ -45,8 +49,12 @@ ThemeData _buildDarkTheme() {
ThemeData _buildLightTheme() {
const Color primaryColor = Color(0xFF0175c2);
final ColorScheme colorScheme = const ColorScheme.light().copyWith(
primary: primaryColor,
);
final ThemeData base = ThemeData.light();
return base.copyWith(
colorScheme: colorScheme,
primaryColor: primaryColor,
buttonColor: primaryColor,
indicatorColor: Colors.white,
......@@ -57,7 +65,8 @@ ThemeData _buildLightTheme() {
scaffoldBackgroundColor: Colors.white,
backgroundColor: Colors.white,
errorColor: const Color(0xFFB00020),
buttonTheme: const ButtonThemeData(
buttonTheme: ButtonThemeData(
colorScheme: colorScheme,
textTheme: ButtonTextTheme.primary,
),
textTheme: _buildTextTheme(base.textTheme),
......
......@@ -380,12 +380,12 @@ void main() {
handle.dispose();
});
testWidgets('overscroll_demo $themeName', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics();
await tester.pumpWidget(MaterialApp(theme: theme, home: const OverscrollDemo()));
await expectLater(tester, meetsGuideline(textContrastGuideline));
handle.dispose();
});
testWidgets('overscroll_demo', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics();
await tester.pumpWidget(const MaterialApp(home: OverscrollDemo()));
await expectLater(tester, meetsGuideline(textContrastGuideline));
handle.dispose();
});
testWidgets('page_selector_demo $themeName', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics();
......
......@@ -32,6 +32,7 @@ export 'src/material/checkbox_list_tile.dart';
export 'src/material/chip.dart';
export 'src/material/chip_theme.dart';
export 'src/material/circle_avatar.dart';
export 'src/material/color_scheme.dart';
export 'src/material/colors.dart';
export 'src/material/constants.dart';
export 'src/material/data_table.dart';
......@@ -65,6 +66,7 @@ export 'src/material/input_border.dart';
export 'src/material/input_decorator.dart';
export 'src/material/list_tile.dart';
export 'src/material/material.dart';
export 'src/material/material_button.dart';
export 'src/material/material_localizations.dart';
export 'src/material/mergeable_material.dart';
export 'src/material/outline_button.dart';
......
......@@ -9,7 +9,6 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'button_theme.dart';
import 'colors.dart';
import 'constants.dart';
import 'ink_well.dart';
import 'material.dart';
......@@ -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].
///
/// Redirect taps that occur in the padded area around the child to the center
......
......@@ -5,7 +5,15 @@
import 'package:flutter/foundation.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_data.dart' show MaterialTapTargetSize;
/// Used with [ButtonTheme] and [ButtonThemeData] to define a button's base
/// colors, and the defaults for the button's minimum size, internal padding,
......@@ -13,8 +21,8 @@ import 'theme.dart';
///
/// See also:
///
/// * [RaisedButton], which styles itself based on the ambient [ButtonTheme].
/// * [FlatButton], which styles itself based on the ambient [ButtonTheme].
/// * [RaisedButton], [FlatButton], [OutlineButton], which are configured
/// based on the ambient [ButtonTheme].
enum ButtonTextTheme {
/// Button text is black or white depending on [ThemeData.brightness].
normal,
......@@ -46,30 +54,19 @@ enum ButtonBarLayoutBehavior {
/// can be overridden with [ButtonTheme].
///
/// The actual appearance of buttons depends on the button theme, the
/// button's enabled state, its elevation (if any) and the overall Material
/// theme.
/// button's enabled state, its elevation (if any), and the overall [Theme].
///
/// See also:
///
/// * [FlatButton] and [RaisedButton], which are styled 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.
/// * [FlatButton] [RaisedButton], and [OutlineButton], which are styled
/// based on the ambient button theme.
/// * [RawMaterialButton], which can be used to configure a button that doesn't
/// depend on any inherited themes.
class ButtonTheme extends InheritedWidget {
/// 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({
Key key,
ButtonTextTheme textTheme = ButtonTextTheme.normal,
......@@ -79,12 +76,19 @@ class ButtonTheme extends InheritedWidget {
EdgeInsetsGeometry padding,
ShapeBorder shape,
bool alignedDropdown = false,
Color buttonColor,
Color disabledColor,
Color highlightColor,
Color splashColor,
ColorScheme colorScheme = const ColorScheme.light(),
MaterialTapTargetSize materialTapTargetSize,
Widget child,
}) : assert(textTheme != null),
assert(minWidth != null && minWidth >= 0.0),
assert(height != null && height >= 0.0),
assert(alignedDropdown != null),
assert(layoutBehavior != null),
assert(colorScheme != null),
data = ButtonThemeData(
textTheme: textTheme,
minWidth: minWidth,
......@@ -93,6 +97,12 @@ class ButtonTheme extends InheritedWidget {
shape: shape,
alignedDropdown: alignedDropdown,
layoutBehavior: layoutBehavior,
buttonColor: buttonColor,
disabledColor: disabledColor,
highlightColor: highlightColor,
splashColor: splashColor,
colorScheme: colorScheme,
materialTapTargetSize: materialTapTargetSize,
),
super(key: key, child: child);
......@@ -128,12 +138,18 @@ class ButtonTheme extends InheritedWidget {
EdgeInsetsGeometry padding = const EdgeInsets.symmetric(horizontal: 8.0),
ShapeBorder shape,
bool alignedDropdown = false,
Color buttonColor,
Color disabledColor,
Color highlightColor,
Color splashColor,
ColorScheme colorScheme = const ColorScheme.light(),
Widget child,
ButtonBarLayoutBehavior layoutBehavior = ButtonBarLayoutBehavior.padded,
}) : assert(textTheme != null),
assert(minWidth != null && minWidth >= 0.0),
assert(height != null && height >= 0.0),
assert(alignedDropdown != null),
assert(colorScheme != null),
data = ButtonThemeData(
textTheme: textTheme,
minWidth: minWidth,
......@@ -142,6 +158,11 @@ class ButtonTheme extends InheritedWidget {
shape: shape,
alignedDropdown: alignedDropdown,
layoutBehavior: layoutBehavior,
buttonColor: buttonColor,
disabledColor: disabledColor,
highlightColor: highlightColor,
splashColor: splashColor,
colorScheme: colorScheme,
),
super(key: key, child: child);
......@@ -174,6 +195,10 @@ class ButtonThemeData extends Diagnosticable {
/// or [ThemeData].
///
/// 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({
this.textTheme = ButtonTextTheme.normal,
this.minWidth = 88.0,
......@@ -182,13 +207,25 @@ class ButtonThemeData extends Diagnosticable {
ShapeBorder shape,
this.layoutBehavior = ButtonBarLayoutBehavior.padded,
this.alignedDropdown = false,
Color buttonColor,
Color disabledColor,
Color highlightColor,
Color splashColor,
this.colorScheme = const ColorScheme.light(),
MaterialTapTargetSize materialTapTargetSize,
}) : assert(textTheme != null),
assert(minWidth != null && minWidth >= 0.0),
assert(height != null && height >= 0.0),
assert(alignedDropdown != null),
assert(layoutBehavior != null),
assert(colorScheme != null),
_buttonColor = buttonColor,
_disabledColor = disabledColor,
_highlightColor = highlightColor,
_splashColor = splashColor,
_padding = padding,
_shape = shape;
_shape = shape,
_materialTapTargetSize = materialTapTargetSize;
/// The minimum width for buttons.
///
......@@ -205,10 +242,13 @@ class ButtonThemeData extends Diagnosticable {
/// Defines a button's base colors, and the defaults for the button's minimum
/// 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;
/// Defines whether a button bar should size itself with a minimum size
/// constraint or padding.
/// Defines whether a [ButtonBar] should size itself with a minimum size
/// constraint or with padding.
///
/// Defaults to [ButtonBarLayoutBehavior.padded].
final ButtonBarLayoutBehavior layoutBehavior;
......@@ -232,16 +272,22 @@ class ButtonThemeData extends Diagnosticable {
///
/// Defaults to 24.0 on the left and right if [textTheme] is
/// [ButtonTextTheme.primary], 16.0 on the left and right otherwise.
///
/// See also:
///
/// * [getPadding], which is used by [RaisedButton], [OutlineButton]
/// and [FlatButton].
EdgeInsetsGeometry get padding {
if (_padding != null)
return _padding;
switch (textTheme) {
switch (textTheme) {
case ButtonTextTheme.normal:
case ButtonTextTheme.accent:
return const EdgeInsets.symmetric(horizontal: 16.0);
case ButtonTextTheme.primary:
return const EdgeInsets.symmetric(horizontal: 24.0);
return const EdgeInsets.symmetric(horizontal: 24.0);
}
assert(false);
return EdgeInsets.zero;
}
final EdgeInsetsGeometry _padding;
......@@ -255,6 +301,11 @@ class ButtonThemeData extends Diagnosticable {
/// Defaults to a rounded rectangle with circular corner radii of 4.0 if
/// [textTheme] is [ButtonTextTheme.primary], a rounded rectangle with
/// circular corner radii of 2.0 otherwise.
///
/// See also:
///
/// * [getShape], which is used by [RaisedButton], [OutlineButton]
/// and [FlatButton].
ShapeBorder get shape {
if (_shape != null)
return _shape;
......@@ -284,6 +335,386 @@ class ButtonThemeData extends Diagnosticable {
/// This property only affects [DropdownButton] and its menu.
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
/// replaced with the non-null parameter values.
ButtonThemeData copyWith({
......@@ -293,6 +724,12 @@ class ButtonThemeData extends Diagnosticable {
EdgeInsetsGeometry padding,
ShapeBorder shape,
bool alignedDropdown,
Color buttonColor,
Color disabledColor,
Color highlightColor,
Color splashColor,
ColorScheme colorScheme,
MaterialTapTargetSize materialTapTargetSize,
}) {
return ButtonThemeData(
textTheme: textTheme ?? this.textTheme,
......@@ -301,6 +738,12 @@ class ButtonThemeData extends Diagnosticable {
padding: padding ?? this.padding,
shape: shape ?? this.shape,
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 {
&& height == typedOther.height
&& padding == typedOther.padding
&& 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
......@@ -326,6 +775,12 @@ class ButtonThemeData extends Diagnosticable {
padding,
shape,
alignedDropdown,
_buttonColor,
_disabledColor,
_highlightColor,
_splashColor,
colorScheme,
_materialTapTargetSize,
);
}
......@@ -343,5 +798,11 @@ class ButtonThemeData extends Diagnosticable {
defaultValue: defaultTheme.alignedDropdown,
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';
import 'button.dart';
import 'button_theme.dart';
import 'colors.dart';
import 'material_button.dart';
import 'theme.dart';
import 'theme_data.dart';
......@@ -49,26 +49,43 @@ import 'theme_data.dart';
/// * [InkWell], which implements the ink splash part of a flat button.
/// * [RawMaterialButton], the widget this widget is based on.
/// * <https://material.google.com/components/buttons.html>
class FlatButton extends StatelessWidget {
class FlatButton extends MaterialButton {
/// Create a simple text button.
const FlatButton({
Key key,
@required this.onPressed,
this.onHighlightChanged,
this.textTheme,
this.textColor,
this.disabledTextColor,
this.color,
this.disabledColor,
this.highlightColor,
this.splashColor,
this.colorBrightness,
this.padding,
this.shape,
this.clipBehavior = Clip.none,
this.materialTapTargetSize,
@required this.child,
}) : assert(clipBehavior != null), super(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 = Clip.none,
MaterialTapTargetSize materialTapTargetSize,
@required Widget child,
}) : 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
/// [icon] and [label].
......@@ -77,244 +94,47 @@ class FlatButton extends StatelessWidget {
/// 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.
FlatButton.icon({
factory FlatButton.icon({
Key key,
@required this.onPressed,
this.onHighlightChanged,
this.textTheme,
this.textColor,
this.disabledTextColor,
this.color,
this.disabledColor,
this.highlightColor,
this.splashColor,
this.colorBrightness,
this.shape,
this.clipBehavior = Clip.none,
this.materialTapTargetSize,
@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),
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;
}
}) = _FlatButtonWithIcon;
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
final ButtonThemeData buttonTheme = ButtonTheme.of(context);
final Color fillColor = enabled ? color : disabledColor;
final Color textColor = _getTextColor(theme, buttonTheme, fillColor);
return RawMaterialButton(
onPressed: onPressed,
onHighlightChanged: onHighlightChanged,
fillColor: fillColor,
textStyle: theme.textTheme.button.copyWith(color: textColor),
highlightColor: _getHighlightColor(theme, buttonTheme),
splashColor: _getSplashColor(theme, buttonTheme),
elevation: 0.0,
highlightElevation: 0.0,
materialTapTargetSize: materialTapTargetSize ?? theme.materialTapTargetSize,
padding: padding ?? buttonTheme.padding,
constraints: buttonTheme.constraints,
shape: shape ?? buttonTheme.shape,
clipBehavior: clipBehavior,
clipBehavior: clipBehavior ?? Clip.none,
fillColor: buttonTheme.getFillColor(this),
textStyle: theme.textTheme.button.copyWith(color: buttonTheme.getTextColor(this)),
highlightColor: buttonTheme.getHighlightColor(this),
splashColor: buttonTheme.getSplashColor(this),
elevation: buttonTheme.getElevation(this),
highlightElevation: buttonTheme.getHighlightElevation(this),
disabledElevation: buttonTheme.getDisabledElevation(this),
padding: buttonTheme.getPadding(this),
constraints: buttonTheme.getConstraints(this),
shape: buttonTheme.getShape(this),
animationDuration: buttonTheme.getAnimationDuration(this),
materialTapTargetSize: buttonTheme.getMaterialTapTargetSize(this),
child: child,
);
}
......@@ -336,3 +156,56 @@ class FlatButton extends StatelessWidget {
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';
import 'button_theme.dart';
import 'colors.dart';
import 'material_button.dart';
import 'raised_button.dart';
import 'theme.dart';
......@@ -48,31 +49,44 @@ const Duration _kElevationDuration = Duration(milliseconds: 75);
/// * [IconButton], to create buttons that just contain icons.
/// * [InkWell], which implements the ink splash part of a flat button.
/// * <https://material.google.com/components/buttons.html>
class OutlineButton extends StatefulWidget {
class OutlineButton extends MaterialButton {
/// Create a filled button.
///
/// The [highlightElevation], [borderWidth], and [clipBehavior]
/// arguments must not be null.
const OutlineButton({
Key key,
@required this.onPressed,
this.textTheme,
this.textColor,
this.disabledTextColor,
this.color,
this.highlightColor,
this.splashColor,
this.highlightElevation = 2.0,
@required VoidCallback onPressed,
ButtonTextTheme textTheme,
Color textColor,
Color disabledTextColor,
Color color,
Color highlightColor,
Color splashColor,
double highlightElevation,
this.borderSide,
this.disabledBorderColor,
this.highlightedBorderColor,
this.padding,
this.shape,
this.clipBehavior = Clip.none,
this.child,
}) : assert(highlightElevation != null && highlightElevation >= 0.0),
assert(clipBehavior != null),
super(key: key);
EdgeInsetsGeometry padding,
ShapeBorder shape,
Clip clipBehavior = Clip.none,
Widget child,
}) : assert(highlightElevation == null || highlightElevation >= 0.0),
super(
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
/// [icon] and [label].
......@@ -82,125 +96,25 @@ class OutlineButton extends StatefulWidget {
///
/// The [highlightElevation], [icon], [label], and [clipBehavior] must not be
/// null.
OutlineButton.icon({
factory OutlineButton.icon({
Key key,
@required this.onPressed,
this.textTheme,
this.textColor,
this.disabledTextColor,
this.color,
this.highlightColor,
this.splashColor,
this.highlightElevation = 2.0,
this.borderSide,
this.disabledBorderColor,
this.highlightedBorderColor,
this.shape,
this.clipBehavior = Clip.none,
@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),
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;
}) = _OutlineButtonWithIcon;
/// The outline border's color when the button is [enabled] and pressed.
///
......@@ -215,36 +129,38 @@ class OutlineButton extends StatefulWidget {
/// dark themes.
final Color disabledBorderColor;
/// 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] 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.
/// Defines the color of the border when the button is enabled but not
/// pressed, and the border outline's width and style in general.
///
/// Often a [Text] widget in all caps.
final Widget child;
/// Whether the button is enabled or disabled.
/// If the border side's [BorderSide.style] is [BorderStyle.none], then
/// an outline is not drawn.
///
/// Buttons are disabled by default. To enable a button, set its [onPressed]
/// property to a non-null value.
bool get enabled => onPressed != null;
/// 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;
@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
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
......@@ -256,7 +172,7 @@ class OutlineButton extends StatefulWidget {
properties.add(DiagnosticsProperty<Color>('color', color, defaultValue: null));
properties.add(DiagnosticsProperty<Color>('highlightColor', highlightColor, 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<Color>('disabledBorderColor', disabledBorderColor, defaultValue: null));
properties.add(DiagnosticsProperty<Color>('highlightedBorderColor', highlightedBorderColor, defaultValue: null));
......@@ -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;
Animation<double> _fillAnimation;
Animation<double> _elevationAnimation;
......@@ -305,36 +321,8 @@ class _OutlineButtonState extends State<OutlineButton> with SingleTickerProvider
super.dispose();
}
ButtonTextTheme _getTextTheme(ButtonThemeData buttonTheme) {
return widget.textTheme ?? buttonTheme.textTheme;
}
// 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;
Color _getFillColor() {
final bool themeIsDark = widget.brightness == Brightness.dark;
final Color color = widget.color ?? (themeIsDark
? const Color(0x00000000)
: const Color(0x00FFFFFF));
......@@ -345,35 +333,18 @@ class _OutlineButtonState extends State<OutlineButton> with SingleTickerProvider
return colorTween.evaluate(_fillAnimation);
}
// TODO(hmuller): this method is the same as FlatButton
Color _getSplashColor(ThemeData theme, ButtonThemeData buttonTheme) {
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;
BorderSide _getOutline() {
final bool isDark = widget.brightness == Brightness.dark;
if (widget.borderSide?.style == BorderStyle.none)
return widget.borderSide;
final Color color = widget.enabled
? (_pressed
? widget.highlightedBorderColor ?? theme.primaryColor
? widget.highlightedBorderColor
: (widget.borderSide?.color ??
(themeIsDark ? Colors.grey[600] : Colors.grey[200])))
(isDark ? Colors.grey[600] : Colors.grey[200])))
: (widget.disabledBorderColor ??
(themeIsDark ? Colors.grey[800] : Colors.grey[100]));
(isDark ? Colors.grey[800] : Colors.grey[100]));
return BorderSide(
color: color,
......@@ -390,19 +361,14 @@ class _OutlineButtonState extends State<OutlineButton> with SingleTickerProvider
@override
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(
animation: _controller,
builder: (BuildContext context, Widget child) {
return RaisedButton(
textColor: textColor,
textColor: widget.textColor,
disabledTextColor: widget.disabledTextColor,
color: _getFillColor(theme),
splashColor: splashColor,
color: _getFillColor(),
splashColor: widget.splashColor,
highlightColor: widget.highlightColor,
disabledColor: Colors.transparent,
onPressed: widget.onPressed,
......@@ -420,8 +386,8 @@ class _OutlineButtonState extends State<OutlineButton> with SingleTickerProvider
},
padding: widget.padding,
shape: _OutlineBorder(
shape: widget.shape ?? buttonTheme.shape,
side: _getOutline(theme, buttonTheme),
shape: widget.shape,
side: _getOutline(),
),
clipBehavior: widget.clipBehavior,
animationDuration: _kElevationDuration,
......
......@@ -7,8 +7,7 @@ import 'package:flutter/widgets.dart';
import 'button.dart';
import 'button_theme.dart';
import 'colors.dart';
import 'constants.dart';
import 'material_button.dart';
import 'theme.dart';
import 'theme_data.dart';
......@@ -41,38 +40,57 @@ import 'theme_data.dart';
/// * [InkWell], which implements the ink splash part of a flat button.
/// * [RawMaterialButton], the widget this widget is based on.
/// * <https://material.google.com/components/buttons.html>
class RaisedButton extends StatelessWidget {
class RaisedButton extends MaterialButton {
/// Create a filled button.
///
/// The [elevation], [highlightElevation], [disabledElevation], and
/// [clipBehavior] arguments must not be null.
const RaisedButton({
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 = 2.0,
this.highlightElevation = 8.0,
this.disabledElevation = 0.0,
this.padding,
this.shape,
this.clipBehavior = Clip.none,
this.materialTapTargetSize,
this.animationDuration = kThemeChangeDuration,
this.child,
}) : assert(elevation != null),
assert(highlightElevation != null),
assert(disabledElevation != null),
assert(animationDuration != null),
assert(clipBehavior != null),
super(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,
EdgeInsetsGeometry padding,
ShapeBorder shape,
Clip clipBehavior = Clip.none,
MaterialTapTargetSize materialTapTargetSize,
Duration animationDuration,
Widget child,
}) : assert(elevation == null || elevation >= 0.0),
assert(highlightElevation == null || highlightElevation >= 0.0),
assert(disabledElevation == null || disabledElevation >= 0.0),
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
/// [icon] and [label].
......@@ -82,325 +100,51 @@ class RaisedButton extends StatelessWidget {
///
/// The [elevation], [highlightElevation], [disabledElevation], [icon],
/// [label], and [clipBehavior] arguments must not be null.
RaisedButton.icon({
factory RaisedButton.icon({
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 = 2.0,
this.highlightElevation = 8.0,
this.disabledElevation = 0.0,
this.shape,
this.clipBehavior = Clip.none,
this.materialTapTargetSize,
this.animationDuration = kThemeChangeDuration,
@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,
MaterialTapTargetSize materialTapTargetSize,
Duration animationDuration,
@required Widget icon,
@required Widget label,
}) : assert(elevation != null),
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.
}) = _RaisedButtonWithIcon;
/// 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;
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
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
final ButtonThemeData buttonTheme = ButtonTheme.of(context);
final Color fillColor = _getFillColor(theme, buttonTheme);
final Color textColor = _getTextColor(theme, buttonTheme, fillColor);
return RawMaterialButton(
onPressed: onPressed,
onHighlightChanged: onHighlightChanged,
fillColor: fillColor,
textStyle: theme.textTheme.button.copyWith(color: textColor),
highlightColor: _getHighlightColor(theme, buttonTheme),
splashColor: splashColor ?? theme.splashColor,
elevation: elevation,
highlightElevation: highlightElevation,
disabledElevation: disabledElevation,
padding: padding ?? buttonTheme.padding,
constraints: buttonTheme.constraints,
shape: shape ?? buttonTheme.shape,
clipBehavior: clipBehavior,
animationDuration: animationDuration,
clipBehavior: clipBehavior ?? Clip.none,
fillColor: buttonTheme.getFillColor(this),
textStyle: theme.textTheme.button.copyWith(color: buttonTheme.getTextColor(this)),
highlightColor: buttonTheme.getHighlightColor(this),
splashColor: buttonTheme.getSplashColor(this),
elevation: buttonTheme.getElevation(this),
highlightElevation: buttonTheme.getHighlightElevation(this),
disabledElevation: buttonTheme.getDisabledElevation(this),
padding: buttonTheme.getPadding(this),
constraints: buttonTheme.getConstraints(this),
shape: buttonTheme.getShape(this),
animationDuration: buttonTheme.getAnimationDuration(this),
materialTapTargetSize: buttonTheme.getMaterialTapTargetSize(this),
child: child,
materialTapTargetSize: materialTapTargetSize ?? theme.materialTapTargetSize,
);
}
......@@ -422,3 +166,64 @@ class RaisedButton extends StatelessWidget {
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';
import 'button_theme.dart';
import 'chip_theme.dart';
import 'color_scheme.dart';
import 'colors.dart';
import 'ink_splash.dart';
import 'ink_well.dart' show InteractiveInkFeatureFactory;
......@@ -147,8 +148,8 @@ class ThemeData extends Diagnosticable {
TargetPlatform platform,
MaterialTapTargetSize materialTapTargetSize,
PageTransitionsTheme pageTransitionsTheme,
ColorScheme colorScheme,
}) {
materialTapTargetSize ??= MaterialTapTargetSize.padded;
brightness ??= Brightness.light;
final bool isDark = brightness == Brightness.dark;
primarySwatch ??= Colors.blue;
......@@ -166,14 +167,22 @@ class ThemeData extends Diagnosticable {
bottomAppBarColor ??= isDark ? Colors.grey[800] : Colors.white;
cardColor ??= isDark ? Colors.grey[800] : Colors.white;
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;
selectedRowColor ??= Colors.grey[100];
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.
secondaryHeaderColor ??= isDark ? Colors.grey[700] : primarySwatch[50];
textSelectionColor ??= isDark ? accentColor : primarySwatch[200];
......@@ -198,11 +207,28 @@ class ThemeData extends Diagnosticable {
primaryTextTheme = defaultPrimaryTextTheme.merge(primaryTextTheme);
final TextTheme defaultAccentTextTheme = accentIsDark ? typography.white : typography.black;
accentTextTheme = defaultAccentTextTheme.merge(accentTextTheme);
materialTapTargetSize ??= MaterialTapTargetSize.padded;
if (fontFamily != null) {
textTheme = textTheme.apply(fontFamily: fontFamily);
primaryTextTheme = primaryTextTheme.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(
primaryColor: primaryColor,
primaryColorLight: primaryColorLight,
......@@ -235,9 +261,9 @@ class ThemeData extends Diagnosticable {
selectedRowColor: selectedRowColor,
unselectedWidgetColor: unselectedWidgetColor,
disabledColor: disabledColor,
buttonTheme: buttonTheme,
buttonColor: buttonColor,
toggleableActiveColor: toggleableActiveColor,
buttonTheme: buttonTheme,
secondaryHeaderColor: secondaryHeaderColor,
textSelectionColor: textSelectionColor,
cursorColor: cursorColor,
......@@ -260,6 +286,7 @@ class ThemeData extends Diagnosticable {
platform: platform,
materialTapTargetSize: materialTapTargetSize,
pageTransitionsTheme: pageTransitionsTheme,
colorScheme: colorScheme,
);
}
......@@ -278,9 +305,9 @@ class ThemeData extends Diagnosticable {
@required this.primaryColorBrightness,
@required this.primaryColorLight,
@required this.primaryColorDark,
@required this.canvasColor,
@required this.accentColor,
@required this.accentColorBrightness,
@required this.canvasColor,
@required this.scaffoldBackgroundColor,
@required this.bottomAppBarColor,
@required this.cardColor,
......@@ -291,8 +318,8 @@ class ThemeData extends Diagnosticable {
@required this.selectedRowColor,
@required this.unselectedWidgetColor,
@required this.disabledColor,
@required this.buttonColor,
@required this.buttonTheme,
@required this.buttonColor,
@required this.secondaryHeaderColor,
@required this.textSelectionColor,
@required this.cursorColor,
......@@ -316,6 +343,7 @@ class ThemeData extends Diagnosticable {
@required this.platform,
@required this.materialTapTargetSize,
@required this.pageTransitionsTheme,
@required this.colorScheme,
}) : assert(brightness != null),
assert(primaryColor != null),
assert(primaryColorBrightness != null),
......@@ -357,7 +385,12 @@ class ThemeData extends Diagnosticable {
assert(chipTheme != null),
assert(platform != 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.
///
......@@ -407,6 +440,9 @@ class ThemeData extends Diagnosticable {
/// A darker version of the [primaryColor].
final Color primaryColorDark;
/// The default color of [MaterialType.canvas] [Material].
final Color canvasColor;
/// The foreground color for widgets (knobs, text, overscroll edge effect, etc).
final Color accentColor;
......@@ -415,9 +451,6 @@ class ThemeData extends Diagnosticable {
/// action button).
final Brightness accentColorBrightness;
/// The default color of [MaterialType.canvas] [Material].
final Color canvasColor;
/// The default color of the [Material] that underlies the [Scaffold]. The
/// background color for a typical material app or a page within the app.
final Color scaffoldBackgroundColor;
......@@ -467,17 +500,13 @@ class ThemeData extends Diagnosticable {
/// checked or unchecked).
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]
/// and [FlatButton].
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.
// According to the spec for data tables:
// https://material.google.com/components/data-tables.html#data-tables-tables-within-cards
......@@ -510,6 +539,10 @@ class ThemeData extends Diagnosticable {
/// The color to use for input validation errors, e.g. in [TextField] fields.
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.
final TextTheme textTheme;
......@@ -569,6 +602,16 @@ class ThemeData extends Diagnosticable {
/// builder is not found, a builder whose platform is null is used.
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.
ThemeData copyWith({
Brightness brightness,
......@@ -589,8 +632,8 @@ class ThemeData extends Diagnosticable {
Color selectedRowColor,
Color unselectedWidgetColor,
Color disabledColor,
Color buttonColor,
ButtonThemeData buttonTheme,
Color buttonColor,
Color secondaryHeaderColor,
Color textSelectionColor,
Color cursorColor,
......@@ -614,6 +657,7 @@ class ThemeData extends Diagnosticable {
TargetPlatform platform,
MaterialTapTargetSize materialTapTargetSize,
PageTransitionsTheme pageTransitionsTheme,
ColorScheme colorScheme,
}) {
return ThemeData.raw(
brightness: brightness ?? this.brightness,
......@@ -659,6 +703,7 @@ class ThemeData extends Diagnosticable {
platform: platform ?? this.platform,
materialTapTargetSize: materialTapTargetSize ?? this.materialTapTargetSize,
pageTransitionsTheme: pageTransitionsTheme ?? this.pageTransitionsTheme,
colorScheme: colorScheme ?? this.colorScheme,
);
}
......@@ -746,6 +791,9 @@ class ThemeData extends Diagnosticable {
assert(a != null);
assert(b != 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(
brightness: t < 0.5 ? a.brightness : b.brightness,
primaryColor: Color.lerp(a.primaryColor, b.primaryColor, t),
......@@ -765,9 +813,8 @@ class ThemeData extends Diagnosticable {
selectedRowColor: Color.lerp(a.selectedRowColor, b.selectedRowColor, t),
unselectedWidgetColor: Color.lerp(a.unselectedWidgetColor, b.unselectedWidgetColor, 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,
buttonColor: Color.lerp(a.buttonColor, b.buttonColor, t),
secondaryHeaderColor: Color.lerp(a.secondaryHeaderColor, b.secondaryHeaderColor, t),
textSelectionColor: Color.lerp(a.textSelectionColor, b.textSelectionColor, t),
cursorColor: Color.lerp(a.cursorColor, b.cursorColor, t),
......@@ -777,6 +824,7 @@ class ThemeData extends Diagnosticable {
indicatorColor: Color.lerp(a.indicatorColor, b.indicatorColor, t),
hintColor: Color.lerp(a.hintColor, b.hintColor, 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),
primaryTextTheme: TextTheme.lerp(a.primaryTextTheme, b.primaryTextTheme, t),
accentTextTheme: TextTheme.lerp(a.accentTextTheme, b.accentTextTheme, t),
......@@ -790,6 +838,7 @@ class ThemeData extends Diagnosticable {
platform: t < 0.5 ? a.platform : b.platform,
materialTapTargetSize: t < 0.5 ? a.materialTapTargetSize : b.materialTapTargetSize,
pageTransitionsTheme: t < 0.5 ? a.pageTransitionsTheme : b.pageTransitionsTheme,
colorScheme: ColorScheme.lerp(a.colorScheme, b.colorScheme, t),
);
}
......@@ -819,9 +868,8 @@ class ThemeData extends Diagnosticable {
(otherData.selectedRowColor == selectedRowColor) &&
(otherData.unselectedWidgetColor == unselectedWidgetColor) &&
(otherData.disabledColor == disabledColor) &&
(otherData.buttonColor == buttonColor) &&
(otherData.toggleableActiveColor == toggleableActiveColor) &&
(otherData.buttonTheme == buttonTheme) &&
(otherData.buttonColor == buttonColor) &&
(otherData.secondaryHeaderColor == secondaryHeaderColor) &&
(otherData.textSelectionColor == textSelectionColor) &&
(otherData.cursorColor == cursorColor) &&
......@@ -831,6 +879,7 @@ class ThemeData extends Diagnosticable {
(otherData.indicatorColor == indicatorColor) &&
(otherData.hintColor == hintColor) &&
(otherData.errorColor == errorColor) &&
(otherData.toggleableActiveColor == toggleableActiveColor) &&
(otherData.textTheme == textTheme) &&
(otherData.primaryTextTheme == primaryTextTheme) &&
(otherData.accentTextTheme == accentTextTheme) &&
......@@ -843,7 +892,8 @@ class ThemeData extends Diagnosticable {
(otherData.chipTheme == chipTheme) &&
(otherData.platform == platform) &&
(otherData.materialTapTargetSize == materialTapTargetSize) &&
(otherData.pageTransitionsTheme == pageTransitionsTheme);
(otherData.pageTransitionsTheme == pageTransitionsTheme) &&
(otherData.colorScheme == colorScheme);
}
@override
......@@ -870,9 +920,11 @@ class ThemeData extends Diagnosticable {
splashFactory,
selectedRowColor,
unselectedWidgetColor,
buttonColor,
disabledColor,
buttonTheme,
hashValues(
buttonColor,
toggleableActiveColor,
secondaryHeaderColor,
textSelectionColor,
cursorColor,
......@@ -882,7 +934,6 @@ class ThemeData extends Diagnosticable {
indicatorColor,
hintColor,
errorColor,
toggleableActiveColor,
textTheme,
primaryTextTheme,
accentTextTheme,
......@@ -891,12 +942,13 @@ class ThemeData extends Diagnosticable {
primaryIconTheme,
accentIconTheme,
sliderTheme,
tabBarTheme,
hashValues(
tabBarTheme,
chipTheme,
platform,
materialTapTargetSize,
pageTransitionsTheme,
colorScheme,
),
),
);
......@@ -946,6 +998,7 @@ class ThemeData extends Diagnosticable {
properties.add(DiagnosticsProperty<ChipThemeData>('chipTheme', chipTheme));
properties.add(DiagnosticsProperty<MaterialTapTargetSize>('materialTapTargetSize', materialTapTargetSize));
properties.add(DiagnosticsProperty<PageTransitionsTheme>('pageTransitionsTheme', pageTransitionsTheme));
properties.add(DiagnosticsProperty<ColorScheme>('colorScheme', colorScheme, defaultValue: defaultData.colorScheme));
}
}
......
......@@ -107,10 +107,12 @@ void main() {
EdgeInsets padding;
ShapeBorder shape;
const Color disabledColor = Color(0xFF00FF00);
await tester.pumpWidget(
Theme(
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(
button: lightTheme.textTheme.button.copyWith(
// The button's height will match because there's no
......@@ -149,7 +151,7 @@ void main() {
));
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));
});
......@@ -169,6 +171,7 @@ void main() {
minWidth: 100.0,
height: 200.0,
padding: EdgeInsets.zero,
buttonColor: const Color(0xFF00FF00), // enabled RaisedButton fill color
shape: const RoundedRectangleBorder(),
child: Builder(
builder: (BuildContext context) {
......
......@@ -89,7 +89,7 @@ void main() {
// Expect that the button is disabled and painted with the disabled border color.
expect(tester.widget<OutlineButton>(outlineButton).enabled, false);
expect(
outlineButton,
outlineButton, //find.byType(OutlineButton),
paints
..clipPath(pathMatcher: coversSameAreaAs(clipPath, areaToCompare: clipRect.inflate(10.0)))
..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