material_button.dart 17.2 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
import 'dart:ui';

7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
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].
///
22 23 24 25 26 27 28 29 30
/// ### This class is obsolete.
///
/// FlatButton, RaisedButton, and OutlineButton have been replaced by
/// TextButton, ElevatedButton, and OutlinedButton respectively.
/// ButtonTheme has been replaced by TextButtonTheme,
/// ElevatedButtonTheme, and OutlinedButtonTheme. The appearance of the
/// new widgets can be customized by specifying a [ButtonStyle]
/// or by creating a one-off style using a `styleFrom` method like
/// [TextButton.styleFrom]. The original button classes
31
/// have been deprecated, please migrate code that uses them.
32 33 34 35
/// There's a detailed migration guide for the new button and button
/// theme classes in
/// [flutter.dev/go/material-button-migration-guide](https://flutter.dev/go/material-button-migration-guide).
///
36 37
/// The button's size will expand to fit the child widget, if necessary.
///
38 39
/// MaterialButtons whose [onPressed] and [onLongPress] callbacks are null will be disabled. To have
/// an enabled button, make sure to pass a non-null value for [onPressed] or [onLongPress].
40 41
///
/// Rather than using this class directly, consider using [FlatButton],
42
/// [OutlineButton], or [RaisedButton], which configure this class with
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
/// 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].
60 61 62 63 64
  ///
  /// The [autofocus] and [clipBehavior] arguments must not be null.
  /// Additionally,  [elevation], [hoverElevation], [focusElevation],
  /// [highlightElevation], and [disabledElevation] must be non-negative, if
  /// specified.
65
  const MaterialButton({
66 67
    Key? key,
    required this.onPressed,
68
    this.onLongPress,
69
    this.onHighlightChanged,
70
    this.mouseCursor,
71 72 73 74 75
    this.textTheme,
    this.textColor,
    this.disabledTextColor,
    this.color,
    this.disabledColor,
76 77
    this.focusColor,
    this.hoverColor,
78 79 80 81
    this.highlightColor,
    this.splashColor,
    this.colorBrightness,
    this.elevation,
82 83
    this.focusElevation,
    this.hoverElevation,
84 85 86
    this.highlightElevation,
    this.disabledElevation,
    this.padding,
87
    this.visualDensity,
88 89
    this.shape,
    this.clipBehavior = Clip.none,
90
    this.focusNode,
91
    this.autofocus = false,
92 93 94 95
    this.materialTapTargetSize,
    this.animationDuration,
    this.minWidth,
    this.height,
96
    this.enableFeedback = true,
97
    this.child,
98 99
  }) : assert(clipBehavior != null),
       assert(autofocus != null),
100 101 102 103 104 105
       assert(elevation == null || elevation >= 0.0),
       assert(focusElevation == null || focusElevation >= 0.0),
       assert(hoverElevation == null || hoverElevation >= 0.0),
       assert(highlightElevation == null || highlightElevation >= 0.0),
       assert(disabledElevation == null || disabledElevation >= 0.0),
       super(key: key);
106 107 108

  /// The callback that is called when the button is tapped or otherwise activated.
  ///
109 110 111 112 113
  /// If this callback and [onLongPress] are null, then the button will be disabled.
  ///
  /// See also:
  ///
  ///  * [enabled], which is true if the button is enabled.
114
  final VoidCallback? onPressed;
115

116 117 118 119 120 121 122
  /// The callback that is called when the button is long-pressed.
  ///
  /// If this callback and [onPressed] are null, then the button will be disabled.
  ///
  /// See also:
  ///
  ///  * [enabled], which is true if the button is enabled.
123
  final VoidCallback? onLongPress;
124

125 126
  /// Called by the underlying [InkWell] widget's [InkWell.onHighlightChanged]
  /// callback.
127 128 129 130
  ///
  /// If [onPressed] changes from null to non-null while a gesture is ongoing,
  /// this can fire during the build phase (in which case calling
  /// [State.setState] is not allowed).
131
  final ValueChanged<bool>? onHighlightChanged;
132

133
  /// {@macro flutter.material.RawMaterialButton.mouseCursor}
