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:
parent
c24923c79a
commit
50f9b88395
@ -412,11 +412,17 @@ class _UiKitViewGestureRecognizer extends OneSequenceGestureRecognizer {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void rejectGesture(int pointer) {
|
void rejectGesture(int pointer) {
|
||||||
// Currently the engine rejects the gesture when the sequence is done.
|
controller.rejectGesture().catchError((dynamic e) {
|
||||||
// This doesn't work well with gesture recognizers that recognize after the sequence
|
if (e is MissingPluginException) {
|
||||||
// has ended.
|
// We land the framework part of active gesture rejection before the engine part.
|
||||||
// TODO(amirh): trigger an engine gesture reject here.
|
// There will be some commit range where we call rejectGesture from the framework and it
|
||||||
// https://github.com/flutter/flutter/issues/24076
|
// 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() {
|
void reset() {
|
||||||
|
@ -630,6 +630,18 @@ class UiKitViewController {
|
|||||||
return SystemChannels.platform_views.invokeMethod('acceptGesture', args);
|
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.
|
/// Disposes the view.
|
||||||
///
|
///
|
||||||
/// The [UiKitViewController] object is unusable after calling this.
|
/// The [UiKitViewController] object is unusable after calling this.
|
||||||
|
@ -159,9 +159,12 @@ class FakeIosPlatformViewsController {
|
|||||||
// delayed until it completes.
|
// delayed until it completes.
|
||||||
Completer<void> creationDelay;
|
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>{};
|
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) {
|
void registerViewType(String viewType) {
|
||||||
_registeredViewTypes.add(viewType);
|
_registeredViewTypes.add(viewType);
|
||||||
}
|
}
|
||||||
@ -174,6 +177,8 @@ class FakeIosPlatformViewsController {
|
|||||||
return _dispose(call);
|
return _dispose(call);
|
||||||
case 'acceptGesture':
|
case 'acceptGesture':
|
||||||
return _acceptGesture(call);
|
return _acceptGesture(call);
|
||||||
|
case 'rejectGesture':
|
||||||
|
return _rejectGesture(call);
|
||||||
}
|
}
|
||||||
return Future<dynamic>.sync(() => null);
|
return Future<dynamic>.sync(() => null);
|
||||||
}
|
}
|
||||||
@ -202,6 +207,7 @@ class FakeIosPlatformViewsController {
|
|||||||
|
|
||||||
_views[id] = FakeUiKitView(id, viewType, creationParams);
|
_views[id] = FakeUiKitView(id, viewType, creationParams);
|
||||||
gesturesAccepted[id] = 0;
|
gesturesAccepted[id] = 0;
|
||||||
|
gesturesRejected[id] = 0;
|
||||||
return Future<int>.sync(() => null);
|
return Future<int>.sync(() => null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,6 +218,13 @@ class FakeIosPlatformViewsController {
|
|||||||
return Future<int>.sync(() => null);
|
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) {
|
Future<dynamic> _dispose(MethodCall call) {
|
||||||
final int id = call.arguments;
|
final int id = call.arguments;
|
||||||
|
|
||||||
|
@ -1196,6 +1196,7 @@ void main() {
|
|||||||
|
|
||||||
expect(verticalDragAcceptedByParent, true);
|
expect(verticalDragAcceptedByParent, true);
|
||||||
expect(viewsController.gesturesAccepted[currentViewId + 1], 0);
|
expect(viewsController.gesturesAccepted[currentViewId + 1], 0);
|
||||||
|
expect(viewsController.gesturesRejected[currentViewId + 1], 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('UiKitView gesture recognizers', (WidgetTester tester) async {
|
testWidgets('UiKitView gesture recognizers', (WidgetTester tester) async {
|
||||||
@ -1237,6 +1238,7 @@ void main() {
|
|||||||
|
|
||||||
expect(verticalDragAcceptedByParent, false);
|
expect(verticalDragAcceptedByParent, false);
|
||||||
expect(viewsController.gesturesAccepted[currentViewId + 1], 1);
|
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 {
|
testWidgets('UiKitView can claim gesture after all pointers are up', (WidgetTester tester) async {
|
||||||
@ -1276,6 +1278,7 @@ void main() {
|
|||||||
expect(verticalDragAcceptedByParent, false);
|
expect(verticalDragAcceptedByParent, false);
|
||||||
|
|
||||||
expect(viewsController.gesturesAccepted[currentViewId + 1], 1);
|
expect(viewsController.gesturesAccepted[currentViewId + 1], 1);
|
||||||
|
expect(viewsController.gesturesRejected[currentViewId + 1], 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('UiKitView rebuilt during gesture', (WidgetTester tester) async {
|
testWidgets('UiKitView rebuilt during gesture', (WidgetTester tester) async {
|
||||||
@ -1320,6 +1323,7 @@ void main() {
|
|||||||
await gesture.up();
|
await gesture.up();
|
||||||
|
|
||||||
expect(viewsController.gesturesAccepted[currentViewId + 1], 1);
|
expect(viewsController.gesturesAccepted[currentViewId + 1], 1);
|
||||||
|
expect(viewsController.gesturesRejected[currentViewId + 1], 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('UiKitView with eager gesture recognizer', (WidgetTester tester) async {
|
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
|
// the Android view). Here we assert that with the eager recognizer in the gesture team the
|
||||||
// pointer down event is immediately dispatched.
|
// pointer down event is immediately dispatched.
|
||||||
expect(viewsController.gesturesAccepted[currentViewId + 1], 1);
|
expect(viewsController.gesturesAccepted[currentViewId + 1], 1);
|
||||||
|
expect(viewsController.gesturesRejected[currentViewId + 1], 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('AndroidView rebuilt with same gestureRecognizers', (WidgetTester tester) async {
|
testWidgets('AndroidView rebuilt with same gestureRecognizers', (WidgetTester tester) async {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user