Fix InkWell highlight and splash sometimes persists (#100880)
diff --git a/packages/flutter/lib/src/material/ink_well.dart b/packages/flutter/lib/src/material/ink_well.dart index e90fb23..6192ec5 100644 --- a/packages/flutter/lib/src/material/ink_well.dart +++ b/packages/flutter/lib/src/material/ink_well.dart
@@ -769,12 +769,12 @@ bool get _anyChildInkResponsePressed => _activeChildren.isNotEmpty; void _simulateTap([Intent? intent]) { - _startSplash(context: context); + _startNewSplash(context: context); _handleTap(); } void _simulateLongPress() { - _startSplash(context: context); + _startNewSplash(context: context); _handleLongPress(); } @@ -966,7 +966,7 @@ void _handleTapDown(TapDownDetails details) { if (_anyChildInkResponsePressed) return; - _startSplash(details: details); + _startNewSplash(details: details); widget.onTapDown?.call(details); } @@ -974,7 +974,7 @@ widget.onTapUp?.call(details); } - void _startSplash({TapDownDetails? details, BuildContext? context}) { + void _startNewSplash({TapDownDetails? details, BuildContext? context}) { assert(details != null || context != null); final Offset globalPosition; @@ -988,6 +988,7 @@ final InteractiveInkFeature splash = _createInkFeature(globalPosition); _splashes ??= HashSet<InteractiveInkFeature>(); _splashes!.add(splash); + _currentSplash?.cancel(); _currentSplash = splash; updateKeepAlive(); updateHighlight(_HighlightType.pressed, value: true); @@ -1014,6 +1015,7 @@ void _handleDoubleTap() { _currentSplash?.confirm(); _currentSplash = null; + updateHighlight(_HighlightType.pressed, value: false); widget.onDoubleTap?.call(); }
diff --git a/packages/flutter/test/material/ink_well_test.dart b/packages/flutter/test/material/ink_well_test.dart index 0d2146f..7957916 100644 --- a/packages/flutter/test/material/ink_well_test.dart +++ b/packages/flutter/test/material/ink_well_test.dart
@@ -1424,4 +1424,90 @@ textDirection: TextDirection.ltr, )); }); + + testWidgets('InkWell highlight should not survive after [onTapDown, onDoubleTap] sequence', (WidgetTester tester) async { + final List<String> log = <String>[]; + + await tester.pumpWidget(Directionality( + textDirection: TextDirection.ltr, + child: Material( + child: Center( + child: InkWell( + onTap: () { + log.add('tap'); + }, + onDoubleTap: () { + log.add('double-tap'); + }, + onTapDown: (TapDownDetails details) { + log.add('tap-down'); + }, + onTapCancel: () { + log.add('tap-cancel'); + }, + ), + ), + ), + )); + + final Offset taplocation = tester.getRect(find.byType(InkWell)).center; + + final TestGesture gesture = await tester.startGesture(taplocation); + await tester.pump(const Duration(milliseconds: 100)); + expect(log, equals(<String>['tap-down'])); + await gesture.up(); + await tester.pump(const Duration(milliseconds: 100)); + await tester.tap(find.byType(InkWell)); + await tester.pump(const Duration(milliseconds: 100)); + expect(log, equals(<String>['tap-down', 'double-tap'])); + + await tester.pumpAndSettle(); + final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures'); + expect(inkFeatures, paintsExactlyCountTimes(#drawRect, 0)); + }); + + testWidgets('InkWell splash should not survive after [onTapDown, onTapDown, onTapCancel, onDoubleTap] sequence', (WidgetTester tester) async { + final List<String> log = <String>[]; + + await tester.pumpWidget(Directionality( + textDirection: TextDirection.ltr, + child: Material( + child: Center( + child: InkWell( + onTap: () { + log.add('tap'); + }, + onDoubleTap: () { + log.add('double-tap'); + }, + onTapDown: (TapDownDetails details) { + log.add('tap-down'); + }, + onTapCancel: () { + log.add('tap-cancel'); + }, + ), + ), + ), + )); + + final Offset tapLocation = tester.getRect(find.byType(InkWell)).center; + + final TestGesture gesture1 = await tester.startGesture(tapLocation); + await tester.pump(const Duration(milliseconds: 100)); + expect(log, equals(<String>['tap-down'])); + await gesture1.up(); + await tester.pump(const Duration(milliseconds: 100)); + + final TestGesture gesture2 = await tester.startGesture(tapLocation); + await tester.pump(const Duration(milliseconds: 100)); + expect(log, equals(<String>['tap-down', 'tap-down'])); + await gesture2.up(); + await tester.pump(const Duration(milliseconds: 100)); + expect(log, equals(<String>['tap-down', 'tap-down', 'tap-cancel', 'double-tap'])); + + await tester.pumpAndSettle(); + final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures'); + expect(inkFeatures, paintsExactlyCountTimes(#drawCircle, 0)); + }); }