floating_action_button.dart 26.8 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:math' as math;

7
import 'package:flutter/foundation.dart';
8
import 'package:flutter/rendering.dart';
9
import 'package:flutter/widgets.dart';
10

11
import 'button.dart';
12
import 'floating_action_button_theme.dart';
13
import 'scaffold.dart';
14
import 'theme.dart';
15
import 'theme_data.dart';
Adam Barth's avatar
Adam Barth committed
16
import 'tooltip.dart';
17

18
const BoxConstraints _kSizeConstraints = BoxConstraints.tightFor(
19 20 21 22
  width: 56.0,
  height: 56.0,
);

23
const BoxConstraints _kMiniSizeConstraints = BoxConstraints.tightFor(
24 25 26 27
  width: 40.0,
  height: 40.0,
);

28 29 30 31 32 33 34
const BoxConstraints _kLargeSizeConstraints = BoxConstraints.tightFor(
  width: 96.0,
  height: 96.0,
);

const BoxConstraints _kExtendedSizeConstraints = BoxConstraints.tightFor(
  height: 48.0,
35
);
36 37 38 39 40 41

class _DefaultHeroTag {
  const _DefaultHeroTag();
  @override
  String toString() => '<default FloatingActionButton tag>';
}
42

43 44 45 46 47 48 49
enum _FloatingActionButtonType {
  regular,
  small,
  large,
  extended,
}

50
/// A material design floating action button.
51 52
///
/// A floating action button is a circular icon button that hovers over content
53 54
/// to promote a primary action in the application. Floating action buttons are
/// most commonly used in the [Scaffold.floatingActionButton] field.
55
///
56 57
/// {@youtube 560 315 https://www.youtube.com/watch?v=2uaoEDOgk_I}
///
58 59
/// Use at most a single floating action button per screen. Floating action
/// buttons should be used for positive actions such as "create", "share", or
60 61 62
/// "navigate". (If more than one floating action button is used within a
/// [Route], then make sure that each button has a unique [heroTag], otherwise
/// an exception will be thrown.)
63
///
64
/// If the [onPressed] callback is null, then the button will be disabled and
65 66 67 68
/// will not react to touch. It is highly discouraged to disable a floating
/// action button as there is no indication to the user that the button is
/// disabled. Consider changing the [backgroundColor] if disabling the floating
/// action button.
69
///
70
/// {@tool dartpad}
71
/// This example shows how to display a [FloatingActionButton] in a
72 73
/// [Scaffold], with a pink [backgroundColor] and a thumbs up [Icon].
///
74
/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/floating_action_button.png)
75
///
76
/// ** See code in examples/api/lib/material/floating_action_button/floating_action_button.0.dart **
77 78
/// {@end-tool}
///
79
/// {@tool dartpad}
80
/// This example shows how to make an extended [FloatingActionButton] in a
81
/// [Scaffold], with a  pink [backgroundColor], a thumbs up [Icon] and a
82
/// [Text] label that reads "Approve".
83
///
84
/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/floating_action_button_label.png)
85
///
86
/// ** See code in examples/api/lib/material/floating_action_button/floating_action_button.1.dart **
87 88
/// {@end-tool}
///
89 90
/// See also:
///
91
///  * [Scaffold], in which floating action buttons typically live.
92
///  * [ElevatedButton], a filled button whose material elevates when pressed.
93
///  * <https://material.io/design/components/buttons-floating-action-button.html>
94
class FloatingActionButton extends StatelessWidget {
95
  /// Creates a circular floating action button.
96
  ///
97
  /// The [mini] and [clipBehavior] arguments must not be null. Additionally,
98 99
  /// [elevation], [highlightElevation], and [disabledElevation] (if specified)
  /// must be non-negative.
100
  const FloatingActionButton({
101
    Key? key,
102
    this.child,
Adam Barth's avatar
Adam Barth committed
103
    this.tooltip,
104
    this.foregroundColor,
105
    this.backgroundColor,
106 107
    this.focusColor,
    this.hoverColor,
108
    this.splashColor,
109
    this.heroTag = const _DefaultHeroTag(),
110
    this.elevation,
111 112
    this.focusElevation,
    this.hoverElevation,
113 114
    this.highlightElevation,
    this.disabledElevation,
115
    required this.onPressed,
116
    this.mouseCursor,
117
    this.mini = false,
118
    this.shape,
119
    this.clipBehavior = Clip.none,
120
    this.focusNode,
121
    this.autofocus = false,
122
    this.materialTapTargetSize,
123
    this.isExtended = false,
124
    this.enableFeedback,
125
  }) : assert(elevation == null || elevation >= 0.0),
126 127
       assert(focusElevation == null || focusElevation >= 0.0),
       assert(hoverElevation == null || hoverElevation >= 0.0),
128
       assert(highlightElevation == null || highlightElevation >= 0.0),
129 130
       assert(disabledElevation == null || disabledElevation >= 0.0),
       assert(mini != null),
131
       assert(clipBehavior != null),
132
       assert(isExtended != null),
133
       assert(autofocus != null),
134 135 136
       _floatingActionButtonType = mini ? _FloatingActionButtonType.small : _FloatingActionButtonType.regular,
       _extendedLabel = null,
       extendedIconLabelSpacing = null,
137
       extendedPadding = null,
138
       extendedTextStyle = null,
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
       super(key: key);

