diff --git a/packages/flutter/lib/src/widgets/tap_region.dart b/packages/flutter/lib/src/widgets/tap_region.dart index 4c50a7088aa..661c7144b4d 100644 --- a/packages/flutter/lib/src/widgets/tap_region.dart +++ b/packages/flutter/lib/src/widgets/tap_region.dart @@ -406,7 +406,7 @@ class TapRegion extends SingleChildRenderObjectWidget { /// /// * [TapRegion], a widget that inserts a [RenderTapRegion] into the render /// tree. -class RenderTapRegion extends RenderProxyBox with Diagnosticable { +class RenderTapRegion extends RenderProxyBox { /// Creates a [RenderTapRegion]. RenderTapRegion({ TapRegionRegistry? registry, @@ -464,6 +464,12 @@ class RenderTapRegion extends RenderProxyBox with Diagnosticable { Object? _groupId; set groupId(Object? value) { if (_groupId != value) { + // If the group changes, we need to unregister and re-register under the + // new group. The re-registration happens automatically in layout(). + if (_isRegistered) { + _registry!.unregisterTapRegion(this); + _isRegistered = false; + } _groupId = value; markNeedsLayout(); } @@ -515,7 +521,7 @@ class RenderTapRegion extends RenderProxyBox with Diagnosticable { @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - properties.add(DiagnosticsProperty('debugLabel', debugLabel, defaultValue: null)); + properties.add(DiagnosticsProperty('debugLabel', debugLabel, defaultValue: null)); properties.add(DiagnosticsProperty('groupId', groupId, defaultValue: null)); properties.add(FlagProperty('enabled', value: enabled, ifFalse: 'DISABLED', defaultValue: true)); } diff --git a/packages/flutter/test/widgets/tap_region_test.dart b/packages/flutter/test/widgets/tap_region_test.dart index f8b687bc97b..f83fa0de2dd 100644 --- a/packages/flutter/test/widgets/tap_region_test.dart +++ b/packages/flutter/test/widgets/tap_region_test.dart @@ -9,7 +9,7 @@ import 'package:flutter_test/flutter_test.dart'; void main() { testWidgets('TapRegionSurface detects outside taps', (WidgetTester tester) async { - final Set clickedOutside = {}; + final Set tappedOutside = {}; await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, @@ -22,21 +22,21 @@ void main() { const Text('Outside'), TapRegion( onTapOutside: (PointerEvent event) { - clickedOutside.add('No Group'); + tappedOutside.add('No Group'); }, child: const Text('No Group'), ), TapRegion( groupId: 1, onTapOutside: (PointerEvent event) { - clickedOutside.add('Group 1 A'); + tappedOutside.add('Group 1 A'); }, child: const Text('Group 1 A'), ), TapRegion( groupId: 1, onTapOutside: (PointerEvent event) { - clickedOutside.add('Group 1 B'); + tappedOutside.add('Group 1 B'); }, child: const Text('Group 1 B'), ), @@ -59,48 +59,48 @@ void main() { await gesture.removePointer(); } - expect(clickedOutside, isEmpty); + expect(tappedOutside, isEmpty); await click(find.text('No Group')); expect( - clickedOutside, + tappedOutside, unorderedEquals({ 'Group 1 A', 'Group 1 B', })); - clickedOutside.clear(); + tappedOutside.clear(); await click(find.text('Group 1 A')); expect( - clickedOutside, + tappedOutside, equals({ 'No Group', })); - clickedOutside.clear(); + tappedOutside.clear(); await click(find.text('Group 1 B')); expect( - clickedOutside, + tappedOutside, equals({ 'No Group', })); - clickedOutside.clear(); + tappedOutside.clear(); await click(find.text('Outside')); expect( - clickedOutside, + tappedOutside, unorderedEquals({ 'No Group', 'Group 1 A', 'Group 1 B', })); - clickedOutside.clear(); + tappedOutside.clear(); await click(find.text('Outside Surface')); - expect(clickedOutside, isEmpty); + expect(tappedOutside, isEmpty); }); testWidgets('TapRegionSurface detects inside taps', (WidgetTester tester) async { - final Set clickedInside = {}; + final Set tappedInside = {}; await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, @@ -113,21 +113,21 @@ void main() { const Text('Outside'), TapRegion( onTapInside: (PointerEvent event) { - clickedInside.add('No Group'); + tappedInside.add('No Group'); }, child: const Text('No Group'), ), TapRegion( groupId: 1, onTapInside: (PointerEvent event) { - clickedInside.add('Group 1 A'); + tappedInside.add('Group 1 A'); }, child: const Text('Group 1 A'), ), TapRegion( groupId: 1, onTapInside: (PointerEvent event) { - clickedInside.add('Group 1 B'); + tappedInside.add('Group 1 B'); }, child: const Text('Group 1 B'), ), @@ -150,39 +150,139 @@ void main() { await gesture.removePointer(); } - expect(clickedInside, isEmpty); + expect(tappedInside, isEmpty); await click(find.text('No Group')); expect( - clickedInside, + tappedInside, unorderedEquals({ 'No Group', })); - clickedInside.clear(); + tappedInside.clear(); await click(find.text('Group 1 A')); expect( - clickedInside, + tappedInside, equals({ 'Group 1 A', 'Group 1 B', })); - clickedInside.clear(); + tappedInside.clear(); await click(find.text('Group 1 B')); expect( - clickedInside, + tappedInside, equals({ 'Group 1 A', 'Group 1 B', })); - clickedInside.clear(); + tappedInside.clear(); await click(find.text('Outside')); - expect(clickedInside, isEmpty); - clickedInside.clear(); + expect(tappedInside, isEmpty); + tappedInside.clear(); await click(find.text('Outside Surface')); - expect(clickedInside, isEmpty); + expect(tappedInside, isEmpty); + }); + testWidgets('Setting the group updates the registration', (WidgetTester tester) async { + final Set tappedOutside = {}; + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: TapRegionSurface( + child: Row( + children: [ + const Text('Outside'), + TapRegion( + groupId: 1, + onTapOutside: (PointerEvent event) { + tappedOutside.add('Group 1 A'); + }, + child: const Text('Group 1 A'), + ), + TapRegion( + groupId: 1, + onTapOutside: (PointerEvent event) { + tappedOutside.add('Group 1 B'); + }, + child: const Text('Group 1 B'), + ), + ], + ), + ), + ), + ); + + await tester.pump(); + + Future click(Finder finder) async { + final TestGesture gesture = await tester.startGesture( + tester.getCenter(finder), + kind: PointerDeviceKind.mouse, + ); + await gesture.up(); + await gesture.removePointer(); + } + + expect(tappedOutside, isEmpty); + + await click(find.text('Group 1 A')); + expect(tappedOutside, isEmpty); + await click(find.text('Group 1 B')); + expect(tappedOutside, isEmpty); + await click(find.text('Outside')); + expect( + tappedOutside, + equals([ + 'Group 1 A', + 'Group 1 B', + ])); + tappedOutside.clear(); + + // Now change out the groups. + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: TapRegionSurface( + child: Row( + children: [ + const Text('Outside'), + TapRegion( + groupId: 1, + onTapOutside: (PointerEvent event) { + tappedOutside.add('Group 1 A'); + }, + child: const Text('Group 1 A'), + ), + TapRegion( + groupId: 2, + onTapOutside: (PointerEvent event) { + tappedOutside.add('Group 2 A'); + }, + child: const Text('Group 2 A'), + ), + ], + ), + ), + ), + ); + + await click(find.text('Group 1 A')); + expect(tappedOutside, equals(['Group 2 A'])); + tappedOutside.clear(); + + await click(find.text('Group 2 A')); + expect(tappedOutside, equals(['Group 1 A'])); + tappedOutside.clear(); + + await click(find.text('Outside')); + expect( + tappedOutside, + equals([ + 'Group 1 A', + 'Group 2 A', + ])); + tappedOutside.clear(); }); }