Migrate FloatingActionButton to Material 3 (#94486)

diff --git a/dev/tools/gen_defaults/data/material-tokens.json b/dev/tools/gen_defaults/data/material-tokens.json
index 3412597..2cd2616 100644
--- a/dev/tools/gen_defaults/data/material-tokens.json
+++ b/dev/tools/gen_defaults/data/material-tokens.json
@@ -1,6 +1,6 @@
 {
-  "version": "v0.72",
-  "date": "2021-12-16 00:27:25.239571",
+  "version": "v0.74",
+  "date": "2022-01-06",
   "md.sys.color.light.on-tertiary": "md.ref.palette.tertiary100",
   "md.sys.color.light.on-secondary-container": "md.ref.palette.secondary10",
   "md.sys.color.light.on-secondary": "md.ref.palette.secondary100",
diff --git a/packages/flutter/lib/src/material/floating_action_button.dart b/packages/flutter/lib/src/material/floating_action_button.dart
index 0b7fefd..fcb6c32 100644
--- a/packages/flutter/lib/src/material/floating_action_button.dart
+++ b/packages/flutter/lib/src/material/floating_action_button.dart
@@ -9,31 +9,14 @@
 import 'package:flutter/widgets.dart';
 
 import 'button.dart';
+import 'color_scheme.dart';
 import 'floating_action_button_theme.dart';
 import 'scaffold.dart';
+import 'text_theme.dart';
 import 'theme.dart';
 import 'theme_data.dart';
 import 'tooltip.dart';
 
-const BoxConstraints _kSizeConstraints = BoxConstraints.tightFor(
-  width: 56.0,
-  height: 56.0,
-);
-
-const BoxConstraints _kMiniSizeConstraints = BoxConstraints.tightFor(
-  width: 40.0,
-  height: 40.0,
-);
-
-const BoxConstraints _kLargeSizeConstraints = BoxConstraints.tightFor(
-  width: 96.0,
-  height: 96.0,
-);
-
-const BoxConstraints _kExtendedSizeConstraints = BoxConstraints.tightFor(
-  height: 48.0,
-);
-
 class _DefaultHeroTag {
   const _DefaultHeroTag();
   @override
@@ -508,82 +491,80 @@
 
   final Widget? _extendedLabel;
 
-  static const double _defaultElevation = 6;
-  static const double _defaultFocusElevation = 6;
-  static const double _defaultHoverElevation = 8;
-  static const double _defaultHighlightElevation = 12;
-  static const ShapeBorder _defaultShape = CircleBorder();
-  static const ShapeBorder _defaultExtendedShape = StadiumBorder();
-
   @override
   Widget build(BuildContext context) {
     final ThemeData theme = Theme.of(context);
     final FloatingActionButtonThemeData floatingActionButtonTheme = theme.floatingActionButtonTheme;
+    final FloatingActionButtonThemeData defaults = theme.useMaterial3
+      ? _M3Defaults(context, _floatingActionButtonType, child != null)
+      : _M2Defaults(context, _floatingActionButtonType, child != null);
 
     final Color foregroundColor = this.foregroundColor
       ?? floatingActionButtonTheme.foregroundColor
-      ?? theme.colorScheme.onSecondary;
+      ?? defaults.foregroundColor!;
     final Color backgroundColor = this.backgroundColor
       ?? floatingActionButtonTheme.backgroundColor
-      ?? theme.colorScheme.secondary;
+      ?? defaults.backgroundColor!;
     final Color focusColor = this.focusColor
       ?? floatingActionButtonTheme.focusColor
-      ?? theme.focusColor;
+      ?? defaults.focusColor!;
     final Color hoverColor = this.hoverColor
       ?? floatingActionButtonTheme.hoverColor
-      ?? theme.hoverColor;
+      ?? defaults.hoverColor!;
     final Color splashColor = this.splashColor
       ?? floatingActionButtonTheme.splashColor
-      ?? theme.splashColor;
+      ?? defaults.splashColor!;
     final double elevation = this.elevation
       ?? floatingActionButtonTheme.elevation
-      ?? _defaultElevation;
+      ?? defaults.elevation!;
     final double focusElevation = this.focusElevation
       ?? floatingActionButtonTheme.focusElevation
-      ?? _defaultFocusElevation;
+      ?? defaults.focusElevation!;
     final double hoverElevation = this.hoverElevation
       ?? floatingActionButtonTheme.hoverElevation
-      ?? _defaultHoverElevation;
+      ?? defaults.hoverElevation!;
     final double disabledElevation = this.disabledElevation
       ?? floatingActionButtonTheme.disabledElevation
+      ?? defaults.disabledElevation
       ?? elevation;
     final double highlightElevation = this.highlightElevation
       ?? floatingActionButtonTheme.highlightElevation
-      ?? _defaultHighlightElevation;
+      ?? defaults.highlightElevation!;
     final MaterialTapTargetSize materialTapTargetSize = this.materialTapTargetSize
       ?? theme.materialTapTargetSize;
     final bool enableFeedback = this.enableFeedback
-      ?? floatingActionButtonTheme.enableFeedback ?? true;
+      ?? floatingActionButtonTheme.enableFeedback
+      ?? defaults.enableFeedback!;
+    final double iconSize = floatingActionButtonTheme.iconSize
+      ?? defaults.iconSize!;
     final TextStyle extendedTextStyle = (this.extendedTextStyle
-        ?? floatingActionButtonTheme.extendedTextStyle
-        ?? theme.textTheme.button!.copyWith(letterSpacing: 1.2)).copyWith(color: foregroundColor);
+      ?? floatingActionButtonTheme.extendedTextStyle
+      ?? defaults.extendedTextStyle!).copyWith(color: foregroundColor);
     final ShapeBorder shape = this.shape
       ?? floatingActionButtonTheme.shape
-      ?? (isExtended ? _defaultExtendedShape : _defaultShape);
+      ?? defaults.shape!;
 
     BoxConstraints sizeConstraints;
-    Widget? resolvedChild = child;
+    Widget? resolvedChild = child != null ? IconTheme.merge(
+      data: IconThemeData(size: iconSize),
+      child: child!,
+    ) : child;
     switch(_floatingActionButtonType) {
       case _FloatingActionButtonType.regular:
-        sizeConstraints = floatingActionButtonTheme.sizeConstraints ?? _kSizeConstraints;
+        sizeConstraints = floatingActionButtonTheme.sizeConstraints ?? defaults.sizeConstraints!;
         break;
       case _FloatingActionButtonType.small:
-        sizeConstraints = floatingActionButtonTheme.smallSizeConstraints ?? _kMiniSizeConstraints;
+        sizeConstraints = floatingActionButtonTheme.smallSizeConstraints ?? defaults.smallSizeConstraints!;
         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;
+        sizeConstraints = floatingActionButtonTheme.largeSizeConstraints ?? defaults.largeSizeConstraints!;
         break;
       case _FloatingActionButtonType.extended:
-        sizeConstraints = floatingActionButtonTheme.extendedSizeConstraints ?? _kExtendedSizeConstraints;
+        sizeConstraints = floatingActionButtonTheme.extendedSizeConstraints ?? defaults.extendedSizeConstraints!;
         final double iconLabelSpacing = extendedIconLabelSpacing ?? floatingActionButtonTheme.extendedIconLabelSpacing ?? 8.0;
         final EdgeInsetsGeometry padding = extendedPadding
             ?? floatingActionButtonTheme.extendedPadding
-            ?? EdgeInsetsDirectional.only(start: child != null && isExtended ? 16.0 : 20.0, end: 20.0);
+            ?? defaults.extendedPadding!;
         resolvedChild = _ChildOverflowBox(
           child: Padding(
             padding: padding,
@@ -730,3 +711,148 @@
     }
   }
 }
+
+// Generate a FloatingActionButtonThemeData that represents
+// the M2 default values. This was generated by hand from the
+// previous hand coded defaults for M2. It uses get method overrides
+// instead of properties to avoid computing values that we may not
+// need upfront.
+class _M2Defaults extends FloatingActionButtonThemeData {
+  _M2Defaults(BuildContext context, this.type, this.hasChild)
+      : _theme = Theme.of(context),
+        _colors = Theme.of(context).colorScheme;
+
+  final _FloatingActionButtonType type;
+  final bool hasChild;
+  final ThemeData _theme;
+  final ColorScheme _colors;
+
+  bool get _isExtended => type == _FloatingActionButtonType.extended;
+  bool get _isLarge => type == _FloatingActionButtonType.large;
+
+  @override Color? get foregroundColor => _colors.onSecondary;
+  @override Color? get backgroundColor => _colors.secondary;
+  @override Color? get focusColor => _theme.focusColor;
+  @override Color? get hoverColor => _theme.hoverColor;
+  @override Color? get splashColor => _theme.splashColor;
+  @override double? get elevation => 6;
+  @override double? get focusElevation => 6;
+  @override double? get hoverElevation => 8;
+  @override double? get highlightElevation => 12;
+  @override ShapeBorder? get shape => _isExtended ? const StadiumBorder() : const CircleBorder();
+  @override bool? get enableFeedback => true;
+  @override double? get iconSize => _isLarge ? 36.0 : 24.0;
+
+  @override
+  BoxConstraints? get sizeConstraints => const BoxConstraints.tightFor(
+    width: 56.0,
+    height: 56.0,
+  );
+
+  @override
+  BoxConstraints? get smallSizeConstraints => const BoxConstraints.tightFor(
+    width: 40.0,
+    height: 40.0,
+  );
+
+  @override
+  BoxConstraints? get largeSizeConstraints => const BoxConstraints.tightFor(
+    width: 96.0,
+    height: 96.0,
+  );
+
+  @override
+  BoxConstraints? get extendedSizeConstraints => const BoxConstraints.tightFor(
+    height: 48.0,
+  );
+
+  @override double? get extendedIconLabelSpacing => 8.0;
+  @override EdgeInsetsGeometry? get extendedPadding => EdgeInsetsDirectional.only(start: hasChild && _isExtended ? 16.0 : 20.0, end: 20.0);
+  @override TextStyle? get extendedTextStyle => _theme.textTheme.button!.copyWith(letterSpacing: 1.2);
+}
+
+// BEGIN GENERATED TOKEN PROPERTIES
+
+// Generated code to the end of this file. Do not edit by hand.
+// These defaults are generated from the Material Design Token
+// database by the script dev/tools/gen_defaults/bin/gen_defaults.dart.
+
+// Generated version v0.74, 2022-01-06
+class _M3Defaults extends FloatingActionButtonThemeData {
+  _M3Defaults(this.context, this.type, this.hasChild)
+    : _colors = Theme.of(context).colorScheme,
+      _textTheme = Theme.of(context).textTheme;
+
+  final BuildContext context;
+  final _FloatingActionButtonType type;
+  final bool hasChild;
+  final ColorScheme _colors;
+  final TextTheme _textTheme;
+
+  bool get _isExtended => type == _FloatingActionButtonType.extended;
+
+  @override Color? get foregroundColor => _colors.onPrimaryContainer;
+  @override Color? get backgroundColor => _colors.primaryContainer;
+  @override Color? get splashColor => _colors.onPrimaryContainer.withOpacity(0.12);
+  @override double get elevation => 6.0;
+  @override Color? get focusColor => _colors.onPrimaryContainer.withOpacity(0.12);
+  @override double get focusElevation => 6.0;
+  @override Color? get hoverColor => _colors.onPrimaryContainer.withOpacity(0.08);
+  @override double get hoverElevation => 8.0;
+  @override double get highlightElevation => 6.0;
+
+  @override
+  ShapeBorder? get shape {
+    switch (type) {
+      case _FloatingActionButtonType.regular:
+       return const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16.0)));
+      case _FloatingActionButtonType.small:
+       return const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(12.0)));
+      case _FloatingActionButtonType.large:
+       return const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(28.0)));
+      case _FloatingActionButtonType.extended:
+       return const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16.0)));
+     }
+  }
+
+  @override bool? get enableFeedback => true;
+
+  @override
+  double? get iconSize {
+    switch (type) {
+      case _FloatingActionButtonType.regular: return 24.0;
+      case _FloatingActionButtonType.small: return  24.0;
+      case _FloatingActionButtonType.large: return 36.0;
+      case _FloatingActionButtonType.extended: return 24.0;
+    }
+  }
+
+  @override
+  BoxConstraints? get sizeConstraints => const BoxConstraints.tightFor(
+    width: 56.0,
+    height: 56.0,
+  );
+
+  @override
+  BoxConstraints? get smallSizeConstraints => const BoxConstraints.tightFor(
+    width: 40.0,
+    height: 40.0,
+  );
+
+  @override
+  BoxConstraints? get largeSizeConstraints => const BoxConstraints.tightFor(
+    width: 96.0,
+    height: 96.0,
+  );
+
+  @override
+  BoxConstraints? get extendedSizeConstraints => const BoxConstraints.tightFor(
+    height: 56.0,
+  );
+
+  @override double? get extendedIconLabelSpacing => 8.0;
+  @override EdgeInsetsGeometry? get extendedPadding => EdgeInsetsDirectional.only(start: hasChild && _isExtended ? 16.0 : 20.0, end: 20.0);
+  @override TextStyle? get extendedTextStyle => _textTheme.labelLarge;
+}
+
+// END GENERATED TOKEN PROPERTIES
diff --git a/packages/flutter/lib/src/material/floating_action_button_theme.dart b/packages/flutter/lib/src/material/floating_action_button_theme.dart
index 61e6338..81337cf 100644
--- a/packages/flutter/lib/src/material/floating_action_button_theme.dart
+++ b/packages/flutter/lib/src/material/floating_action_button_theme.dart
@@ -43,6 +43,7 @@
     this.highlightElevation,
     this.shape,
     this.enableFeedback,
