Skip to content

Commit 10ae869

Browse files
authored
Added MaterialStateProperty overlayColor to InkWell (flutter#58650)
1 parent bbe18f7 commit 10ae869

File tree

2 files changed

+199
-3
lines changed

2 files changed

+199
-3
lines changed

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

+41-3
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,7 @@ class InkResponse extends StatelessWidget {
306306
this.focusColor,
307307
this.hoverColor,
308308
this.highlightColor,
309+
this.overlayColor,
309310
this.splashColor,
310311
this.splashFactory,
311312
this.enableFeedback = true,
@@ -479,6 +480,31 @@ class InkResponse extends StatelessWidget {
479480
/// * [splashFactory], which defines the appearance of the splash.
480481
final Color highlightColor;
481482

483+
/// Defines the ink response focus, hover, and splash colors.
484+
///
485+
/// This default null property can be used as an alternative to
486+
/// [focusColor], [hoverColor], and [splashColor]. If non-null,
487+
/// it is resolved against one of [MaterialState.focused],
488+
/// [MaterialState.hovered], and [MaterialState.pressed]. It's
489+
/// convenient to use when the parent widget can pass along its own
490+
/// MaterialStateProperty value for the overlay color.
491+
///
492+
/// [MaterialState.pressed] triggers a ripple (an ink splash), per
493+
/// the current Material Design spec. The [overlayColor] doesn't map
494+
/// a state to [highlightColor] because a separate highlight is not
495+
/// used by the current design guidelines. See
496+
/// https://material.io/design/interaction/states.html#pressed
497+
///
498+
/// If the overlay color is null or resolves to null, then [focusColor],
499+
/// [hoverColor], [splashColor] and their defaults are used instead.
500+
///
501+
/// See also:
502+
///
503+
/// * The Material Design specification for overlay colors and how they
504+
/// to a component's state:
505+
/// <https://material.io/design/interaction/states.html#anatomy>.
506+
final MaterialStateProperty<Color> overlayColor;
507+
482508
/// The splash color of the ink response. If this property is null then the
483509
/// splash color of the theme, [ThemeData.splashColor], will be used.
484510
///
@@ -571,6 +597,7 @@ class InkResponse extends StatelessWidget {
571597
focusColor: focusColor,
572598
hoverColor: hoverColor,
573599
highlightColor: highlightColor,
600+
overlayColor: overlayColor,
574601
splashColor: splashColor,
575602
splashFactory: splashFactory,
576603
enableFeedback: enableFeedback,
@@ -619,6 +646,7 @@ class _InkResponseStateWidget extends StatefulWidget {
619646
this.focusColor,
620647
this.hoverColor,
621648
this.highlightColor,
649+
this.overlayColor,
622650
this.splashColor,
623651
this.splashFactory,
624652
this.enableFeedback = true,
@@ -654,6 +682,7 @@ class _InkResponseStateWidget extends StatefulWidget {
654682
final Color focusColor;
655683
final Color hoverColor;
656684
final Color highlightColor;
685+
final MaterialStateProperty<Color> overlayColor;
657686
final Color splashColor;
658687
final InteractiveInkFeatureFactory splashFactory;
659688
final bool enableFeedback;
@@ -760,13 +789,19 @@ class _InkResponseState extends State<_InkResponseStateWidget>
760789
bool get wantKeepAlive => highlightsExist || (_splashes != null && _splashes.isNotEmpty);
761790

762791
Color getHighlightColorForType(_HighlightType type) {
792+
const Set<MaterialState> focused = <MaterialState>{MaterialState.focused};
793+
const Set<MaterialState> hovered = <MaterialState>{MaterialState.hovered};
794+
763795
switch (type) {
796+
// The pressed state triggers a ripple (ink splash), per the current
797+
// Material Design spec. A separate highlight is no longer used.
798+
// See https://material.io/design/interaction/states.html#pressed
764799
case _HighlightType.pressed:
765800
return widget.highlightColor ?? Theme.of(context).highlightColor;
766801
case _HighlightType.focus:
767-
return widget.focusColor ?? Theme.of(context).focusColor;
802+
return widget.overlayColor?.resolve(focused) ?? widget.focusColor ?? Theme.of(context).focusColor;
768803
case _HighlightType.hover:
769-
return widget.hoverColor ?? Theme.of(context).hoverColor;
804+
return widget.overlayColor?.resolve(hovered) ?? widget.hoverColor ?? Theme.of(context).hoverColor;
770805
}
771806
assert(false, 'Unhandled $_HighlightType $type');
772807
return null;
@@ -839,7 +874,8 @@ class _InkResponseState extends State<_InkResponseStateWidget>
839874
final MaterialInkController inkController = Material.of(context);
840875
final RenderBox referenceBox = context.findRenderObject() as RenderBox;
841876
final Offset position = referenceBox.globalToLocal(globalPosition);
842-
final Color color = widget.splashColor ?? Theme.of(context).splashColor;
877+
const Set<MaterialState> pressed = <MaterialState>{MaterialState.pressed};
878+
final Color color = widget.overlayColor?.resolve(pressed) ?? widget.splashColor ?? Theme.of(context).splashColor;
843879
final RectCallback rectCallback = widget.containedInkWell ? widget.getRectCallback(referenceBox) : null;
844880
final BorderRadius borderRadius = widget.borderRadius;
845881
final ShapeBorder customBorder = widget.customBorder;
@@ -1180,6 +1216,7 @@ class InkWell extends InkResponse {
11801216
Color focusColor,
11811217
Color hoverColor,
11821218
Color highlightColor,
1219+
MaterialStateProperty<Color> overlayColor,
11831220
Color splashColor,
11841221
InteractiveInkFeatureFactory splashFactory,
11851222
double radius,
@@ -1207,6 +1244,7 @@ class InkWell extends InkResponse {
12071244
focusColor: focusColor,
12081245
hoverColor: hoverColor,
12091246
highlightColor: highlightColor,
1247+
overlayColor: overlayColor,
12101248
splashColor: splashColor,
12111249
splashFactory: splashFactory,
12121250
radius: radius,

packages/flutter/test/material/ink_well_test.dart

+158
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,43 @@ void main() {
120120
expect(inkFeatures, paints..rect(rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0), color: const Color(0xff00ff00)));
121121
});
122122

123+
testWidgets('ink well changes color on hover with overlayColor', (WidgetTester tester) async {
124+
// Same test as 'ink well changes color on hover' except that the
125+
// hover color is specified with the overlayColor parameter.
126+
await tester.pumpWidget(Material(
127+
child: Directionality(
128+
textDirection: TextDirection.ltr,
129+
child: Center(
130+
child: Container(
131+
width: 100,
132+
height: 100,
133+
child: InkWell(
134+
overlayColor: MaterialStateProperty.resolveWith<Color>((Set<MaterialState> states) {
135+
if (states.contains(MaterialState.hovered))
136+
return const Color(0xff00ff00);
137+
if (states.contains(MaterialState.focused))
138+
return const Color(0xff0000ff);
139+
if (states.contains(MaterialState.pressed))
140+
return const Color(0xf00fffff);
141+
return const Color(0xffbadbad); // Shouldn't happen.
142+
}),
143+
onTap: () { },
144+
onLongPress: () { },
145+
onHover: (bool hover) { },
146+
),
147+
),
148+
),
149+
),
150+
));
151+
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
152+
await gesture.addPointer();
153+
addTearDown(gesture.removePointer);
154+
await gesture.moveTo(tester.getCenter(find.byType(Container)));
155+
await tester.pumpAndSettle();
156+
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
157+
expect(inkFeatures, paints..rect(rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0), color: const Color(0xff00ff00)));
158+
});
159+
123160
testWidgets('ink response changes color on focus', (WidgetTester tester) async {
124161
FocusManager.instance.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
125162
final FocusNode focusNode = FocusNode(debugLabel: 'Ink Focus');
@@ -155,6 +192,127 @@ void main() {
155192
..rect(rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0), color: const Color(0xff0000ff)));
156193
});
157194

195+
testWidgets('ink response changes color on focus with overlayColor', (WidgetTester tester) async {
196+
// Same test as 'ink well changes color on focus' except that the
197+
// hover color is specified with the overlayColor parameter.
198+
FocusManager.instance.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
199+
final FocusNode focusNode = FocusNode(debugLabel: 'Ink Focus');
200+
await tester.pumpWidget(
201+
Material(
202+
child: Directionality(
203+
textDirection: TextDirection.ltr,
204+
child: Center(
205+
child: Container(
206+
width: 100,
207+
height: 100,
208+
child: InkWell(
209+
focusNode: focusNode,
210+
overlayColor: MaterialStateProperty.resolveWith<Color>((Set<MaterialState> states) {
211+
if (states.contains(MaterialState.hovered))
212+
return const Color(0xff00ff00);
213+
if (states.contains(MaterialState.focused))
214+
return const Color(0xff0000ff);
215+
if (states.contains(MaterialState.pressed))
216+
return const Color(0xf00fffff);
217+
return const Color(0xffbadbad); // Shouldn't happen.
218+
}),
219+
highlightColor: const Color(0xf00fffff),
220+
onTap: () { },
221+
onLongPress: () { },
222+
onHover: (bool hover) { },
223+
),
224+
),
225+
),
226+
),
227+
),
228+
);
229+
await tester.pumpAndSettle();
230+
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
231+
expect(inkFeatures, paintsExactlyCountTimes(#rect, 0));
232+
focusNode.requestFocus();
233+
await tester.pumpAndSettle();
234+
expect(inkFeatures, paints
235+
..rect(rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0), color: const Color(0xff0000ff)));
236+
});
237+
238+
testWidgets('ink response splashColor matches splashColor parameter', (WidgetTester tester) async {
239+
FocusManager.instance.highlightStrategy = FocusHighlightStrategy.alwaysTouch;
240+
final FocusNode focusNode = FocusNode(debugLabel: 'Ink Focus');
241+
const Color splashColor = Color(0xffff0000);
242+
await tester.pumpWidget(Material(
243+
child: Directionality(
244+
textDirection: TextDirection.ltr,
245+
child: Center(
246+
child: Focus(
247+
focusNode: focusNode,
248+
child: Container(
249+
width: 100,
250+
height: 100,
251+
child: InkWell(
252+
hoverColor: const Color(0xff00ff00),
253+
splashColor: splashColor,
254+
focusColor: const Color(0xff0000ff),
255+
highlightColor: const Color(0xf00fffff),
256+
onTap: () { },
257+
onLongPress: () { },
258+
onHover: (bool hover) { },
259+
),
260+
),
261+
),
262+
),
263+
),
264+
));
265+
await tester.pumpAndSettle();
266+
final TestGesture gesture = await tester.startGesture(tester.getRect(find.byType(InkWell)).center);
267+
await tester.pump(const Duration(milliseconds: 200)); // unconfirmed splash is well underway
268+
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
269+
expect(inkFeatures, paints..circle(x: 50, y: 50, color: splashColor));
270+
await gesture.up();
271+
});
272+
273+
testWidgets('ink response splashColor matches resolved overlayColor for MaterialState.pressed', (WidgetTester tester) async {
274+
// Same test as 'ink response splashColor matches splashColor
275+
// parameter' except that the splash color is specified with the
276+
// overlayColor parameter.
277+
FocusManager.instance.highlightStrategy = FocusHighlightStrategy.alwaysTouch;
278+
final FocusNode focusNode = FocusNode(debugLabel: 'Ink Focus');
279+
const Color splashColor = Color(0xffff0000);
280+
await tester.pumpWidget(Material(
281+
child: Directionality(
282+
textDirection: TextDirection.ltr,
283+
child: Center(
284+
child: Focus(
285+
focusNode: focusNode,
286+
child: Container(
287+
width: 100,
288+
height: 100,
289+
child: InkWell(
290+
overlayColor: MaterialStateProperty.resolveWith<Color>((Set<MaterialState> states) {
291+
if (states.contains(MaterialState.hovered))
292+
return const Color(0xff00ff00);
293+
if (states.contains(MaterialState.focused))
294+
return const Color(0xff0000ff);
295+
if (states.contains(MaterialState.pressed))
296+
return splashColor;
297+
return const Color(0xffbadbad); // Shouldn't happen.
298+
}),
299+
onTap: () { },
300+
onLongPress: () { },
301+
onHover: (bool hover) { },
302+
),
303+
),
304+
),
305+
),
306+
),
307+
));
308+
await tester.pumpAndSettle();
309+
final TestGesture gesture = await tester.startGesture(tester.getRect(find.byType(InkWell)).center);
310+
await tester.pump(const Duration(milliseconds: 200)); // unconfirmed splash is well underway
311+
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
312+
expect(inkFeatures, paints..circle(x: 50, y: 50, color: splashColor));
313+
await gesture.up();
314+
});
315+
158316
testWidgets("ink response doesn't change color on focus when on touch device", (WidgetTester tester) async {
159317
FocusManager.instance.highlightStrategy = FocusHighlightStrategy.alwaysTouch;
160318
final FocusNode focusNode = FocusNode(debugLabel: 'Ink Focus');

0 commit comments

Comments
 (0)