button.dart 8.65 KB
Newer Older
1 2 3 4
// 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.

5
import 'package:flutter/widgets.dart';
6
import 'package:meta/meta.dart';
7

8
import 'colors.dart';
9
import 'constants.dart';
10
import 'debug.dart';
11
import 'flat_button.dart';
12 13
import 'icon_theme_data.dart';
import 'icon_theme.dart';
14 15
import 'ink_well.dart';
import 'material.dart';
16
import 'raised_button.dart';
17
import 'theme.dart';
18

19 20 21 22 23 24 25
/// Whether a button should use the accent color for its text.
///
/// See also:
///
///  * [ButtonTheme]
///  * [RaisedButton]
///  * [FlatButton]
26
enum ButtonTextTheme {
27 28 29 30 31 32
  /// The button should use the normal color (e.g., black or white depending on the [ThemeData.brightness]) for its text.
  normal,

  /// The button should use the accent color (e.g., [ThemeData.accentColor]) for its text.
  accent,
}
33

34 35 36 37
/// Defines the button color used by a widget subtree.
///
/// See also:
///
38
///  * [ButtonTextTheme]
39 40
///  * [RaisedButton]
///  * [FlatButton]
41
class ButtonTheme extends InheritedWidget {
42 43 44 45
  /// Creates a button theme.
  ///
  /// The child argument is required.
  const ButtonTheme({
46
    Key key,
47 48 49 50
    this.textTheme: ButtonTextTheme.normal,
    this.minWidth: 88.0,
    this.height: 36.0,
    this.padding: const EdgeInsets.symmetric(horizontal: 16.0),
51
    Widget child
52 53
  }) : super(key: key, child: child);

54 55
  /// Creates a button theme that is appropriate for button bars, as used in
  /// dialog footers and in the headers of data tables.
56 57 58 59 60
  ///
  /// This theme is denser, with a smaller [minWidth] and [padding], than the
  /// default theme. Also, this theme uses [ButtonTextTheme.accent] rather than
  /// [ButtonTextTheme.normal].
  ///
61 62 63 64 65
  /// For best effect, the label of the button at the edge of the container
  /// should have text that ends up wider than 64.0 pixels. This ensures that
  /// the alignment of the text matches the alignment of the edge of the
  /// container.
  ///
66 67
  /// For example, buttons at the bottom of [Dialog] or [Card] widgets use this
  /// button theme.
68
  const ButtonTheme.bar({
69 70 71 72 73 74 75
    Key key,
    this.textTheme: ButtonTextTheme.accent,
    this.minWidth: 64.0,
    this.height: 36.0,
    this.padding: const EdgeInsets.symmetric(horizontal: 8.0),
    Widget child
  }) : super(key: key, child: child);
76

77
  /// The button color that this subtree should use.
78
  final ButtonTextTheme textTheme;
79

80 81 82 83 84 85
  /// The smallest horizontal extent that the button will occupy.
  ///
  /// Defaults to 88.0 logical pixels.
  final double minWidth;

  /// The vertical extent of the button.
86
  ///
87 88 89 90 91 92 93 94 95
  /// Defaults to 36.0 logical pixels.
  final double height;

  /// The amount of space to surround the child inside the bounds of the button.
  ///
  /// Defaults to 16.0 pixels of horizontal padding.
  final EdgeInsets padding;

  /// The color from the closest instance of this class that encloses the given context.
96 97 98 99 100 101
  ///
  /// Typical usage is as follows:
  ///
  /// ```dart
  /// ButtonTheme theme = ButtonTheme.of(context);
  /// ```
102
  static ButtonTheme of(BuildContext context) {
Ian Hickson's avatar
Ian Hickson committed
103
    ButtonTheme result = context.inheritFromWidgetOfExactType(ButtonTheme);
104
    return result ?? const ButtonTheme();
105 106
  }

107
  @override
108 109 110 111 112 113
  bool updateShouldNotify(ButtonTheme oldTheme) {
    return textTheme != oldTheme.textTheme
        || padding != oldTheme.padding
        || minWidth != oldTheme.minWidth
        || height != oldTheme.height;
  }
114 115
}

116
/// A material design button.
117
///
118
/// Rather than using this class directly, consider using [FlatButton] or [RaisedButton].
119 120 121
///
/// MaterialButtons whose [onPressed] handler is null will be disabled. To have
/// an enabled button, make sure to pass a non-null value for onPressed.
122 123 124 125 126
class MaterialButton extends StatefulWidget {
  /// Creates a material button.
  ///
  /// Rather than creating a material button directly, consider using
  /// [FlatButton] or [RaisedButton].
127
  MaterialButton({
128
    Key key,
129
    this.colorBrightness,
130
    this.textTheme,
131
    this.textColor,
132 133 134 135 136 137
    this.color,
    this.elevation,
    this.highlightElevation,
    this.minWidth,
    this.height,
    this.padding,
138
    @required this.onPressed,
139
    this.child
140
  }) : super(key: key);
141

142 143 144
  /// The theme brightness to use for this button.
  ///
  /// Defaults to the brightness from [ThemeData.brightness].
145
  final Brightness colorBrightness;
146 147 148 149