134
  final MouseCursor? mouseCursor;
135

136 137 138 139
  /// 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`.
140
  final ButtonTextTheme? textTheme;
141 142 143

  /// The color to use for this button's text.
  ///
144 145 146
  /// The button's [Material.textStyle] will be the current theme's button text
  /// style, [TextTheme.button] of [ThemeData.textTheme], configured with this
  /// color.
147 148 149 150
  ///
  /// The default text color depends on the button theme's text theme,
  /// [ButtonThemeData.textTheme].
  ///
151 152
  /// If [textColor] is a [MaterialStateProperty<Color>], [disabledTextColor]
  /// will be ignored.
153
  ///
154
  /// See also:
155 156 157
  ///
  ///  * [disabledTextColor], the text color to use when the button has been
  ///    disabled.
158
  final Color? textColor;
159 160 161

  /// The color to use for this button's text when the button is disabled.
  ///
162 163 164
  /// The button's [Material.textStyle] will be the current theme's button text
  /// style, [TextTheme.button] of [ThemeData.textTheme], configured with this
  /// color.
165 166 167 168
  ///
  /// The default value is the theme's disabled color,
  /// [ThemeData.disabledColor].
  ///
169 170
  /// If [textColor] is a [MaterialStateProperty<Color>], [disabledTextColor]
  /// will be ignored.
171
  ///
172
  /// See also:
173
  ///
174
  ///  * [textColor] - The color to use for this button's text when the button is [enabled].
175
  final Color? disabledTextColor;
176 177 178 179 180

  /// The button's fill color, displayed by its [Material], while it
  /// is in its default (unpressed, [enabled]) state.
  ///
  /// See also:
181 182
  ///
  ///  * [disabledColor] - the fill color of the button when the button is disabled.
183
  final Color? color;
184 185 186 187 188 189 190

  /// 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:
191 192
  ///
  ///  * [color] - the fill color of the button when the button is [enabled].
193
  final Color? disabledColor;
194 195 196 197 198 199 200 201 202 203 204 205

  /// 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].
206
  final Color? splashColor;
207

208 209 210 211
  /// The fill color of the button's [Material] when it has the input focus.
  ///
  /// The button changed focus color when the button has the input focus. It
  /// appears behind the button's child.
212
  final Color? focusColor;
213 214 215 216 217 218

  /// The fill color of the button's [Material] when a pointer is hovering over
  /// it.
  ///
  /// The button changes fill color when a pointer is hovering over the button.
  /// It appears behind the button's child.
219
  final Color? hoverColor;
220

221 222 223 224 225 226 227 228 229
  /// 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].
230
  final Color? highlightColor;
231

232 233 234
  /// The z-coordinate at which to place this button relative to its parent.
  ///
  /// This controls the size of the shadow below the raised button.
235
  ///
236 237
  /// Defaults to 2, the appropriate elevation for raised buttons. The value
  /// is always non-negative.
238 239 240 241
  ///
  /// See also:
  ///
  ///  * [FlatButton], a button with no elevation or fill color.
242 243 244
  ///  * [focusElevation], the elevation when the button is focused.
  ///  * [hoverElevation], the elevation when a pointer is hovering over the
  ///    button.
245 246
  ///  * [disabledElevation], the elevation when the button is disabled.
  ///  * [highlightElevation], the elevation when the button is pressed.
247
  final double? elevation;
248

249 250 251 252 253 254 255 256 257 258 259
  /// The elevation for the button's [Material] when the button
  /// is [enabled] and a pointer is hovering over it.
  ///
  /// Defaults to 4.0. The value is always non-negative.
  ///
  /// See also:
  ///
  ///  * [elevation], the default elevation.
  ///  * [focusElevation], the elevation when the button is focused.
  ///  * [disabledElevation], the elevation when the button is disabled.
  ///  * [highlightElevation], the elevation when the button is pressed.
260
  final double? hoverElevation;