+    this.iconSize,
     this.sizeConstraints,
     this.smallSizeConstraints,
     this.largeSizeConstraints,
@@ -103,6 +104,9 @@
   /// ignored.
   final bool? enableFeedback;
 
+  /// Overrides the default icon size for the [FloatingActionButton];
+  final double? iconSize;
+
   /// Overrides the default size constraints for the [FloatingActionButton].
   final BoxConstraints? sizeConstraints;
 
@@ -140,6 +144,7 @@
     double? highlightElevation,
     ShapeBorder? shape,
     bool? enableFeedback,
+    double? iconSize,
     BoxConstraints? sizeConstraints,
     BoxConstraints? smallSizeConstraints,
     BoxConstraints? largeSizeConstraints,
@@ -161,6 +166,7 @@
       highlightElevation: highlightElevation ?? this.highlightElevation,
       shape: shape ?? this.shape,
       enableFeedback: enableFeedback ?? this.enableFeedback,
+      iconSize: iconSize ?? this.iconSize,
       sizeConstraints: sizeConstraints ?? this.sizeConstraints,
       smallSizeConstraints: smallSizeConstraints ?? this.smallSizeConstraints,
       largeSizeConstraints: largeSizeConstraints ?? this.largeSizeConstraints,
@@ -193,6 +199,7 @@
       highlightElevation: lerpDouble(a?.highlightElevation, b?.highlightElevation, t),
       shape: ShapeBorder.lerp(a?.shape, b?.shape, t),
       enableFeedback: t < 0.5 ? a?.enableFeedback : b?.enableFeedback,