  /// The color scheme to use for this button's text.
  ///
  /// Defaults to the button color from [ButtonTheme].
150
  final ButtonTextTheme textTheme;
151

152
  /// The color to use for this button's text.
153
  final Color textColor;
154

155 156 157 158
  /// The color of the button, as printed on the [Material].
  final Color color;

  /// The z-coordinate at which to place this button.
159
  ///
160
  /// The following elevations have defined shadows: 1, 2, 3, 4, 6, 8, 9, 12, 16, 24
161 162
  ///
  /// Defaults to 0.
163 164 165 166 167
  final int elevation;

  /// The z-coordinate at which to place this button when highlighted.
  ///
  /// The following elevations have defined shadows: 1, 2, 3, 4, 6, 8, 9, 12, 16, 24
168 169
  ///
  /// Defaults to 0.
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
  final int 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 amount of space to surround the child inside the bounds of the button.
  ///
  /// Defaults to the value from the current [ButtonTheme].
  final EdgeInsets padding;
186

187
  /// The callback that is called when the button is tapped or otherwise activated.
188 189
  ///
  /// If this is set to null, the button will be disabled.
190
  final VoidCallback onPressed;
Hixie's avatar
Hixie committed
191

192 193 194
  /// The widget below this widget in the tree.
  final Widget child;

195 196
  /// 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.
197 198
  bool get enabled => onPressed != null;

199 200 201
  @override
  _MaterialButtonState createState() => new _MaterialButtonState();

202
  @override
Hixie's avatar
Hixie committed
203 204 205 206 207
  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
    if (!enabled)
      description.add('disabled');
  }
208 209
}

210 211
class _MaterialButtonState extends State<MaterialButton> {
  bool _highlight = false;
212

213
  Brightness get _colorBrightness {
214 215 216
    return config.colorBrightness ?? Theme.of(context).brightness;
  }

217 218 219
  Color get _textColor {
    if (config.textColor != null)
      return config.textColor;
220
    if (config.enabled) {
221 222
      switch (config.textTheme ?? ButtonTheme.of(context).textTheme) {
        case ButtonTextTheme.accent:
223
          return Theme.of(context).accentColor;
224 225
        case ButtonTextTheme.normal:
          switch (_colorBrightness) {
226
            case Brightness.light:
227
              return Colors.black87;
228
            case Brightness.dark:
229 230 231
              return Colors.white;
          }
      }
232 233
    } else {
      switch (_colorBrightness) {
234
        case Brightness.light:
235
          return Colors.black26;
236
        case Brightness.dark:
237 238
          return Colors.white30;
      }
239
    }
pq's avatar
pq committed
240
    assert(_colorBrightness != null);
pq's avatar
pq committed
241
    return null;
242 243
  }

244 245
  void _handleHighlightChanged(bool value) {
    setState(() {
246
      _highlight = value;
247 248 249
    });
  }

250
  @override
251
  Widget build(BuildContext context) {
252
    assert(debugCheckHasMaterial(context));
253 254 255 256 257
    final Color textColor = _textColor;
    final TextStyle style = Theme.of(context).textTheme.button.copyWith(color: textColor);
    final ButtonTheme buttonTheme = ButtonTheme.of(context);
    final double height = config.height ?? buttonTheme.height;
    final int elevation = (_highlight ? config.highlightElevation : config.elevation) ?? 0;
Ian Hickson's avatar
Ian Hickson committed
258 259
    Widget contents = new IconTheme.merge(
      context: context,
260 261 262 263 264 265 266
      data: new IconThemeData(
        color: textColor
      ),
      child: new InkWell(
        onTap: config.onPressed,
        onHighlightChanged: _handleHighlightChanged,
        child: new Container(
267
          padding: config.padding ?? ButtonTheme.of(context).padding,
268 269 270 271
          child: new Center(
            widthFactor: 1.0,
            child: config.child
          )
272
        )
273
      )
274
    );
275
    if (elevation > 0 || config.color != null) {
276 277
      contents = new Material(
        type: MaterialType.button,
278
        color: config.color,
279 280 281 282 283
        elevation: elevation,
        textStyle: style,
        child: contents
      );
    } else {
284
      contents = new AnimatedDefaultTextStyle(
285
        style: style,
286
        duration: kThemeChangeDuration,
287 288 289
        child: contents
      );
    }
290 291 292 293 294 295
    return new ConstrainedBox(
      constraints: new BoxConstraints(
        minWidth: config.minWidth ?? buttonTheme.minWidth,
        minHeight: height,
        maxHeight: height
      ),
296
      child: contents
297 298 299
    );
  }
}