261 262 263 264 265 266 267 268 269 270 271 272 273

  /// The elevation for the button's [Material] when the button
  /// is [enabled] and has the input focus.
  ///
  /// Defaults to 4.0. The value is always non-negative.
  ///
  /// See also:
  ///
  ///  * [elevation], the default elevation.
  ///  * [hoverElevation], the elevation when a pointer is hovering over the
  ///    button.
  ///  * [disabledElevation], the elevation when the button is disabled.
  ///  * [highlightElevation], the elevation when the button is pressed.
274
  final double? focusElevation;
275

276 277
  /// The elevation for the button's [Material] relative to its parent when the
  /// button is [enabled] and pressed.
278 279 280 281 282
  ///
  /// 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".
  ///
283
  /// Defaults to 8.0. The value is always non-negative.
284 285 286 287
  ///
  /// See also:
  ///
  ///  * [elevation], the default elevation.
288 289 290
  ///  * [focusElevation], the elevation when the button is focused.
  ///  * [hoverElevation], the elevation when a pointer is hovering over the
  ///    button.
291
  ///  * [disabledElevation], the elevation when the button is disabled.
292
  final double? highlightElevation;
293

294 295
  /// The elevation for the button's [Material] relative to its parent when the
  /// button is not [enabled].
296
  ///
297
  /// Defaults to 0.0. The value is always non-negative.
298 299 300 301 302
  ///
  /// See also:
  ///
  ///  * [elevation], the default elevation.
  ///  * [highlightElevation], the elevation when the button is pressed.
303
  final double? disabledElevation;
304 305 306

  /// The theme brightness to use for this button.
  ///
307 308 309 310 311 312 313
  /// Defaults to the theme's brightness in [ThemeData.brightness]. Setting
  /// this value determines the button text's colors based on
  /// [ButtonThemeData.getTextColor].
  ///
  /// See also:
  ///
  ///  * [ButtonTextTheme], uses [Brightness] to determine text color.
314
  final Brightness? colorBrightness;
315 316 317 318

  /// The button's label.
  ///
  /// Often a [Text] widget in all caps.
319
  final Widget? child;
320 321 322 323

  /// Whether the button is enabled or disabled.
  ///
  /// Buttons are disabled by default. To enable a button, set its [onPressed]
324 325
  /// or [onLongPress] properties to a non-null value.
  bool get enabled => onPressed != null || onLongPress != null;
326 327 328 329 330

  /// The internal padding for the button's [child].
  ///
  /// Defaults to the value from the current [ButtonTheme],
  /// [ButtonThemeData.padding].
331
  final EdgeInsetsGeometry? padding;
332

333 334 335 336 337 338
  /// Defines how compact the button's layout will be.
  ///
  /// {@macro flutter.material.themedata.visualDensity}
  ///
  /// See also:
  ///
339 340
  ///  * [ThemeData.visualDensity], which specifies the [visualDensity] for all
  ///    widgets within a [Theme].
341
  final VisualDensity? visualDensity;
342

343 344 345 346 347
  /// 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.
348 349 350
  ///
  /// Defaults to the value from the current [ButtonTheme],
  /// [ButtonThemeData.shape].
351
  final ShapeBorder? shape;
352

353
  /// {@macro flutter.material.Material.clipBehavior}
354 355
  ///
  /// Defaults to [Clip.none], and must not be null.
356 357
  final Clip clipBehavior;

358
  /// {@macro flutter.widgets.Focus.focusNode}
359
  final FocusNode? focusNode;
360

361 362 363
  /// {@macro flutter.widgets.Focus.autofocus}
  final bool autofocus;

364 365 366
  /// Defines the duration of animated changes for [shape] and [elevation].
  ///
  /// The default value is [kThemeChangeDuration].
367
  final Duration? animationDuration;
368 369 370 371 372 373 374

  /// Configures the minimum size of the tap target.
  ///
  /// Defaults to [ThemeData.materialTapTargetSize].
  ///
  /// See also:
  ///
375
  ///  * [MaterialTapTargetSize], for a description of how this affects tap targets.
376
  final MaterialTapTargetSize? materialTapTargetSize;
377 378 379 380

  /// The smallest horizontal extent that the button will occupy.
  ///
  /// Defaults to the value from the current [ButtonTheme].
