| // Copyright 2015 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/widgets.dart'; |
| |
| import 'button_theme.dart'; |
| import 'colors.dart'; |
| import 'constants.dart'; |
| import 'ink_well.dart'; |
| import 'material.dart'; |
| import 'theme.dart'; |
| |
| /// Creates a button based on [Semantics], [Material], and [InkWell] |
| /// widgets. |
| /// |
| /// This class does not use the current [Theme] or [ButtonTheme] to |
| /// compute default values for unspecified parameters. It's intended to |
| /// be used for custom Material buttons that optionally incorporate defaults |
| /// from the themes or from app-specific sources. |
| /// |
| /// [RaisedButton] and [FlatButton] configure a [RawMaterialButton] based |
| /// on the current [Theme] and [ButtonTheme]. |
| class RawMaterialButton extends StatefulWidget { |
| /// Create a button based on [Semantics], [Material], and [InkWell] widgets. |
| /// |
| /// The [shape], [elevation], [padding], and [constraints] arguments |
| /// must not be null. |
| const RawMaterialButton({ |
| Key key, |
| @required this.onPressed, |
| this.onHighlightChanged, |
| this.textStyle, |
| this.fillColor, |
| this.highlightColor, |
| this.splashColor, |
| this.elevation = 2.0, |
| this.highlightElevation = 8.0, |
| this.disabledElevation = 0.0, |
| this.outerPadding, |
| this.padding = EdgeInsets.zero, |
| this.constraints = const BoxConstraints(minWidth: 88.0, minHeight: 36.0), |
| this.shape = const RoundedRectangleBorder(), |
| this.animationDuration = kThemeChangeDuration, |
| this.child, |
| }) : assert(shape != null), |
| assert(elevation != null), |
| assert(highlightElevation != null), |
| assert(disabledElevation != null), |
| assert(padding != null), |
| assert(constraints != null), |
| assert(animationDuration != null), |
| 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; |
| |
| /// Padding to increase the size of the gesture detector which doesn't |
| /// increase the visible material of the button. |
| final EdgeInsets outerPadding; |
| |
| /// Called by the underlying [InkWell] widget's [InkWell.onHighlightChanged] |
| /// callback. |
| final ValueChanged<bool> onHighlightChanged; |
| |
| /// Defines the default text style, with [Material.textStyle], for the |
| /// button's [child]. |
| final TextStyle textStyle; |
| |
| /// The color of the button's [Material]. |
| final Color fillColor; |
| |
| /// The highlight color for the button's [InkWell]. |
| final Color highlightColor; |
| |
| /// The splash color for the button's [InkWell]. |
| final Color splashColor; |
| |
| /// 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. |
| final double elevation; |
| |
| /// The elevation for the button's [Material] when the button |
| /// is [enabled] and pressed. |
| /// |
| /// 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. |
| /// |
| /// * [elevation], the default elevation. |
| /// * [highlightElevation], the elevation when the button is pressed. |
| final double disabledElevation; |
| |
| /// The internal padding for the button's [child]. |
| final EdgeInsetsGeometry padding; |
| |
| /// Defines the button's size. |
| /// |
| /// Typically used to constrain the button's minimum size. |
| final BoxConstraints constraints; |
| |
| /// 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. |
| final ShapeBorder shape; |
| |
| /// Defines the duration of animated changes for [shape] and [elevation]. |
| /// |
| /// The default value is [kThemeChangeDuration]. |
| final Duration animationDuration; |
| |
| /// Typically the button's label. |
| 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; |
| |
| @override |
| _RawMaterialButtonState createState() => new _RawMaterialButtonState(); |
| } |
| |
| class _RawMaterialButtonState extends State<RawMaterialButton> { |
| bool _highlight = false; |
| void _handleHighlightChanged(bool value) { |
| setState(() { |
| _highlight = value; |
| if (widget.onHighlightChanged != null) |
| widget.onHighlightChanged(value); |
| }); |
| } |
| |
| @override |
| Widget build(BuildContext context) { |
| final double elevation = widget.enabled |
| ? (_highlight ? widget.highlightElevation : widget.elevation) |
| : widget.disabledElevation; |
| |
| Widget result = new ConstrainedBox( |
| constraints: widget.constraints, |
| child: new Material( |
| elevation: elevation, |
| textStyle: widget.textStyle, |
| shape: widget.shape, |
| color: widget.fillColor, |
| type: widget.fillColor == null ? MaterialType.transparency : MaterialType.button, |
| animationDuration: widget.animationDuration, |
| child: new InkWell( |
| onHighlightChanged: _handleHighlightChanged, |
| splashColor: widget.splashColor, |
| highlightColor: widget.highlightColor, |
| onTap: widget.onPressed, |
| child: IconTheme.merge( |
| data: new IconThemeData(color: widget.textStyle?.color), |
| child: new Container( |
| padding: widget.padding, |
| child: new Center( |
| widthFactor: 1.0, |
| heightFactor: 1.0, |
| child: widget.child, |
| ), |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| if (widget.outerPadding != null) { |
| result = new GestureDetector( |
| behavior: HitTestBehavior.translucent, |
| excludeFromSemantics: true, |
| onTap: widget.onPressed, |
| child: new Padding( |
| padding: widget.outerPadding, |
| child: result |
| ), |
| ); |
| } |
| |
| return new Semantics( |
| container: true, |
| button: true, |
| enabled: widget.enabled, |
| child: result, |
| ); |
| } |
| } |
| |
| /// 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]. |
| 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, |
| @required this.onPressed, |
| this.child |
| }) : 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 |
| /// new MaterialButton( |
| /// color: Colors.blue[500], |
| /// onPressed: _handleTap, |
| /// child: new 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; |
| |
| /// 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) |
| : (themeIsDark ? Colors.white30 : Colors.black26); |
| case ButtonTextTheme.accent: |
| return enabled |
| ? theme.accentColor |
| : (themeIsDark ? Colors.white30 : Colors.black26); |
| 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 new 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, |
| ); |
| } |
| |
| @override |
| void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
| super.debugFillProperties(properties); |
| properties.add(new FlagProperty('enabled', value: enabled, ifFalse: 'disabled')); |
| } |
| } |