Skip to content

Commit 5099701

Browse files
authored
Make RenderUiKitView reject absorbed touch events (flutter#28666)
When a touch event that is in the bounds of a RenderUiKitView is absorbed by another render object, the RenderUiKitView's handleEvent is not called for that object. On the platform side, the touch event hits the FlutterTouchInterceptingView which is waiting for a framework decision that never arrived on whether to reject or accept the gesture. This change fixes the issue by having RenderUiKitView register a global PointerRoute, that is used to reject absorbed touch events.
1 parent 013fd21 commit 5099701

File tree

2 files changed

+62
-2
lines changed

2 files changed

+62
-2
lines changed

packages/flutter/lib/src/rendering/platform_view.dart

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,8 @@ class RenderUiKitView extends RenderBox {
323323

324324
_UiKitViewGestureRecognizer _gestureRecognizer;
325325

326+
PointerEvent _lastPointerDownEvent;
327+
326328
@override
327329
void performResize() {
328330
size = constraints.biggest;
@@ -349,13 +351,41 @@ class RenderUiKitView extends RenderBox {
349351

350352
@override
351353
void handleEvent(PointerEvent event, HitTestEntry entry) {
352-
if (event is PointerDownEvent) {
353-
_gestureRecognizer.addPointer(event);
354+
if (event is! PointerDownEvent) {
355+
return;
356+
}
357+
_gestureRecognizer.addPointer(event);
358+
_lastPointerDownEvent = event;
359+
}
360+
361+
// This is registered as a global PointerRoute while the render object is attached.
362+
void _handleGlobalPointerEvent(PointerEvent event) {
363+
if (event is! PointerDownEvent) {
364+
return;
365+
}
366+
final Offset localOffset = globalToLocal(event.position);
367+
if(!(Offset.zero & size).contains(localOffset)) {
368+
return;
369+
}
370+
if (event != _lastPointerDownEvent) {
371+
// The pointer event is in the bounds of this render box, but we didn't get it in handleEvent.
372+
// This means that the pointer event was absorbed by a different render object.
373+
// Since on the platform side the FlutterTouchIntercepting view is seeing all events that are
374+
// within its bounds we need to tell it to reject the current touch sequence.
375+
_viewController.rejectGesture();
354376
}
377+
_lastPointerDownEvent = null;
378+
}
379+
380+
@override
381+
void attach(PipelineOwner owner) {
382+
super.attach(owner);
383+
GestureBinding.instance.pointerRouter.addGlobalRoute(_handleGlobalPointerEvent);
355384
}
356385

357386
@override
358387
void detach() {
388+
GestureBinding.instance.pointerRouter.removeGlobalRoute(_handleGlobalPointerEvent);
359389
_gestureRecognizer.reset();
360390
super.detach();
361391
}

packages/flutter/test/widgets/platform_view_test.dart

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1364,6 +1364,36 @@ void main() {
13641364
expect(viewsController.gesturesRejected[currentViewId + 1], 0);
13651365
});
13661366

1367+
testWidgets('UiKitView rejects gestures absorbed by siblings', (WidgetTester tester) async {
1368+
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
1369+
final FakeIosPlatformViewsController viewsController = FakeIosPlatformViewsController();
1370+
viewsController.registerViewType('webview');
1371+
1372+
await tester.pumpWidget(
1373+
Stack(
1374+
alignment: Alignment.topLeft,
1375+
children: <Widget>[
1376+
const UiKitView(viewType: 'webview', layoutDirection: TextDirection.ltr),
1377+
Container(
1378+
color: const Color.fromARGB(255, 255, 255, 255),
1379+
width: 100,
1380+
height: 100,
1381+
),
1382+
],
1383+
)
1384+
);
1385+
1386+
// First frame is before the platform view was created so the render object
1387+
// is not yet in the tree.
1388+
await tester.pump();
1389+
1390+
final TestGesture gesture = await tester.startGesture(const Offset(50.0, 50.0));
1391+
await gesture.up();
1392+
1393+
expect(viewsController.gesturesRejected[currentViewId + 1], 1);
1394+
expect(viewsController.gesturesAccepted[currentViewId + 1], 0);
1395+
});
1396+
13671397
testWidgets('AndroidView rebuilt with same gestureRecognizers', (WidgetTester tester) async {
13681398
final FakeIosPlatformViewsController viewsController = FakeIosPlatformViewsController();
13691399
viewsController.registerViewType('webview');

0 commit comments

Comments
 (0)