Skip to content

Commit

Permalink
Make RenderUiKitView reject absorbed touch events (flutter#28666)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
amirh committed Mar 4, 2019
1 parent 013fd21 commit 5099701
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 2 deletions.
34 changes: 32 additions & 2 deletions packages/flutter/lib/src/rendering/platform_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,8 @@ class RenderUiKitView extends RenderBox {

_UiKitViewGestureRecognizer _gestureRecognizer;

PointerEvent _lastPointerDownEvent;

@override
void performResize() {
size = constraints.biggest;
Expand All @@ -349,13 +351,41 @@ class RenderUiKitView extends RenderBox {

@override
void handleEvent(PointerEvent event, HitTestEntry entry) {
if (event is PointerDownEvent) {
_gestureRecognizer.addPointer(event);
if (event is! PointerDownEvent) {
return;
}
_gestureRecognizer.addPointer(event);
_lastPointerDownEvent = event;
}

// This is registered as a global PointerRoute while the render object is attached.
void _handleGlobalPointerEvent(PointerEvent event) {
if (event is! PointerDownEvent) {
return;
}
final Offset localOffset = globalToLocal(event.position);
if(!(Offset.zero & size).contains(localOffset)) {
return;
}
if (event != _lastPointerDownEvent) {
// The pointer event is in the bounds of this render box, but we didn't get it in handleEvent.
// This means that the pointer event was absorbed by a different render object.
// Since on the platform side the FlutterTouchIntercepting view is seeing all events that are
// within its bounds we need to tell it to reject the current touch sequence.
_viewController.rejectGesture();
}
_lastPointerDownEvent = null;
}

@override
void attach(PipelineOwner owner) {
super.attach(owner);
GestureBinding.instance.pointerRouter.addGlobalRoute(_handleGlobalPointerEvent);
}

@override
void detach() {
GestureBinding.instance.pointerRouter.removeGlobalRoute(_handleGlobalPointerEvent);
_gestureRecognizer.reset();
super.detach();
}
Expand Down
30 changes: 30 additions & 0 deletions packages/flutter/test/widgets/platform_view_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1364,6 +1364,36 @@ void main() {
expect(viewsController.gesturesRejected[currentViewId + 1], 0);
});

testWidgets('UiKitView rejects gestures absorbed by siblings', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
final FakeIosPlatformViewsController viewsController = FakeIosPlatformViewsController();
viewsController.registerViewType('webview');

await tester.pumpWidget(
Stack(
alignment: Alignment.topLeft,
children: <Widget>[
const UiKitView(viewType: 'webview', layoutDirection: TextDirection.ltr),
Container(
color: const Color.fromARGB(255, 255, 255, 255),
width: 100,
height: 100,
),
],
)
);

// First frame is before the platform view was created so the render object
// is not yet in the tree.
await tester.pump();

final TestGesture gesture = await tester.startGesture(const Offset(50.0, 50.0));
await gesture.up();

expect(viewsController.gesturesRejected[currentViewId + 1], 1);
expect(viewsController.gesturesAccepted[currentViewId + 1], 0);
});

testWidgets('AndroidView rebuilt with same gestureRecognizers', (WidgetTester tester) async {
final FakeIosPlatformViewsController viewsController = FakeIosPlatformViewsController();
viewsController.registerViewType('webview');
Expand Down

0 comments on commit 5099701

Please sign in to comment.