  /// Creates a small circular floating action button.
  ///
  /// This constructor overrides the default size constraints of the floating
  /// action button.
  ///
  /// The [clipBehavior] and [autofocus] arguments must not be null.
  /// Additionally, [elevation], [focusElevation], [hoverElevation],
  /// [highlightElevation], and [disabledElevation] (if specified) must be
  /// non-negative.
  const FloatingActionButton.small({
    Key? key,
    this.child,
    this.tooltip,
    this.foregroundColor,
    this.backgroundColor,
    this.focusColor,
    this.hoverColor,
    this.splashColor,
    this.heroTag = const _DefaultHeroTag(),
    this.elevation,
    this.focusElevation,
    this.hoverElevation,
    this.highlightElevation,
    this.disabledElevation,
    required this.onPressed,
    this.mouseCursor,
    this.shape,
    this.clipBehavior = Clip.none,
    this.focusNode,
    this.autofocus = false,
    this.materialTapTargetSize,
    this.enableFeedback,
  }) : 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),
       assert(clipBehavior != null),
       assert(autofocus != null),
       _floatingActionButtonType = _FloatingActionButtonType.small,
       mini = true,
       isExtended = false,
       _extendedLabel = null,
       extendedIconLabelSpacing = null,
185
       extendedPadding = null,
186
       extendedTextStyle = null,
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
       super(key: key);

  /// Creates a large circular floating action button.
  ///
  /// This constructor overrides the default size constraints of the floating
  /// action button.
  ///
  /// The [clipBehavior] and [autofocus] arguments must not be null.
  /// Additionally, [elevation], [focusElevation], [hoverElevation],
  /// [highlightElevation], and [disabledElevation] (if specified) must be
  /// non-negative.
  const FloatingActionButton.large({
    Key? key,
    this.child,
    this.tooltip,
    this.foregroundColor,
    this.backgroundColor,
    this.focusColor,
    this.hoverColor,
    this.splashColor,
    this.heroTag = const _DefaultHeroTag(),
    this.elevation,
    this.focusElevation,
    this.hoverElevation,
    this.highlightElevation,
    this.disabledElevation,
    required this.onPressed,
    this.mouseCursor,
    this.shape,
    this.clipBehavior = Clip.none,
    this.focusNode,
    this.autofocus = false,
    this.materialTapTargetSize,
    this.enableFeedback,
  }) : 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),
       assert(clipBehavior != null),
       assert(autofocus != null),
       _floatingActionButtonType = _FloatingActionButtonType.large,
       mini = false,
       isExtended = false,
       _extendedLabel = null,
       extendedIconLabelSpacing = null,
233
       extendedPadding = null,
234
       extendedTextStyle = null,
235
       super(key: key);
236

237 238
  /// Creates a wider [StadiumBorder]-shaped floating action button with
  /// an optional [icon] and a [label].
239
  ///
240
  /// The [label], [autofocus], and [clipBehavior] arguments must not be null.