381
  final double? minWidth;
382 383 384 385

  /// The vertical extent of the button.
  ///
  /// Defaults to the value from the current [ButtonTheme].
386
  final double? height;
387

388 389 390 391 392 393 394 395 396 397
  /// Whether detected gestures should provide acoustic and/or haptic feedback.
  ///
  /// For example, on Android a tap will produce a clicking sound and a
  /// long-press will produce a short vibration, when feedback is enabled.
  ///
  /// See also:
  ///
  ///  * [Feedback] for providing platform-specific feedback to certain actions.
  final bool enableFeedback;

398 399
  @override
  Widget build(BuildContext context) {
400
    final ThemeData theme = Theme.of(context);
401 402 403 404
    final ButtonThemeData buttonTheme = ButtonTheme.of(context);

    return RawMaterialButton(
      onPressed: onPressed,
405
      onLongPress: onLongPress,
406
      enableFeedback: enableFeedback,
407
      onHighlightChanged: onHighlightChanged,
408
      mouseCursor: mouseCursor,
409
      fillColor: buttonTheme.getFillColor(this),
410 411 412
      textStyle: theme.textTheme.button!.copyWith(color: buttonTheme.getTextColor(this)),
      focusColor: focusColor ?? buttonTheme.getFocusColor(this),
      hoverColor: hoverColor ?? buttonTheme.getHoverColor(this),
413 414 415
      highlightColor: highlightColor ?? theme.highlightColor,
      splashColor: splashColor ?? theme.splashColor,
      elevation: buttonTheme.getElevation(this),
416 417
      focusElevation: buttonTheme.getFocusElevation(this),
      hoverElevation: buttonTheme.getHoverElevation(this),
418 419
      highlightElevation: buttonTheme.getHighlightElevation(this),
      padding: buttonTheme.getPadding(this),
420
      visualDensity: visualDensity ?? theme.visualDensity,
421 422 423 424
      constraints: buttonTheme.getConstraints(this).copyWith(
        minWidth: minWidth,
        minHeight: height,
      ),
425
      shape: buttonTheme.getShape(this),
426
      clipBehavior: clipBehavior,
427
      focusNode: focusNode,
428
      autofocus: autofocus,
429 430
      animationDuration: buttonTheme.getAnimationDuration(this),
      materialTapTargetSize: materialTapTargetSize ?? theme.materialTapTargetSize,
431
      disabledElevation: disabledElevation ?? 0.0,
432
      child: child,
433 434 435 436 437 438
    );
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
439
    properties.add(FlagProperty('enabled', value: enabled, ifFalse: 'disabled'));
440
    properties.add(DiagnosticsProperty<ButtonTextTheme>('textTheme', textTheme, defaultValue: null));
441 442 443 444 445 446 447 448
    properties.add(ColorProperty('textColor', textColor, defaultValue: null));
    properties.add(ColorProperty('disabledTextColor', disabledTextColor, defaultValue: null));
    properties.add(ColorProperty('color', color, defaultValue: null));
    properties.add(ColorProperty('disabledColor', disabledColor, defaultValue: null));
    properties.add(ColorProperty('focusColor', focusColor, defaultValue: null));
    properties.add(ColorProperty('hoverColor', hoverColor, defaultValue: null));
    properties.add(ColorProperty('highlightColor', highlightColor, defaultValue: null));
    properties.add(ColorProperty('splashColor', splashColor, defaultValue: null));
449 450
    properties.add(DiagnosticsProperty<Brightness>('colorBrightness', colorBrightness, defaultValue: null));
    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
451
    properties.add(DiagnosticsProperty<VisualDensity>('visualDensity', visualDensity, defaultValue: null));
452 453 454
    properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
    properties.add(DiagnosticsProperty<FocusNode>('focusNode', focusNode, defaultValue: null));
    properties.add(DiagnosticsProperty<MaterialTapTargetSize>('materialTapTargetSize', materialTapTargetSize, defaultValue: null));
455 456 457 458 459 460 461 462
  }
}

/// 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].
463
mixin MaterialButtonWithIconMixin { }