Skip to content

Commit bbc68cd

Browse files
authored
Added TabBar.splashFactory, TabBarTheme.splashFactory,overlayColor (flutter#96252)
1 parent 1612310 commit bbc68cd

File tree

4 files changed

+151
-8
lines changed

4 files changed

+151
-8
lines changed

packages/flutter/lib/src/material/tab_bar_theme.dart

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import 'package:flutter/foundation.dart';
66
import 'package:flutter/widgets.dart';
77

8+
import 'ink_well.dart';
9+
import 'material_state.dart';
810
import 'tabs.dart';
911
import 'theme.dart';
1012

@@ -33,6 +35,8 @@ class TabBarTheme with Diagnosticable {
3335
this.labelStyle,
3436
this.unselectedLabelColor,
3537
this.unselectedLabelStyle,
38+
this.overlayColor,
39+
this.splashFactory,
3640
});
3741

3842
/// Default value for [TabBar.indicator].
@@ -60,6 +64,12 @@ class TabBarTheme with Diagnosticable {
6064
/// Default value for [TabBar.unselectedLabelStyle].
6165
final TextStyle? unselectedLabelStyle;
6266

67+
/// Default value for [TabBar.overlayColor].
68+
final MaterialStateProperty<Color?>? overlayColor;
69+
70+
/// Default value for [TabBar.splashFactory].
71+
final InteractiveInkFeatureFactory? splashFactory;
72+
6373
/// Creates a copy of this object but with the given fields replaced with the
6474
/// new values.
6575
TabBarTheme copyWith({
@@ -70,6 +80,8 @@ class TabBarTheme with Diagnosticable {
7080
TextStyle? labelStyle,
7181
Color? unselectedLabelColor,
7282
TextStyle? unselectedLabelStyle,
83+
MaterialStateProperty<Color?>? overlayColor,
84+
InteractiveInkFeatureFactory? splashFactory,
7385
}) {
7486
return TabBarTheme(
7587
indicator: indicator ?? this.indicator,
@@ -79,6 +91,8 @@ class TabBarTheme with Diagnosticable {
7991
labelStyle: labelStyle ?? this.labelStyle,
8092
unselectedLabelColor: unselectedLabelColor ?? this.unselectedLabelColor,
8193
unselectedLabelStyle: unselectedLabelStyle ?? this.unselectedLabelStyle,
94+
overlayColor: overlayColor ?? this.overlayColor,
95+
splashFactory: splashFactory ?? this.splashFactory,
8296
);
8397
}
8498

@@ -104,6 +118,8 @@ class TabBarTheme with Diagnosticable {
104118
labelStyle: TextStyle.lerp(a.labelStyle, b.labelStyle, t),
105119
unselectedLabelColor: Color.lerp(a.unselectedLabelColor, b.unselectedLabelColor, t),
106120
unselectedLabelStyle: TextStyle.lerp(a.unselectedLabelStyle, b.unselectedLabelStyle, t),
121+
overlayColor: _LerpColors(a.overlayColor, b.overlayColor, t),
122+
splashFactory: t < 0.5 ? a.splashFactory : b.splashFactory,
107123
);
108124
}
109125

@@ -117,6 +133,8 @@ class TabBarTheme with Diagnosticable {
117133
labelStyle,
118134
unselectedLabelColor,
119135
unselectedLabelStyle,
136+
overlayColor,
137+
splashFactory,
120138
);
121139
}
122140

@@ -133,6 +151,42 @@ class TabBarTheme with Diagnosticable {
133151
&& other.labelPadding == labelPadding
134152
&& other.labelStyle == labelStyle
135153
&& other.unselectedLabelColor == unselectedLabelColor
136-
&& other.unselectedLabelStyle == unselectedLabelStyle;
154+
&& other.unselectedLabelStyle == unselectedLabelStyle
155+
&& other.overlayColor == overlayColor
156+
&& other.splashFactory == splashFactory;
157+
}
158+
}
159+
160+
161+
@immutable
162+
class _LerpColors implements MaterialStateProperty<Color?> {
163+
const _LerpColors(this.a, this.b, this.t);
164+
165+
final MaterialStateProperty<Color?>? a;
166+
final MaterialStateProperty<Color?>? b;
167+
final double t;
168+
169+
@override
170+
Color? resolve(Set<MaterialState> states) {
171+
final Color? resolvedA = a?.resolve(states);
172+
final Color? resolvedB = b?.resolve(states);
173+
return Color.lerp(resolvedA, resolvedB, t);
174+
}
175+
176+
@override
177+
int get hashCode {
178+
return hashValues(a, b, t);
179+
}
180+
181+
@override
182+
bool operator ==(Object other) {
183+
if (identical(this, other))
184+
return true;
185+
if (other.runtimeType != runtimeType)
186+
return false;
187+
return other is _LerpColors
188+
&& other.a == a
189+
&& other.b == b
190+
&& other.t == t;
137191
}
138192
}