241 242
  /// Additionally, [elevation], [highlightElevation], and [disabledElevation]
  /// (if specified) must be non-negative.
243
  const FloatingActionButton.extended({
244
    Key? key,
245 246 247
    this.tooltip,
    this.foregroundColor,
    this.backgroundColor,
248 249
    this.focusColor,
    this.hoverColor,
250
    this.heroTag = const _DefaultHeroTag(),
251
    this.elevation,
252 253
    this.focusElevation,
    this.hoverElevation,
254
    this.splashColor,
255 256
    this.highlightElevation,
    this.disabledElevation,
257
    required this.onPressed,
258
    this.mouseCursor = SystemMouseCursors.click,
259
    this.shape,
260
    this.isExtended = true,
261
    this.materialTapTargetSize,
262
    this.clipBehavior = Clip.none,
263
    this.focusNode,
264
    this.autofocus = false,
265
    this.extendedIconLabelSpacing,
266
    this.extendedPadding,
267
    this.extendedTextStyle,
268 269
    Widget? icon,
    required Widget label,
270
    this.enableFeedback,
271
  }) : assert(elevation == null || elevation >= 0.0),
272 273
       assert(focusElevation == null || focusElevation >= 0.0),
       assert(hoverElevation == null || hoverElevation >= 0.0),
274
       assert(highlightElevation == null || highlightElevation >= 0.0),
275 276
       assert(disabledElevation == null || disabledElevation >= 0.0),
       assert(isExtended != null),
277
       assert(clipBehavior != null),
278
       assert(autofocus != null),
279
       mini = false,
280 281 282
       _floatingActionButtonType = _FloatingActionButtonType.extended,
       child = icon,
       _extendedLabel = label,
283
       super(key: key);
284

285
  /// The widget below this widget in the tree.
286 287
  ///
  /// Typically an [Icon].
288
  final Widget? child;
289

290 291 292 293
  /// Text that describes the action that will occur when the button is pressed.
  ///
  /// This text is displayed when the user long-presses on the button and is
  /// used for accessibility.
294
  final String? tooltip;
295

296
  /// The default foreground color for icons and text within the button.
297
  ///
298 299 300 301 302
  /// If this property is null, then the
  /// [FloatingActionButtonThemeData.foregroundColor] of
  /// [ThemeData.floatingActionButtonTheme] is used. If that property is also
  /// null, then the [ColorScheme.onSecondary] color of [ThemeData.colorScheme]
  /// is used.
303
  final Color? foregroundColor;
304

305
  /// The button's background color.
306
  ///
307 308 309
  /// If this property is null, then the
  /// [FloatingActionButtonThemeData.backgroundColor] of
  /// [ThemeData.floatingActionButtonTheme] is used. If that property is also
310
  /// null, then the [Theme]'s [ColorScheme.secondary] color is used.
311
  final Color? backgroundColor;
312

313 314 315
  /// The color to use for filling the button when the button has input focus.
  ///
  /// Defaults to [ThemeData.focusColor] for the current theme.
316
  final Color? focusColor;
317 318 319 320 321

  /// The color to use for filling the button when the button has a pointer
  /// hovering over it.
  ///
  /// Defaults to [ThemeData.hoverColor] for the current theme.
322
  final Color? hoverColor;
323

324 325 326 327
  /// The splash color for this [FloatingActionButton]'s [InkWell].
  ///
  /// If null, [FloatingActionButtonThemeData.splashColor] is used, if that is
  /// null, [ThemeData.splashColor] is used.
328
  final Color? splashColor;
329

330 331 332
  /// The tag to apply to the button's [Hero] widget.
  ///
  /// Defaults to a tag that matches other floating action buttons.
333 334 335 336 337 338 339 340 341
  ///
  /// Set this to null explicitly if you don't want the floating action button to
  /// have a hero tag.
  ///
  /// If this is not explicitly set, then there can only be one
  /// [FloatingActionButton] per route (that is, per screen), since otherwise
  /// there would be a tag conflict (multiple heroes on one route can't have the
  /// same tag). The material design specification recommends only using one
  /// floating action button per screen.
342
  final Object? heroTag;
343

