Added TabBar.splashFactory, TabBarTheme.splashFactory,overlayColor (#96252)

diff --git a/packages/flutter/lib/src/material/tab_bar_theme.dart b/packages/flutter/lib/src/material/tab_bar_theme.dart
index 49a95fe..ff442fe 100644
--- a/packages/flutter/lib/src/material/tab_bar_theme.dart
+++ b/packages/flutter/lib/src/material/tab_bar_theme.dart
@@ -5,6 +5,8 @@
 import 'package:flutter/foundation.dart';
 import 'package:flutter/widgets.dart';
 
+import 'ink_well.dart';
+import 'material_state.dart';
 import 'tabs.dart';
 import 'theme.dart';
 
@@ -33,6 +35,8 @@
     this.labelStyle,
     this.unselectedLabelColor,
     this.unselectedLabelStyle,
+    this.overlayColor,
+    this.splashFactory,
   });
 
   /// Default value for [TabBar.indicator].
@@ -60,6 +64,12 @@
   /// Default value for [TabBar.unselectedLabelStyle].
   final TextStyle? unselectedLabelStyle;
 
+  /// Default value for [TabBar.overlayColor].
+  final MaterialStateProperty<Color?>? overlayColor;
+
+  /// Default value for [TabBar.splashFactory].
+  final InteractiveInkFeatureFactory? splashFactory;
+
   /// Creates a copy of this object but with the given fields replaced with the
   /// new values.
   TabBarTheme copyWith({
@@ -70,6 +80,8 @@
     TextStyle? labelStyle,
     Color? unselectedLabelColor,
     TextStyle? unselectedLabelStyle,
+    MaterialStateProperty<Color?>? overlayColor,
+    InteractiveInkFeatureFactory? splashFactory,
   }) {
     return TabBarTheme(
       indicator: indicator ?? this.indicator,
@@ -79,6 +91,8 @@
       labelStyle: labelStyle ?? this.labelStyle,
       unselectedLabelColor: unselectedLabelColor ?? this.unselectedLabelColor,
       unselectedLabelStyle: unselectedLabelStyle ?? this.unselectedLabelStyle,
+      overlayColor: overlayColor ?? this.overlayColor,
+      splashFactory: splashFactory ?? this.splashFactory,
     );
   }
 
@@ -104,6 +118,8 @@
       labelStyle: TextStyle.lerp(a.labelStyle, b.labelStyle, t),
       unselectedLabelColor: Color.lerp(a.unselectedLabelColor, b.unselectedLabelColor, t),
       unselectedLabelStyle: TextStyle.lerp(a.unselectedLabelStyle, b.unselectedLabelStyle, t),
+      overlayColor: _LerpColors(a.overlayColor, b.overlayColor, t),
+      splashFactory: t < 0.5 ? a.splashFactory : b.splashFactory,
     );
   }
 
@@ -117,6 +133,8 @@
       labelStyle,
       unselectedLabelColor,
       unselectedLabelStyle,
+      overlayColor,
+      splashFactory,
     );
   }
 
@@ -133,6 +151,42 @@
         && other.labelPadding == labelPadding
         && other.labelStyle == labelStyle
         && other.unselectedLabelColor == unselectedLabelColor
-        && other.unselectedLabelStyle == unselectedLabelStyle;
+        && other.unselectedLabelStyle == unselectedLabelStyle
+        && other.overlayColor == overlayColor
+        && other.splashFactory == splashFactory;
+  }
+}
+
+
+@immutable
+class _LerpColors implements MaterialStateProperty<Color?> {
+  const _LerpColors(this.a, this.b, this.t);
+
+  final MaterialStateProperty<Color?>? a;
+  final MaterialStateProperty<Color?>? b;
+  final double t;
+
+  @override
+  Color? resolve(Set<MaterialState> states) {
+    final Color? resolvedA = a?.resolve(states);
+    final Color? resolvedB = b?.resolve(states);
+    return Color.lerp(resolvedA, resolvedB, t);
+  }
+
+  @override
+  int get hashCode {
+    return hashValues(a, b, t);
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other))
+      return true;
+    if (other.runtimeType != runtimeType)
+      return false;
+    return other is _LerpColors
+      && other.a == a
+      && other.b == b
+      && other.t == t;
   }
 }
diff --git a/packages/flutter/lib/src/material/tabs.dart b/packages/flutter/lib/src/material/tabs.dart
index 4028120..e1504a4 100644
--- a/packages/flutter/lib/src/material/tabs.dart
+++ b/packages/flutter/lib/src/material/tabs.dart
@@ -643,6 +643,7 @@
     this.enableFeedback,
     this.onTap,
     this.physics,
+    this.splashFactory,
   }) : assert(tabs != null),
        assert(isScrollable != null),
        assert(dragStartBehavior != null),
@@ -786,14 +787,11 @@
   /// [MaterialState.hovered], and [MaterialState.pressed].
   ///
   /// [MaterialState.pressed] triggers a ripple (an ink splash), per
-  /// the current Material Design spec. The [overlayColor] doesn't map
-  /// a state to [InkResponse.highlightColor] because a separate highlight
-  /// is not used by the current design guidelines. See
-  /// https://material.io/design/interaction/states.html#pressed
+  /// the current Material Design spec.
   ///
   /// If the overlay color is null or resolves to null, then the default values
-  /// for [InkResponse.focusColor], [InkResponse.hoverColor], [InkResponse.splashColor]
-  /// will be used instead.
+  /// for [InkResponse.focusColor], [InkResponse.hoverColor], [InkResponse.splashColor],
+  /// and [InkResponse.highlightColor] will be used instead.
   final MaterialStateProperty<Color?>? overlayColor;
 
   /// {@macro flutter.widgets.scrollable.dragStartBehavior}
