From dc6ab62696388e4ea8c778e7bcaeab3f1241ccd0 Mon Sep 17 00:00:00 2001 From: Hans Muller Date: Thu, 8 Sep 2022 10:20:32 -0700 Subject: [PATCH] Fixed one-frame InkWell overlay color problem on unhover (#111112) --- .../flutter/lib/src/material/ink_well.dart | 54 +++++++++++++------ .../flutter/test/material/ink_well_test.dart | 38 +++++++++++++ 2 files changed, 75 insertions(+), 17 deletions(-) diff --git a/packages/flutter/lib/src/material/ink_well.dart b/packages/flutter/lib/src/material/ink_well.dart index d343605e6a2..50560379738 100644 --- a/packages/flutter/lib/src/material/ink_well.dart +++ b/packages/flutter/lib/src/material/ink_well.dart @@ -857,22 +857,6 @@ class _InkResponseState extends State<_InkResponseStateWidget> @override bool get wantKeepAlive => highlightsExist || (_splashes != null && _splashes!.isNotEmpty); - Color getHighlightColorForType(_HighlightType type) { - final ThemeData theme = Theme.of(context); - final Color? resolvedOverlayColor = widget.overlayColor?.resolve(statesController.value); - switch (type) { - // The pressed state triggers a ripple (ink splash), per the current - // Material Design spec. A separate highlight is no longer used. - // See https://material.io/design/interaction/states.html#pressed - case _HighlightType.pressed: - return resolvedOverlayColor ?? widget.highlightColor ?? theme.highlightColor; - case _HighlightType.focus: - return resolvedOverlayColor ?? widget.focusColor ?? theme.focusColor; - case _HighlightType.hover: - return resolvedOverlayColor ?? widget.hoverColor ?? theme.hoverColor; - } - } - Duration getFadeDurationForType(_HighlightType type) { switch (type) { case _HighlightType.pressed: @@ -911,13 +895,30 @@ class _InkResponseState extends State<_InkResponseStateWidget> if (value == (highlight != null && highlight.active)) { return; } + if (value) { if (highlight == null) { + Color? resolvedOverlayColor = widget.overlayColor?.resolve(statesController.value); + if (resolvedOverlayColor == null) { + // Use the backwards compatible defaults + final ThemeData theme = Theme.of(context); + switch (type) { + case _HighlightType.pressed: + resolvedOverlayColor = widget.highlightColor ?? theme.highlightColor; + break; + case _HighlightType.focus: + resolvedOverlayColor = widget.focusColor ?? theme.focusColor; + break; + case _HighlightType.hover: + resolvedOverlayColor = widget.hoverColor ?? theme.hoverColor; + break; + } + } final RenderBox referenceBox = context.findRenderObject()! as RenderBox; _highlights[type] = InkHighlight( controller: Material.of(context)!, referenceBox: referenceBox, - color: getHighlightColorForType(type), + color: resolvedOverlayColor, shape: widget.highlightShape, radius: widget.radius, borderRadius: widget.borderRadius, @@ -1159,6 +1160,25 @@ class _InkResponseState extends State<_InkResponseStateWidget> Widget build(BuildContext context) { assert(widget.debugCheckContext(context)); super.build(context); // See AutomaticKeepAliveClientMixin. + + Color getHighlightColorForType(_HighlightType type) { + const Set pressed = {MaterialState.pressed}; + const Set focused = {MaterialState.focused}; + const Set hovered = {MaterialState.hovered}; + + final ThemeData theme = Theme.of(context); + switch (type) { + // The pressed state triggers a ripple (ink splash), per the current + // Material Design spec. A separate highlight is no longer used. + // See https://material.io/design/interaction/states.html#pressed + case _HighlightType.pressed: + return widget.overlayColor?.resolve(pressed) ?? widget.highlightColor ?? theme.highlightColor; + case _HighlightType.focus: + return widget.overlayColor?.resolve(focused) ?? widget.focusColor ?? theme.focusColor; + case _HighlightType.hover: + return widget.overlayColor?.resolve(hovered) ?? widget.hoverColor ?? theme.hoverColor; + } + } for (final _HighlightType type in _highlights.keys) { _highlights[type]?.color = getHighlightColorForType(type); } diff --git a/packages/flutter/test/material/ink_well_test.dart b/packages/flutter/test/material/ink_well_test.dart index 113d72e060c..9f3cd2ba23b 100644 --- a/packages/flutter/test/material/ink_well_test.dart +++ b/packages/flutter/test/material/ink_well_test.dart @@ -1556,4 +1556,42 @@ void main() { expect(tapCount, 3); expect(pressedCount, 2); }); + + testWidgets('ink well overlayColor opacity fades from 0xff when hover ends', (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/issues/110266 + await tester.pumpWidget(Material( + child: Directionality( + textDirection: TextDirection.ltr, + child: Center( + child: SizedBox( + width: 100, + height: 100, + child: InkWell( + overlayColor: MaterialStateProperty.resolveWith((Set states) { + if (states.contains(MaterialState.hovered)) { + return const Color(0xff00ff00); + } + return null; + }), + onTap: () { }, + onLongPress: () { }, + onHover: (bool hover) { }, + ), + ), + ), + ), + )); + final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); + await gesture.addPointer(); + await gesture.moveTo(tester.getCenter(find.byType(SizedBox))); + await tester.pumpAndSettle(); + await gesture.moveTo(const Offset(10, 10)); // fade out the overlay + await tester.pump(); // trigger the fade out animation + final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures'); + // Fadeout begins with the MaterialStates.hovered overlay color + expect(inkFeatures, paints..rect(rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0), color: const Color(0xff00ff00))); + // 50ms fadeout is 50% complete, overlay color alpha goes from 0xff to 0x80 + await tester.pump(const Duration(milliseconds: 25)); + expect(inkFeatures, paints..rect(rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0), color: const Color(0x8000ff00))); + }); }