Actively reject UiKitView gestures. (#25792)

flutter/engine#7307 changes the engine side of embedded UIView to only
reject gestures when the framework sends a `rejectGesture` message, so
that gesture resolution can done after a touch sequence has ended (see
PR description for flutter/engine#7307 for more details).

This change makes the framework send a `rejectGesture` message to the
engine when a UiKitView rejects a gesture.

I'm planning to land this PR before the engine side change, so right now
it swallows the exception thrown if there is no engine implementation
for `rejectGesture` (which keeps us with the current behavior). After
this change lands I'll land the engine PR, and then clean up the part
that swallows the exception.
This commit is contained in:
Amir Hardon 2018-12-26 19:34:53 -08:00 committed by GitHub
parent c24923c79a
commit 50f9b88395
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 42 additions and 6 deletions

View File

@ -412,11 +412,17 @@ class _UiKitViewGestureRecognizer extends OneSequenceGestureRecognizer {
@override
void rejectGesture(int pointer) {
// Currently the engine rejects the gesture when the sequence is done.
// This doesn't work well with gesture recognizers that recognize after the sequence
// has ended.
// TODO(amirh): trigger an engine gesture reject here.
// https://github.com/flutter/flutter/issues/24076
controller.rejectGesture().catchError((dynamic e) {
if (e is MissingPluginException) {
// We land the framework part of active gesture rejection before the engine part.
// There will be some commit range where we call rejectGesture from the framework and it
// isn't implemented in the engine, if that is the case we swallow the MissingPluginException.
// Once the engine support lands we will remove the enclosing catchError call.
// TODO(amirh): remove this once the engine supports active gesture rejection.
return;
}
throw e;
});
}
void reset() {

View File

@ -630,6 +630,18 @@ class UiKitViewController {
return SystemChannels.platform_views.invokeMethod('acceptGesture', args);
}
/// Rejects an active gesture.
///
/// When a touch sequence is happening on the embedded UIView all touch events are delayed.
/// Calling this method drops the buffered touch events and prevents any future touch events for
/// the pointers that are part of the active touch sequence from arriving to the embedded view.
Future<void> rejectGesture() {
final Map<String, dynamic> args = <String, dynamic> {
'id': id,
};
return SystemChannels.platform_views.invokeMethod('rejectGesture', args);
}
/// Disposes the view.
///
/// The [UiKitViewController] object is unusable after calling this.

View File

@ -159,9 +159,12 @@ class FakeIosPlatformViewsController {
// delayed until it completes.
Completer<void> creationDelay;
// Maps a view id to the number of gestures it accepted so fat.
// Maps a view id to the number of gestures it accepted so far.
final Map<int, int> gesturesAccepted = <int, int>{};
// Maps a view id to the number of gestures it rejected so far.
final Map<int, int> gesturesRejected = <int, int>{};
void registerViewType(String viewType) {
_registeredViewTypes.add(viewType);
}
@ -174,6 +177,8 @@ class FakeIosPlatformViewsController {
return _dispose(call);
case 'acceptGesture':
return _acceptGesture(call);
case 'rejectGesture':
return _rejectGesture(call);
}
return Future<dynamic>.sync(() => null);
}
@ -202,6 +207,7 @@ class FakeIosPlatformViewsController {
_views[id] = FakeUiKitView(id, viewType, creationParams);
gesturesAccepted[id] = 0;
gesturesRejected[id] = 0;
return Future<int>.sync(() => null);
}
@ -212,6 +218,13 @@ class FakeIosPlatformViewsController {
return Future<int>.sync(() => null);
}
Future<dynamic> _rejectGesture(MethodCall call) async {
final Map<dynamic, dynamic> args = call.arguments;
final int id = args['id'];
gesturesRejected[id] += 1;
return Future<int>.sync(() => null);
}
Future<dynamic> _dispose(MethodCall call) {
final int id = call.arguments;

View File

@ -1196,6 +1196,7 @@ void main() {
expect(verticalDragAcceptedByParent, true);
expect(viewsController.gesturesAccepted[currentViewId + 1], 0);
expect(viewsController.gesturesRejected[currentViewId + 1], 1);
});
testWidgets('UiKitView gesture recognizers', (WidgetTester tester) async {
@ -1237,6 +1238,7 @@ void main() {
expect(verticalDragAcceptedByParent, false);
expect(viewsController.gesturesAccepted[currentViewId + 1], 1);
expect(viewsController.gesturesRejected[currentViewId + 1], 0);
});
testWidgets('UiKitView can claim gesture after all pointers are up', (WidgetTester tester) async {
@ -1276,6 +1278,7 @@ void main() {
expect(verticalDragAcceptedByParent, false);
expect(viewsController.gesturesAccepted[currentViewId + 1], 1);
expect(viewsController.gesturesRejected[currentViewId + 1], 0);
});
testWidgets('UiKitView rebuilt during gesture', (WidgetTester tester) async {
@ -1320,6 +1323,7 @@ void main() {
await gesture.up();
expect(viewsController.gesturesAccepted[currentViewId + 1], 1);
expect(viewsController.gesturesRejected[currentViewId + 1], 0);
});
testWidgets('UiKitView with eager gesture recognizer', (WidgetTester tester) async {
@ -1359,6 +1363,7 @@ void main() {
// the Android view). Here we assert that with the eager recognizer in the gesture team the
// pointer down event is immediately dispatched.
expect(viewsController.gesturesAccepted[currentViewId + 1], 1);
expect(viewsController.gesturesRejected[currentViewId + 1], 0);
});
testWidgets('AndroidView rebuilt with same gestureRecognizers', (WidgetTester tester) async {