packages/flutter/lib/src/material/tabs.dart

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -643,6 +643,7 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget {
643643
this.enableFeedback,
644644
this.onTap,
645645
this.physics,
646+
this.splashFactory,
646647
}) : assert(tabs != null),
647648
assert(isScrollable != null),
648649
assert(dragStartBehavior != null),
@@ -786,14 +787,11 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget {
786787
/// [MaterialState.hovered], and [MaterialState.pressed].
787788
///
788789
/// [MaterialState.pressed] triggers a ripple (an ink splash), per
789-
/// the current Material Design spec. The [overlayColor] doesn't map
790-
/// a state to [InkResponse.highlightColor] because a separate highlight
791-
/// is not used by the current design guidelines. See
792-
/// https://material.io/design/interaction/states.html#pressed
790+
/// the current Material Design spec.
793791
///
794792
/// If the overlay color is null or resolves to null, then the default values
795-
/// for [InkResponse.focusColor], [InkResponse.hoverColor], [InkResponse.splashColor]
796-
/// will be used instead.
793+
/// for [InkResponse.focusColor], [InkResponse.hoverColor], [InkResponse.splashColor],
794+
/// and [InkResponse.highlightColor] will be used instead.
797795
final MaterialStateProperty<Color?>? overlayColor;
798796

799797
/// {@macro flutter.widgets.scrollable.dragStartBehavior}
@@ -832,6 +830,25 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget {
832830
/// Defaults to matching platform conventions.
833831
final ScrollPhysics? physics;
834832

833+
/// Creates the tab bar's [InkWell] splash factory, which defines
834+
/// the appearance of "ink" splashes that occur in response to taps.
835+
///
836+
/// Use [NoSplash.splashFactory] to defeat ink splash rendering. For example
837+
/// to defeat both the splash and the hover/pressed overlay, but not the
838+
/// keyboard focused overlay:
839+
/// ```dart
840+
/// TabBar(
841+
/// splashFactory: NoSplash.splashFactory,
842+
/// overlayColor: MaterialStateProperty.resolveWith<Color?>(
843+
/// (Set<MaterialState> states) {
844+
/// return states.contains(MaterialState.focused) ? null : Colors.transparent;
845+
/// },
846+
/// ),
847+
/// ...
848+
/// )
849+
/// ```
850+
final InteractiveInkFeatureFactory? splashFactory;
851+
835852
/// A size whose height depends on if the tabs have both icons and text.
836853
///
837854
/// [AppBar] uses this size to compute its own preferred size.
@@ -1187,7 +1204,8 @@ class _TabBarState extends State<TabBar> {
11871204
mouseCursor: widget.mouseCursor ?? SystemMouseCursors.click,
11881205
onTap: () { _handleTap(index); },
11891206
enableFeedback: widget.enableFeedback ?? true,
1190-
overlayColor: widget.overlayColor,
1207+
overlayColor: widget.overlayColor ?? tabBarTheme.overlayColor,
1208+
splashFactory: widget.splashFactory ?? tabBarTheme.splashFactory,
11911209
child: Padding(
11921210
padding: EdgeInsets.only(bottom: widget.indicatorWeight),
11931211
child: Stack(

packages/flutter/test/material/tab_bar_theme_test.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,21 @@ RenderParagraph _iconRenderObject(WidgetTester tester, IconData icon) {
5454
}
5555

5656
void main() {
57+
test('TabBarTheme copyWith, ==, hashCode, defaults', () {
58+
expect(const TabBarTheme(), const TabBarTheme().copyWith());
59+
expect(const TabBarTheme().hashCode, const TabBarTheme().copyWith().hashCode);
60+
61+
expect(const TabBarTheme().indicator, null);
62+
expect(const TabBarTheme().indicatorSize, null);
63+
expect(const TabBarTheme().labelColor, null);
64+
expect(const TabBarTheme().labelPadding, null);
65+
expect(const TabBarTheme().labelStyle, null);
66+
expect(const TabBarTheme().unselectedLabelColor, null);
67+
expect(const TabBarTheme().unselectedLabelStyle, null);
68+
expect(const TabBarTheme().overlayColor, null);
69+
expect(const TabBarTheme().splashFactory, null);
70+
});
71+
5772
testWidgets('Tab bar defaults - label style and selected/unselected label colors', (WidgetTester tester) async {
5873
// tests for the default label color and label styles when tabBarTheme and tabBar do not provide any
5974
await tester.pumpWidget(_withTheme(null));

packages/flutter/test/material/tabs_test.dart

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4322,6 +4322,62 @@ void main() {
43224322
expect(controller3.index, 2);
43234323
expect(pageController.page, 2);
43244324
});
4325+
4326+
testWidgets('TabBar InkWell splashFactory and overlayColor', (WidgetTester tester) async {
4327+
const InteractiveInkFeatureFactory splashFactory = NoSplash.splashFactory;
4328+
final MaterialStateProperty<Color?> overlayColor = MaterialStateProperty.resolveWith<Color?>(
4329+
(Set<MaterialState> states) => Colors.transparent,
4330+
);
4331+
4332+
// TabBarTheme splashFactory and overlayColor
4333+
await tester.pumpWidget(
4334+
MaterialApp(
4335+
theme: ThemeData.light().copyWith(
4336+
tabBarTheme: TabBarTheme(
4337+
splashFactory: splashFactory,
4338+
overlayColor: overlayColor,
4339+
)),
4340+
home: DefaultTabController(
4341+
length: 1,
4342+
child: Scaffold(
4343+
appBar: AppBar(
4344+
bottom: TabBar(
4345+
tabs: <Widget>[
4346+
Container(width: 100, height: 100, color: Colors.green),
4347+
],
4348+
),
4349+
),
4350+
),
4351+
),
4352+
),
4353+
);
4354+
4355+
expect(tester.widget<InkWell>(find.byType(InkWell)).splashFactory, splashFactory);
4356+
expect(tester.widget<InkWell>(find.byType(InkWell)).overlayColor, overlayColor);
4357+
4358+
// TabBar splashFactory and overlayColor
4359+
await tester.pumpWidget(
4360+
MaterialApp(
4361+
home: DefaultTabController(
4362+
length: 1,
4363+
child: Scaffold(
4364+
appBar: AppBar(
4365+
bottom: TabBar(
4366+
splashFactory: splashFactory,
4367+
overlayColor: overlayColor,
4368+
tabs: <Widget>[
4369+
Container(width: 100, height: 100, color: Colors.green),
4370+
],
4371+
),
4372+
),
4373+
),
4374+
),
4375+
),
4376+
);
4377+
await tester.pumpAndSettle(); // theme animation
4378+
expect(tester.widget<InkWell>(find.byType(InkWell)).splashFactory, splashFactory);
4379+
expect(tester.widget<InkWell>(find.byType(InkWell)).overlayColor, overlayColor);
4380+
});
43254381
}
43264382

43274383
class KeepAliveInk extends StatefulWidget {

0 commit comments

Comments
 (0)