@@ -832,6 +830,25 @@
   /// Defaults to matching platform conventions.
   final ScrollPhysics? physics;
 
+  /// Creates the tab bar's [InkWell] splash factory, which defines
+  /// the appearance of "ink" splashes that occur in response to taps.
+  ///
+  /// Use [NoSplash.splashFactory] to defeat ink splash rendering. For example
+  /// to defeat both the splash and the hover/pressed overlay, but not the
+  /// keyboard focused overlay:
+  /// ```dart
+  /// TabBar(
+  ///   splashFactory: NoSplash.splashFactory,
+  ///   overlayColor: MaterialStateProperty.resolveWith<Color?>(
+  ///     (Set<MaterialState> states) {
+  ///       return states.contains(MaterialState.focused) ? null : Colors.transparent;
+  ///     },
+  ///   ),
+  ///   ...
+  /// )
+  /// ```
+  final InteractiveInkFeatureFactory? splashFactory;
+
   /// A size whose height depends on if the tabs have both icons and text.
   ///
   /// [AppBar] uses this size to compute its own preferred size.
@@ -1187,7 +1204,8 @@
         mouseCursor: widget.mouseCursor ?? SystemMouseCursors.click,
         onTap: () { _handleTap(index); },
         enableFeedback: widget.enableFeedback ?? true,
-        overlayColor: widget.overlayColor,
+        overlayColor: widget.overlayColor ?? tabBarTheme.overlayColor,
+        splashFactory: widget.splashFactory ?? tabBarTheme.splashFactory,
         child: Padding(
           padding: EdgeInsets.only(bottom: widget.indicatorWeight),
           child: Stack(
diff --git a/packages/flutter/test/material/tab_bar_theme_test.dart b/packages/flutter/test/material/tab_bar_theme_test.dart
index 319554c..33fb1ab 100644
--- a/packages/flutter/test/material/tab_bar_theme_test.dart
+++ b/packages/flutter/test/material/tab_bar_theme_test.dart
@@ -54,6 +54,21 @@
 }
 
 void main() {
+  test('TabBarTheme copyWith, ==, hashCode, defaults', () {
+    expect(const TabBarTheme(), const TabBarTheme().copyWith());
+    expect(const TabBarTheme().hashCode, const TabBarTheme().copyWith().hashCode);
+
+    expect(const TabBarTheme().indicator, null);
+    expect(const TabBarTheme().indicatorSize, null);
+    expect(const TabBarTheme().labelColor, null);
+    expect(const TabBarTheme().labelPadding, null);
+    expect(const TabBarTheme().labelStyle, null);
+    expect(const TabBarTheme().unselectedLabelColor, null);
+    expect(const TabBarTheme().unselectedLabelStyle, null);
+    expect(const TabBarTheme().overlayColor, null);
+    expect(const TabBarTheme().splashFactory, null);
+  });
+
   testWidgets('Tab bar defaults - label style and selected/unselected label colors', (WidgetTester tester) async {
     // tests for the default label color and label styles when tabBarTheme and tabBar do not provide any
     await tester.pumpWidget(_withTheme(null));
diff --git a/packages/flutter/test/material/tabs_test.dart b/packages/flutter/test/material/tabs_test.dart
index 6691f61..3c98bb1 100644
--- a/packages/flutter/test/material/tabs_test.dart
+++ b/packages/flutter/test/material/tabs_test.dart
@@ -4322,6 +4322,62 @@
     expect(controller3.index, 2);
     expect(pageController.page, 2);
   });
+
+  testWidgets('TabBar InkWell splashFactory and overlayColor', (WidgetTester tester) async {
+    const InteractiveInkFeatureFactory splashFactory = NoSplash.splashFactory;
+    final MaterialStateProperty<Color?> overlayColor = MaterialStateProperty.resolveWith<Color?>(
+      (Set<MaterialState> states) => Colors.transparent,
+    );
+
+    // TabBarTheme splashFactory and overlayColor
+    await tester.pumpWidget(
+      MaterialApp(
+        theme: ThemeData.light().copyWith(
+          tabBarTheme: TabBarTheme(
+            splashFactory: splashFactory,
+            overlayColor: overlayColor,
+          )),
+        home: DefaultTabController(
+          length: 1,
+          child: Scaffold(
+            appBar: AppBar(
+              bottom: TabBar(
+                tabs: <Widget>[
+                  Container(width: 100, height: 100, color: Colors.green),
+                ],
+              ),
+            ),
+          ),
+        ),
+      ),
+    );
+
+    expect(tester.widget<InkWell>(find.byType(InkWell)).splashFactory, splashFactory);
+    expect(tester.widget<InkWell>(find.byType(InkWell)).overlayColor, overlayColor);
+
+    // TabBar splashFactory and overlayColor
+    await tester.pumpWidget(
+      MaterialApp(
+        home: DefaultTabController(
+          length: 1,
+          child: Scaffold(
+            appBar: AppBar(
+              bottom: TabBar(
+                splashFactory: splashFactory,
+                overlayColor: overlayColor,
+                tabs: <Widget>[
+                  Container(width: 100, height: 100, color: Colors.green),
+                ],
+              ),
+            ),
+          ),
+        ),
+      ),
+    );
+    await tester.pumpAndSettle(); // theme animation
+    expect(tester.widget<InkWell>(find.byType(InkWell)).splashFactory, splashFactory);
+    expect(tester.widget<InkWell>(find.byType(InkWell)).overlayColor, overlayColor);
+  });
 }
 
 class KeepAliveInk extends StatefulWidget {