344
  /// The callback that is called when the button is tapped or otherwise activated.
345 346
  ///
  /// If this is set to null, the button will be disabled.
347
  final VoidCallback? onPressed;
348

349
  /// {@macro flutter.material.RawMaterialButton.mouseCursor}
350
  final MouseCursor? mouseCursor;
351

352
  /// The z-coordinate at which to place this button relative to its parent.
353
  ///
354 355 356 357
  /// This controls the size of the shadow below the floating action button.
  ///
  /// Defaults to 6, the appropriate elevation for floating action buttons. The
  /// value is always non-negative.
358 359 360 361 362
  ///
  /// See also:
  ///
  ///  * [highlightElevation], the elevation when the button is pressed.
  ///  * [disabledElevation], the elevation when the button is disabled.
363
  final double? elevation;
364

365 366 367 368 369 370 371 372 373 374 375 376 377
  /// The z-coordinate at which to place this button relative to its parent when
  /// the button has the input focus.
  ///
  /// This controls the size of the shadow below the floating action button.
  ///
  /// Defaults to 8, the appropriate elevation for floating action buttons
  /// while they have focus. The value is always non-negative.
  ///
  /// See also:
  ///
  ///  * [elevation], the default elevation.
  ///  * [highlightElevation], the elevation when the button is pressed.
  ///  * [disabledElevation], the elevation when the button is disabled.
378
  final double? focusElevation;
379 380 381 382 383 384 385 386 387 388 389 390 391 392

  /// The z-coordinate at which to place this button relative to its parent when
  /// the button is enabled and has a pointer hovering over it.
  ///
  /// This controls the size of the shadow below the floating action button.
  ///
  /// Defaults to 8, the appropriate elevation for floating action buttons while
  /// they have a pointer hovering over them. The value is always non-negative.
  ///
  /// See also:
  ///
  ///  * [elevation], the default elevation.
  ///  * [highlightElevation], the elevation when the button is pressed.
  ///  * [disabledElevation], the elevation when the button is disabled.
393
  final double? hoverElevation;
394

395 396 397 398
  /// The z-coordinate at which to place this button relative to its parent when
  /// the user is touching the button.
  ///
  /// This controls the size of the shadow below the floating action button.
399 400
  ///
  /// Defaults to 12, the appropriate elevation for floating action buttons
401
  /// while they are being touched. The value is always non-negative.
402 403 404 405
  ///
  /// See also:
  ///
  ///  * [elevation], the default elevation.
406
  final double? highlightElevation;
407

408 409 410 411 412 413
  /// The z-coordinate at which to place this button when the button is disabled
  /// ([onPressed] is null).
  ///
  /// This controls the size of the shadow below the floating action button.
  ///
  /// Defaults to the same value as [elevation]. Setting this to zero makes the
414
  /// floating action button work similar to an [ElevatedButton] but the titular
415 416 417 418 419 420
  /// "floating" effect is lost. The value is always non-negative.
  ///
  /// See also:
  ///
  ///  * [elevation], the default elevation.
  ///  * [highlightElevation], the elevation when the button is pressed.
421
  final double? disabledElevation;
422

423 424 425 426
  /// Controls the size of this button.
  ///
  /// By default, floating action buttons are non-mini and have a height and
  /// width of 56.0 logical pixels. Mini floating action buttons have a height
427
  /// and width of 40.0 logical pixels with a layout width and height of 48.0
428 429 430
  /// logical pixels. (The extra 4 pixels of padding on each side are added as a
  /// result of the floating action button having [MaterialTapTargetSize.padded]
  /// set on the underlying [RawMaterialButton.materialTapTargetSize].)
Devon Carew's avatar
Devon Carew committed
431
  final bool mini;
432

433 434 435 436 437
  /// 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.
438
  final ShapeBorder? shape;
439

440
  /// {@macro flutter.material.Material.clipBehavior}
441 442
  ///
  /// Defaults to [Clip.none], and must not be null.
443 444
  final Clip clipBehavior;