+      iconSize: lerpDouble(a?.iconSize, b?.iconSize, t),
       sizeConstraints: BoxConstraints.lerp(a?.sizeConstraints, b?.sizeConstraints, t),
       smallSizeConstraints: BoxConstraints.lerp(a?.smallSizeConstraints, b?.smallSizeConstraints, t),
       largeSizeConstraints: BoxConstraints.lerp(a?.largeSizeConstraints, b?.largeSizeConstraints, t),
@@ -218,6 +225,7 @@
       highlightElevation,
       shape,
       enableFeedback,
+      iconSize,
       sizeConstraints,
       smallSizeConstraints,
       largeSizeConstraints,
@@ -247,6 +255,7 @@
         && other.highlightElevation == highlightElevation
         && other.shape == shape
         && other.enableFeedback == enableFeedback
+        && other.iconSize == iconSize
         && other.sizeConstraints == sizeConstraints
         && other.smallSizeConstraints == smallSizeConstraints
         && other.largeSizeConstraints == largeSizeConstraints
@@ -272,6 +281,7 @@
     properties.add(DoubleProperty('highlightElevation', highlightElevation, defaultValue: null));
     properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
     properties.add(DiagnosticsProperty<bool>('enableFeedback', enableFeedback, defaultValue: null));
+    properties.add(DoubleProperty('iconSize', iconSize, defaultValue: null));
     properties.add(DiagnosticsProperty<BoxConstraints>('sizeConstraints', sizeConstraints, defaultValue: null));
     properties.add(DiagnosticsProperty<BoxConstraints>('smallSizeConstraints', smallSizeConstraints, defaultValue: null));
     properties.add(DiagnosticsProperty<BoxConstraints>('largeSizeConstraints', largeSizeConstraints, defaultValue: null));