445 446 447 448 449 450 451 452 453 454 455
  /// True if this is an "extended" floating action button.
  ///
  /// Typically [extended] buttons have a [StadiumBorder] [shape]
  /// and have been created with the [FloatingActionButton.extended]
  /// constructor.
  ///
  /// The [Scaffold] animates the appearance of ordinary floating
  /// action buttons with scale and rotation transitions. Extended
  /// floating action buttons are scaled and faded in.
  final bool isExtended;

456
  /// {@macro flutter.widgets.Focus.focusNode}
457
  final FocusNode? focusNode;
458

459 460 461
  /// {@macro flutter.widgets.Focus.autofocus}
  final bool autofocus;

462 463 464 465 466 467
  /// Configures the minimum size of the tap target.
  ///
  /// Defaults to [ThemeData.materialTapTargetSize].
  ///
  /// See also:
  ///
468
  ///  * [MaterialTapTargetSize], for a description of how this affects tap targets.
469
  final MaterialTapTargetSize? materialTapTargetSize;
470

471 472 473 474 475 476 477 478 479 480 481 482 483
  /// 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.
  ///
  /// If null, [FloatingActionButtonThemeData.enableFeedback] is used.
  /// If both are null, then default value is true.
  ///
  /// See also:
  ///
  ///  * [Feedback] for providing platform-specific feedback to certain actions.
  final bool? enableFeedback;

484 485 486 487 488 489 490 491

  /// The spacing between the icon and the label for an extended
  /// [FloatingActionButton].
  ///
  /// If null, [FloatingActionButtonThemeData.extendedIconLabelSpacing] is used.
  /// If that is also null, the default is 8.0.
  final double? extendedIconLabelSpacing;

492 493 494 495 496 497 498 499
  /// The padding for an extended [FloatingActionButton]'s content.
  ///
  /// If null, [FloatingActionButtonThemeData.extendedPadding] is used. If that
  /// is also null, the default is
  /// `EdgeInsetsDirectional.only(start: 16.0, end: 20.0)` if an icon is
  /// provided, and `EdgeInsetsDirectional.only(start: 20.0, end: 20.0)` if not.
  final EdgeInsetsGeometry? extendedPadding;

500 501 502 503 504 505 506
  /// The text style for an extended [FloatingActionButton]'s label.
  ///
  /// If null, [FloatingActionButtonThemeData.extendedTextStyle] is used. If
  /// that is also null, then [TextTheme.button] with a letter spacing of 1.2
  /// is used.
  final TextStyle? extendedTextStyle;

507 508 509
  final _FloatingActionButtonType _floatingActionButtonType;

  final Widget? _extendedLabel;
510

511
  static const double _defaultElevation = 6;
512 513
  static const double _defaultFocusElevation = 6;
  static const double _defaultHoverElevation = 8;
514 515 516 517
  static const double _defaultHighlightElevation = 12;
  static const ShapeBorder _defaultShape = CircleBorder();
  static const ShapeBorder _defaultExtendedShape = StadiumBorder();

518
  @override
519
  Widget build(BuildContext context) {
520
    final ThemeData theme = Theme.of(context);
521 522 523 524 525
    final FloatingActionButtonThemeData floatingActionButtonTheme = theme.floatingActionButtonTheme;

    final Color foregroundColor = this.foregroundColor
      ?? floatingActionButtonTheme.foregroundColor
      ?? theme.colorScheme.onSecondary;
526 527 528 529 530 531 532 533 534
    final Color backgroundColor = this.backgroundColor
      ?? floatingActionButtonTheme.backgroundColor
      ?? theme.colorScheme.secondary;
    final Color focusColor = this.focusColor
      ?? floatingActionButtonTheme.focusColor
      ?? theme.focusColor;
    final Color hoverColor = this.hoverColor
      ?? floatingActionButtonTheme.hoverColor
      ?? theme.hoverColor;
535 536 537
    final Color splashColor = this.splashColor
      ?? floatingActionButtonTheme.splashColor
      ?? theme.splashColor;
538 539 540
    final double elevation = this.elevation
      ?? floatingActionButtonTheme.elevation
      ?? _defaultElevation;
541 542 543 544 545 546
    final double focusElevation = this.focusElevation
      ?? floatingActionButtonTheme.focusElevation
      ?? _defaultFocusElevation;
    final double hoverElevation = this.hoverElevation
      ?? floatingActionButtonTheme.hoverElevation
      ?? _defaultHoverElevation;
547 548 549 550 551 552 553 554
    final double disabledElevation = this.disabledElevation
      ?? floatingActionButtonTheme.disabledElevation
      ?? elevation;
    final double highlightElevation = this.highlightElevation
      ?? floatingActionButtonTheme.highlightElevation
      ?? _defaultHighlightElevation;
    final MaterialTapTargetSize materialTapTargetSize = this.materialTapTargetSize
      ?? theme.materialTapTargetSize;
555 556
    final bool enableFeedback = this.enableFeedback
      ?? floatingActionButtonTheme.enableFeedback ?? true;
557 558 559
    final TextStyle extendedTextStyle = (this.extendedTextStyle
        ?? floatingActionButtonTheme.extendedTextStyle
        ?? theme.textTheme.button!.copyWith(letterSpacing: 1.2)).copyWith(color: foregroundColor);
560 561 562 563
    final ShapeBorder shape = this.shape
      ?? floatingActionButtonTheme.shape
      ?? (isExtended ? _defaultExtendedShape : _defaultShape);

564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583
    BoxConstraints sizeConstraints;
    Widget? resolvedChild = child;
    switch(_floatingActionButtonType) {
      case _FloatingActionButtonType.regular:
        sizeConstraints = floatingActionButtonTheme.sizeConstraints ?? _kSizeConstraints;
        break;
      case _FloatingActionButtonType.small:
        sizeConstraints = floatingActionButtonTheme.smallSizeConstraints ?? _kMiniSizeConstraints;
        break;
      case _FloatingActionButtonType.large:
        sizeConstraints = floatingActionButtonTheme.largeSizeConstraints ?? _kLargeSizeConstraints;
        // The large FAB uses a larger icon.
        resolvedChild = child != null ? IconTheme.merge(
          data: const IconThemeData(size: 36.0),
          child: child!,
        ) : child;
        break;
      case _FloatingActionButtonType.extended:
        sizeConstraints = floatingActionButtonTheme.extendedSizeConstraints ?? _kExtendedSizeConstraints;
        final double iconLabelSpacing = extendedIconLabelSpacing ?? floatingActionButtonTheme.extendedIconLabelSpacing ?? 8.0;
584 585 586
        final EdgeInsetsGeometry padding = extendedPadding
            ?? floatingActionButtonTheme.extendedPadding
            ?? EdgeInsetsDirectional.only(start: child != null && isExtended ? 16.0 : 20.0, end: 20.0);
587
        resolvedChild = _ChildOverflowBox(
588 589 590 591 592 593 594 595 596 597 598 599 600
          child: Padding(
            padding: padding,
            child: Row(
                mainAxisSize: MainAxisSize.min,
                children: <Widget>[
                  if (child != null)
                    child!,
                  if (child != null && isExtended)
                    SizedBox(width: iconLabelSpacing),
                  if (isExtended)
                    _extendedLabel!,
                ],
            ),
601 602 603 604 605
          ),
        );
        break;
    }

606
    Widget result = RawMaterialButton(
607
      onPressed: onPressed,
608
      mouseCursor: mouseCursor,
609
      elevation: elevation,
610 611
      focusElevation: focusElevation,
      hoverElevation: hoverElevation,
612 613
      highlightElevation: highlightElevation,
      disabledElevation: disabledElevation,
614
      constraints: sizeConstraints,
615 616
      materialTapTargetSize: materialTapTargetSize,
      fillColor: backgroundColor,
617 618
      focusColor: focusColor,
      hoverColor: hoverColor,
619
      splashColor: splashColor,
620
      textStyle: extendedTextStyle,
621
      shape: shape,
622
      clipBehavior: clipBehavior,
623
      focusNode: focusNode,
624
      autofocus: autofocus,
625
      enableFeedback: enableFeedback,
626
      child: resolvedChild,
627 628
    );

629
    if (tooltip != null) {
630
      result = Tooltip(
631
        message: tooltip,
632
        child: result,
633 634 635
      );
    }

636
    if (heroTag != null) {
637
      result = Hero(
638
        tag: heroTag!,
639 640 641 642
        child: result,
      );
    }

643
    return MergeSemantics(child: result);
644
  }