diff --git a/packages/flutter/lib/src/material/theme_data.dart b/packages/flutter/lib/src/material/theme_data.dart
index dc96187..6a40f3d 100644
--- a/packages/flutter/lib/src/material/theme_data.dart
+++ b/packages/flutter/lib/src/material/theme_data.dart
@@ -1134,10 +1134,6 @@
   /// start using new colors, typography and other features of Material 3.
   /// If false, they will use the Material 2 look and feel.
   ///
-  /// Currently no components have been migrated to support Material 3.
-  /// As they are updated to include Material 3 support this documentation
-  /// will be modified to indicate exactly what widgets this flag will affect.
-  ///
   /// During the migration to Material 3, turning this on may yield
   /// inconsistent look and feel in your app. Some components will be migrated
   /// before others and typography changes will be coming in stages.
@@ -1148,6 +1144,10 @@
   /// all uses of it. Everything will use the Material 3 look and feel at
   /// that point.
   ///
+  /// Components that have been migrated to Material 3 are:
+  ///
+  ///   * [FloatingActionButton]
+  ///
   /// See also:
   ///
   ///   * [Material Design 3](https://m3.material.io/).
diff --git a/packages/flutter/test/material/floating_action_button_test.dart b/packages/flutter/test/material/floating_action_button_test.dart
index 20d895f..18322d9 100644
--- a/packages/flutter/test/material/floating_action_button_test.dart
+++ b/packages/flutter/test/material/floating_action_button_test.dart
@@ -18,6 +18,10 @@
 import 'feedback_tester.dart';
 
 void main() {
+
+  final ThemeData material3Theme = ThemeData.light().copyWith(useMaterial3: true);
+  final ThemeData material2Theme = ThemeData.light().copyWith(useMaterial3: false);
+
   testWidgets('Floating Action Button control test', (WidgetTester tester) async {
     bool didPressButton = false;
     await tester.pumpWidget(
@@ -171,6 +175,7 @@
   testWidgets('Floating Action Button elevation when highlighted - effect', (WidgetTester tester) async {
     await tester.pumpWidget(
       MaterialApp(
+        theme: material3Theme,
         home: Scaffold(
           floatingActionButton: FloatingActionButton(
             onPressed: () { },
@@ -183,7 +188,7 @@
     await tester.pump();
     expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 6.0);
     await tester.pump(const Duration(seconds: 1));
-    expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 12.0);
+    expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 6.0);
     await tester.pumpWidget(
       MaterialApp(
         home: Scaffold(
@@ -195,7 +200,7 @@
       ),
     );
     await tester.pump();
-    expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 12.0);
+    expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 6.0);
     await tester.pump(const Duration(seconds: 1));
     expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 20.0);
     await gesture.up();
@@ -277,6 +282,7 @@
   testWidgets('Floating Action Button elevation when disabled while highlighted - effect', (WidgetTester tester) async {
     await tester.pumpWidget(
       MaterialApp(
+        theme: material3Theme,
         home: Scaffold(
           floatingActionButton: FloatingActionButton(
             onPressed: () { },
@@ -289,10 +295,11 @@
     await tester.pump();
     expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 6.0);
     await tester.pump(const Duration(seconds: 1));
-    expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 12.0);
+    expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 6.0);
     await tester.pumpWidget(
-      const MaterialApp(
-        home: Scaffold(
+      MaterialApp(
+        theme: material3Theme,
+        home: const Scaffold(
           floatingActionButton: FloatingActionButton(
             onPressed: null,
           ),
@@ -300,11 +307,12 @@
       ),
     );
     await tester.pump();
-    expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 12.0);
+    expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 6.0);
     await tester.pump(const Duration(seconds: 1));
     expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 6.0);
     await tester.pumpWidget(
       MaterialApp(
+        theme: material3Theme,
         home: Scaffold(
           floatingActionButton: FloatingActionButton(
             onPressed: () { },
@@ -323,6 +331,7 @@
 
     await tester.pumpWidget(
       MaterialApp(
+        theme: material3Theme,
         home: Scaffold(
           body: FloatingActionButton.extended(
             label: const Text('tooltip'),
@@ -359,7 +368,7 @@
     await gesture.down(center);
     await tester.pump(); // Start the splash and highlight animations.
     await tester.pump(const Duration(milliseconds: 800)); // Wait for splash and highlight to be well under way.
-    expect(getFABWidget(fabFinder).elevation, 12);
+    expect(getFABWidget(fabFinder).elevation, 6);
   });
 
   testWidgets('FlatActionButton mini size is configurable by ThemeData.materialTapTargetSize', (WidgetTester tester) async {
@@ -402,8 +411,9 @@
 
   testWidgets('FloatingActionButton.isExtended', (WidgetTester tester) async {
     await tester.pumpWidget(
-      const MaterialApp(
-        home: Scaffold(
+      MaterialApp(
+        theme: material3Theme,
+        home: const Scaffold(
           floatingActionButton: FloatingActionButton(onPressed: null),
         ),
       ),
@@ -422,7 +432,10 @@
     }
 
     expect(getFabWidget().isExtended, false);
-    expect(getRawMaterialButtonWidget().shape, const CircleBorder());
+    expect(
+      getRawMaterialButtonWidget().shape,
+      const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16.0)))
+    );
 
     await tester.pumpWidget(
       const MaterialApp(
@@ -440,13 +453,16 @@
     );
 
     expect(getFabWidget().isExtended, true);
-    expect(getRawMaterialButtonWidget().shape, const StadiumBorder());
+    expect(
+      getRawMaterialButtonWidget().shape,
+      const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16.0)))
+    );
     expect(find.text('label'), findsOneWidget);
     expect(find.byType(Icon), findsOneWidget);
 
-    // Verify that the widget's height is 48 and that its internal
+    // Verify that the widget's height is 56 and that its internal
     /// horizontal layout is: 16 icon 8 label 20
-    expect(tester.getSize(fabFinder).height, 48.0);
+    expect(tester.getSize(fabFinder).height, 56.0);
 
     final double fabLeft = tester.getTopLeft(fabFinder).dx;
     final double fabRight = tester.getTopRight(fabFinder).dx;
@@ -479,8 +495,9 @@
     }
 
     await tester.pumpWidget(
-      const MaterialApp(
-        home: Scaffold(
+      MaterialApp(
+        theme: material3Theme,
+        home: const Scaffold(
           floatingActionButton: FloatingActionButton.extended(
             label: SizedBox(
               width: 100.0,
@@ -493,13 +510,16 @@
     );
 
     expect(getFabWidget().isExtended, true);
-    expect(getRawMaterialButtonWidget().shape, const StadiumBorder());
+    expect(
+        getRawMaterialButtonWidget().shape,
+        const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16.0)))
+    );
     expect(find.text('label'), findsOneWidget);
     expect(find.byType(Icon), findsNothing);
 
-    // Verify that the widget's height is 48 and that its internal
+    // Verify that the widget's height is 56 and that its internal
     /// horizontal layout is: 20 label 20
-    expect(tester.getSize(fabFinder).height, 48.0);
+    expect(tester.getSize(fabFinder).height, 56.0);
 
     final double fabLeft = tester.getTopLeft(fabFinder).dx;
     final double fabRight = tester.getTopRight(fabFinder).dx;
@@ -770,6 +790,7 @@
     final GlobalKey key = GlobalKey();
     await tester.pumpWidget(
       MaterialApp(
+        theme: material3Theme,
         home: Scaffold(
           body: Center(
             child: RepaintBoundary(
@@ -1054,6 +1075,289 @@
     expect(rawMaterialButton.textStyle, style.copyWith(color: const Color(0xffffffff)));
   });
 
+  group('Material 2', () {
+    // Tests that are only relevant for Material 2. Once ThemeData.useMaterial3
+    // is turned on by default, these tests can be removed.
+
+    testWidgets('Floating Action Button elevation when highlighted - effect', (WidgetTester tester) async {
+      await tester.pumpWidget(
+        MaterialApp(
+          theme: material2Theme,
+          home: Scaffold(
+            floatingActionButton: FloatingActionButton(
+              onPressed: () { },
+            ),
+          ),
+        ),
+      );
+      expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 6.0);
+      final TestGesture gesture = await tester.press(find.byType(PhysicalShape));
+      await tester.pump();
+      expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 6.0);
+      await tester.pump(const Duration(seconds: 1));
+      expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 12.0);
+      await tester.pumpWidget(
+        MaterialApp(
+          theme: material2Theme,
+          home: Scaffold(
+            floatingActionButton: FloatingActionButton(
+              onPressed: () { },
+              highlightElevation: 20.0,
+            ),
+          ),
+        ),
+      );
+      await tester.pump();
+      expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 12.0);
+      await tester.pump(const Duration(seconds: 1));
+      expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 20.0);
+      await gesture.up();
+      await tester.pump();
+      expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 20.0);
+      await tester.pump(const Duration(seconds: 1));
+      expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 6.0);
+    });
+
+    testWidgets('Floating Action Button elevation when disabled while highlighted - effect', (WidgetTester tester) async {
+      await tester.pumpWidget(
+        MaterialApp(
+          theme: material2Theme,
+          home: Scaffold(
+            floatingActionButton: FloatingActionButton(
+              onPressed: () { },
+            ),
+          ),
+        ),
+      );
+      expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 6.0);
+      await tester.press(find.byType(PhysicalShape));
+      await tester.pump();
+      expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 6.0);
+      await tester.pump(const Duration(seconds: 1));
+      expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 12.0);
+      await tester.pumpWidget(
+        MaterialApp(
+          theme: material2Theme,
+          home: const Scaffold(
+            floatingActionButton: FloatingActionButton(
+              onPressed: null,
+            ),
+          ),
+        ),
+      );
+      await tester.pump();
+      expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 12.0);
+      await tester.pump(const Duration(seconds: 1));
+      expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 6.0);
+      await tester.pumpWidget(
+        MaterialApp(
+          theme: material2Theme,
+          home: Scaffold(
+            floatingActionButton: FloatingActionButton(
+              onPressed: () { },
+            ),
+          ),
+        ),
+      );
+      await tester.pump();
+      expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 6.0);
+      await tester.pump(const Duration(seconds: 1));
+      expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 6.0);
+    });
+
+    testWidgets('Floating Action Button states elevation', (WidgetTester tester) async {
+      final FocusNode focusNode = FocusNode();
+
+      await tester.pumpWidget(
+        MaterialApp(
+          theme: material2Theme,
+          home: Scaffold(
+            body: FloatingActionButton.extended(
+              label: const Text('tooltip'),
+              onPressed: () {},
+              focusNode: focusNode,
+            ),
+          ),
+        ),
+      );
+
+      final Finder fabFinder = find.byType(PhysicalShape);
+      PhysicalShape getFABWidget(Finder finder) => tester.widget<PhysicalShape>(finder);
+
+      // Default, not disabled.
+      expect(getFABWidget(fabFinder).elevation, 6);
+
+      // Focused.
+      focusNode.requestFocus();
+      await tester.pumpAndSettle();
+      expect(getFABWidget(fabFinder).elevation, 6);
+
+      // Hovered.
+      final Offset center = tester.getCenter(fabFinder);
+      final TestGesture gesture = await tester.createGesture(
+        kind: PointerDeviceKind.mouse,
+      );
+      await gesture.addPointer();
+      addTearDown(gesture.removePointer);
+      await gesture.moveTo(center);
+      await tester.pumpAndSettle();
+      expect(getFABWidget(fabFinder).elevation, 8);
+
+      // Highlighted (pressed).
+      await gesture.down(center);
+      await tester.pump(); // Start the splash and highlight animations.
+      await tester.pump(const Duration(milliseconds: 800)); // Wait for splash and highlight to be well under way.
+      expect(getFABWidget(fabFinder).elevation, 12);
+    });
+
+    testWidgets('FloatingActionButton.isExtended', (WidgetTester tester) async {
+      await tester.pumpWidget(
+        MaterialApp(
+          theme: material2Theme,
+          home: const Scaffold(
+            floatingActionButton: FloatingActionButton(onPressed: null),
+          ),
+        ),
+      );
+
+      final Finder fabFinder = find.byType(FloatingActionButton);
+
+      FloatingActionButton getFabWidget() {
+        return tester.widget<FloatingActionButton>(fabFinder);
+      }
+
+      final Finder materialButtonFinder = find.byType(RawMaterialButton);
+
+      RawMaterialButton getRawMaterialButtonWidget() {
+        return tester.widget<RawMaterialButton>(materialButtonFinder);
+      }
+
+      expect(getFabWidget().isExtended, false);
+      expect(getRawMaterialButtonWidget().shape, const CircleBorder());
+
+      await tester.pumpWidget(
+        MaterialApp(
+          theme: material2Theme,
+          home: const Scaffold(
+            floatingActionButton: FloatingActionButton.extended(
+              label: SizedBox(
+                width: 100.0,
+                child: Text('label'),
+              ),
+              icon: Icon(Icons.android),
+              onPressed: null,
+            ),
+          ),
+        ),
+      );
+
+      expect(getFabWidget().isExtended, true);
+      expect(getRawMaterialButtonWidget().shape, const StadiumBorder());
+      expect(find.text('label'), findsOneWidget);
+      expect(find.byType(Icon), findsOneWidget);
+
+      // Verify that the widget's height is 48 and that its internal
+      /// horizontal layout is: 16 icon 8 label 20
+      expect(tester.getSize(fabFinder).height, 48.0);
+
+      final double fabLeft = tester.getTopLeft(fabFinder).dx;
+      final double fabRight = tester.getTopRight(fabFinder).dx;
+      final double iconLeft = tester.getTopLeft(find.byType(Icon)).dx;
+      final double iconRight = tester.getTopRight(find.byType(Icon)).dx;
+      final double labelLeft = tester.getTopLeft(find.text('label')).dx;
+      final double labelRight = tester.getTopRight(find.text('label')).dx;
+      expect(iconLeft - fabLeft, 16.0);
+      expect(labelLeft - iconRight, 8.0);
+      expect(fabRight - labelRight, 20.0);
+
+      // The overall width of the button is:
+      // 168 = 16 + 24(icon) + 8 + 100(label) + 20
+      expect(tester.getSize(find.byType(Icon)).width, 24.0);
+      expect(tester.getSize(find.text('label')).width, 100.0);
+      expect(tester.getSize(fabFinder).width, 168);
+    });
+
+    testWidgets('FloatingActionButton.isExtended (without icon)', (WidgetTester tester) async {
+      final Finder fabFinder = find.byType(FloatingActionButton);
+
+      FloatingActionButton getFabWidget() {
+        return tester.widget<FloatingActionButton>(fabFinder);
+      }
+
+      final Finder materialButtonFinder = find.byType(RawMaterialButton);
+
+      RawMaterialButton getRawMaterialButtonWidget() {
+        return tester.widget<RawMaterialButton>(materialButtonFinder);
+      }
+
+      await tester.pumpWidget(
+        MaterialApp(
+          theme: material2Theme,
+          home: const Scaffold(
+            floatingActionButton: FloatingActionButton.extended(
+              label: SizedBox(
+                width: 100.0,
+                child: Text('label'),
+              ),
+              onPressed: null,
+            ),
+          ),
+        ),
+      );
+
+      expect(getFabWidget().isExtended, true);
+      expect(getRawMaterialButtonWidget().shape, const StadiumBorder());
+      expect(find.text('label'), findsOneWidget);
+      expect(find.byType(Icon), findsNothing);
+
+      // Verify that the widget's height is 48 and that its internal
+      /// horizontal layout is: 20 label 20
+      expect(tester.getSize(fabFinder).height, 48.0);
+
+      final double fabLeft = tester.getTopLeft(fabFinder).dx;
+      final double fabRight = tester.getTopRight(fabFinder).dx;
+      final double labelLeft = tester.getTopLeft(find.text('label')).dx;
+      final double labelRight = tester.getTopRight(find.text('label')).dx;
+      expect(labelLeft - fabLeft, 20.0);
+      expect(fabRight - labelRight, 20.0);
+
+      // The overall width of the button is:
+      // 140 = 20 + 100(label) + 20
+      expect(tester.getSize(find.text('label')).width, 100.0);
+      expect(tester.getSize(fabFinder).width, 140);
+    });
+
+
+    // This test prevents https://github.com/flutter/flutter/issues/20483
+    testWidgets('Floating Action Button clips ink splash and highlight', (WidgetTester tester) async {
+      final GlobalKey key = GlobalKey();
+      await tester.pumpWidget(
+        MaterialApp(
+          theme: material2Theme,
+          home: Scaffold(
+            body: Center(
+              child: RepaintBoundary(
+                key: key,
+                child: FloatingActionButton(
+                  onPressed: () { },
+                  child: const Icon(Icons.add),
+                ),
+              ),
+            ),
+          ),
+        ),
+      );
+
+      await tester.press(find.byKey(key));
+      await tester.pump();
+      await tester.pump(const Duration(milliseconds: 1000));
+      await expectLater(
+        find.byKey(key),
+        matchesGoldenFile('floating_action_button_test_m2.clip.png'),
+      );
+    });
+  });
+
   group('feedback', () {
     late FeedbackTester feedback;
 
diff --git a/packages/flutter/test/material/floating_action_button_theme_test.dart b/packages/flutter/test/material/floating_action_button_theme_test.dart
index 1b302a6..5cd5d27 100644
--- a/packages/flutter/test/material/floating_action_button_theme_test.dart
+++ b/packages/flutter/test/material/floating_action_button_theme_test.dart
@@ -33,6 +33,8 @@
     expect(_getRawMaterialButton(tester).shape, const CircleBorder());
     expect(_getRawMaterialButton(tester).splashColor, ThemeData().splashColor);
     expect(_getRawMaterialButton(tester).constraints, const BoxConstraints.tightFor(width: 56.0, height: 56.0));
+    expect(_getIconSize(tester).width, 24.0);
+    expect(_getIconSize(tester).height, 24.0);
   });
 
   testWidgets('FloatingActionButtonThemeData values are used when no FloatingActionButton properties are specified', (WidgetTester tester) async {
@@ -138,6 +140,7 @@
 
   testWidgets('FloatingActionButton.small uses custom constraints when specified in the theme', (WidgetTester tester) async {
     const BoxConstraints constraints = BoxConstraints.tightFor(width: 100.0, height: 100.0);
+    const double iconSize = 24.0;
 
     await tester.pumpWidget(MaterialApp(
       theme: ThemeData().copyWith(
@@ -154,10 +157,13 @@
     ));
 
     expect(_getRawMaterialButton(tester).constraints, constraints);
+    expect(_getIconSize(tester).width, iconSize);
+    expect(_getIconSize(tester).height, iconSize);
   });
 
   testWidgets('FloatingActionButton.large uses custom constraints when specified in the theme', (WidgetTester tester) async {
     const BoxConstraints constraints = BoxConstraints.tightFor(width: 100.0, height: 100.0);
+    const double iconSize = 36.0;
 
     await tester.pumpWidget(MaterialApp(
       theme: ThemeData().copyWith(
@@ -174,6 +180,8 @@
     ));
 
     expect(_getRawMaterialButton(tester).constraints, constraints);
+    expect(_getIconSize(tester).width, iconSize);
+    expect(_getIconSize(tester).height, iconSize);
   });
 
   testWidgets('FloatingActionButton.extended uses custom properties when specified in the theme', (WidgetTester tester) async {
@@ -271,6 +279,7 @@
       highlightElevation: 43,
       shape: BeveledRectangleBorder(),
       enableFeedback: true,
+      iconSize: 42,
       sizeConstraints: BoxConstraints.tightFor(width: 100.0, height: 100.0),
       smallSizeConstraints: BoxConstraints.tightFor(width: 101.0, height: 101.0),
       largeSizeConstraints: BoxConstraints.tightFor(width: 102.0, height: 102.0),
@@ -298,6 +307,7 @@
       'highlightElevation: 43.0',
       'shape: BeveledRectangleBorder(BorderSide(Color(0xff000000), 0.0, BorderStyle.none), BorderRadius.zero)',
       'enableFeedback: true',
+      'iconSize: 42.0',
       'sizeConstraints: BoxConstraints(w=100.0, h=100.0)',
       'smallSizeConstraints: BoxConstraints(w=101.0, h=101.0)',
       'largeSizeConstraints: BoxConstraints(w=102.0, h=102.0)',
@@ -326,3 +336,15 @@
     ),
   );
 }
+
+SizedBox _getIconSize(WidgetTester tester) {
+  return tester.widget<SizedBox>(
+    find.descendant(
+      of: find.descendant(
+        of: find.byType(FloatingActionButton),
+        matching: find.byType(Icon),
+      ),
+      matching: find.byType(SizedBox),
+    ),
+  );
+}