645 646 647 648 649 650

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(ObjectFlagProperty<VoidCallback>('onPressed', onPressed, ifNull: 'disabled'));
    properties.add(StringProperty('tooltip', tooltip, defaultValue: null));
651 652 653 654
    properties.add(ColorProperty('foregroundColor', foregroundColor, defaultValue: null));
    properties.add(ColorProperty('backgroundColor', backgroundColor, defaultValue: null));
    properties.add(ColorProperty('focusColor', focusColor, defaultValue: null));
    properties.add(ColorProperty('hoverColor', hoverColor, defaultValue: null));
655
    properties.add(ColorProperty('splashColor', splashColor, defaultValue: null));
656
    properties.add(ObjectFlagProperty<Object>('heroTag', heroTag, ifPresent: 'hero'));
657 658 659 660 661
    properties.add(DoubleProperty('elevation', elevation, defaultValue: null));
    properties.add(DoubleProperty('focusElevation', focusElevation, defaultValue: null));
    properties.add(DoubleProperty('hoverElevation', hoverElevation, defaultValue: null));
    properties.add(DoubleProperty('highlightElevation', highlightElevation, defaultValue: null));
    properties.add(DoubleProperty('disabledElevation', disabledElevation, defaultValue: null));
662 663 664 665 666
    properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
    properties.add(DiagnosticsProperty<FocusNode>('focusNode', focusNode, defaultValue: null));
    properties.add(FlagProperty('isExtended', value: isExtended, ifTrue: 'extended'));
    properties.add(DiagnosticsProperty<MaterialTapTargetSize>('materialTapTargetSize', materialTapTargetSize, defaultValue: null));
  }
667
}
668 669 670 671 672 673 674 675

// This widget's size matches its child's size unless its constraints
// force it to be larger or smaller. The child is centered.
//
// Used to encapsulate extended FABs whose size is fixed, using Row
// and MainAxisSize.min, to be as wide as their label and icon.
class _ChildOverflowBox extends SingleChildRenderObjectWidget {
  const _ChildOverflowBox({
676 677
    Key? key,
    Widget? child,
678 679 680 681
  }) : super(key: key, child: child);

  @override
  _RenderChildOverflowBox createRenderObject(BuildContext context) {
682
    return _RenderChildOverflowBox(
683 684 685 686 687 688
      textDirection: Directionality.of(context),
    );
  }

  @override
  void updateRenderObject(BuildContext context, _RenderChildOverflowBox renderObject) {
689
    renderObject.textDirection = Directionality.of(context);
690 691 692 693 694
  }
}

class _RenderChildOverflowBox extends RenderAligningShiftedBox {
  _RenderChildOverflowBox({
695 696
    RenderBox? child,
    TextDirection? textDirection,
697 698 699 700 701 702 703 704
  }) : super(child: child, alignment: Alignment.center, textDirection: textDirection);

  @override
  double computeMinIntrinsicWidth(double height) => 0.0;

  @override
  double computeMinIntrinsicHeight(double width) => 0.0;

705 706 707 708 709 710 711 712 713 714 715 716 717
  @override
  Size computeDryLayout(BoxConstraints constraints) {
    if (child != null) {
      final Size childSize = child!.getDryLayout(const BoxConstraints());
      return Size(
        math.max(constraints.minWidth, math.min(constraints.maxWidth, childSize.width)),
        math.max(constraints.minHeight, math.min(constraints.maxHeight, childSize.height)),
      );
    } else {
      return constraints.biggest;
    }
  }

718 719
  @override
  void performLayout() {
720
    final BoxConstraints constraints = this.constraints;
721
    if (child != null) {
722
      child!.layout(const BoxConstraints(), parentUsesSize: true);
723
      size = Size(
724 725
        math.max(constraints.minWidth, math.min(constraints.maxWidth, child!.size.width)),
        math.max(constraints.minHeight, math.min(constraints.maxHeight, child!.size.height)),
726 727 728 729 730 731 732
      );
      alignChild();
    } else {
      size = constraints.biggest;
    }
  }
}