Unverified Commit 333c9618 authored by Chris Yang's avatar Chris Yang Committed by GitHub

Extract common PlatformView functionality: Gesture and PointerEvent (#37497)

parent 0cd0c660
...@@ -7,6 +7,7 @@ import 'dart:typed_data'; ...@@ -7,6 +7,7 @@ import 'dart:typed_data';
import 'dart:ui'; import 'dart:ui';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'message_codec.dart'; import 'message_codec.dart';
import 'system_channels.dart'; import 'system_channels.dart';
...@@ -725,4 +726,7 @@ abstract class PlatformViewController { ...@@ -725,4 +726,7 @@ abstract class PlatformViewController {
/// ///
/// See also [PlatformViewRegistry] which is a helper for managing platform view ids. /// See also [PlatformViewRegistry] which is a helper for managing platform view ids.
int get viewId; int get viewId;
/// Dispatches the `event` to the platform view.
void dispatchPointerEvent(PointerEvent event);
} }
...@@ -603,7 +603,11 @@ class PlatformViewSurface extends LeafRenderObjectWidget { ...@@ -603,7 +603,11 @@ class PlatformViewSurface extends LeafRenderObjectWidget {
/// The [controller] must not be null. /// The [controller] must not be null.
const PlatformViewSurface({ const PlatformViewSurface({
@required this.controller, @required this.controller,
}) : assert(controller != null); @required this.hitTestBehavior,
@required this.gestureRecognizers,
}) : assert(controller != null),
assert(hitTestBehavior != null),
assert(gestureRecognizers != null);
/// The controller for the platform view integrated by this [PlatformViewSurface]. /// The controller for the platform view integrated by this [PlatformViewSurface].
/// ///
...@@ -611,14 +615,60 @@ class PlatformViewSurface extends LeafRenderObjectWidget { ...@@ -611,14 +615,60 @@ class PlatformViewSurface extends LeafRenderObjectWidget {
/// [PlatformViewController.viewId] identifies the platform view whose contents are painted by this widget. /// [PlatformViewController.viewId] identifies the platform view whose contents are painted by this widget.
final PlatformViewController controller; final PlatformViewController controller;
/// Which gestures should be forwarded to the PlatformView.
///
/// {@macro flutter.widgets.platformViews.gestureRecognizersDescHead}
///
/// For example, with the following setup vertical drags will not be dispatched to the platform view
/// as the vertical drag gesture is claimed by the parent [GestureDetector].
///
/// ```dart
/// GestureDetector(
/// onVerticalDragStart: (DragStartDetails details) {},
/// child: PlatformViewSurface(
/// ),
/// )
/// ```
///
/// To get the [PlatformViewSurface] to claim the vertical drag gestures we can pass a vertical drag
/// gesture recognizer factory in [gestureRecognizers] e.g:
///
/// ```dart
/// GestureDetector(
/// onVerticalDragStart: (DragStartDetails details) {},
/// child: SizedBox(
/// width: 200.0,
/// height: 100.0,
/// child: PlatformViewSurface(
/// gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>[
/// new Factory<OneSequenceGestureRecognizer>(
/// () => new EagerGestureRecognizer(),
/// ),
/// ].toSet(),
/// ),
/// ),
/// )
/// ```
///
/// {@macro flutter.widgets.platformViews.gestureRecognizersDescFoot}
// We use OneSequenceGestureRecognizers as they support gesture arena teams.
// TODO(amirh): get a list of GestureRecognizers here.
// https://github.com/flutter/flutter/issues/20953
final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers;
/// {@macro flutter.widgets.platformViews.hittestParam}
final PlatformViewHitTestBehavior hitTestBehavior;
@override @override
RenderObject createRenderObject(BuildContext context) { RenderObject createRenderObject(BuildContext context) {
return PlatformViewRenderBox(controller: controller); return PlatformViewRenderBox(controller: controller, gestureRecognizers: gestureRecognizers, hitTestBehavior: hitTestBehavior);
} }
@override @override
void updateRenderObject(BuildContext context, PlatformViewRenderBox renderObject) { void updateRenderObject(BuildContext context, PlatformViewRenderBox renderObject) {
renderObject renderObject
..controller = controller; ..controller = controller
..hitTestBehavior = hitTestBehavior
..updateGestureRecognizers(gestureRecognizers);
} }
} }
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
...@@ -15,7 +17,16 @@ void main() { ...@@ -15,7 +17,16 @@ void main() {
PlatformViewRenderBox platformViewRenderBox; PlatformViewRenderBox platformViewRenderBox;
setUp((){ setUp((){
fakePlatformViewController = FakePlatformViewController(0); fakePlatformViewController = FakePlatformViewController(0);
platformViewRenderBox = PlatformViewRenderBox(controller: fakePlatformViewController); platformViewRenderBox = PlatformViewRenderBox(
controller: fakePlatformViewController,
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{
Factory<VerticalDragGestureRecognizer>(
() {
return VerticalDragGestureRecognizer();
},
),
},);
}); });
test('layout should size to max constraint', () { test('layout should size to max constraint', () {
......
...@@ -8,6 +8,7 @@ import 'package:collection/collection.dart'; ...@@ -8,6 +8,7 @@ import 'package:collection/collection.dart';
import 'package:flutter/painting.dart'; import 'package:flutter/painting.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
/// Used in internal testing. /// Used in internal testing.
class FakePlatformViewController extends PlatformViewController { class FakePlatformViewController extends PlatformViewController {
...@@ -16,10 +17,22 @@ class FakePlatformViewController extends PlatformViewController { ...@@ -16,10 +17,22 @@ class FakePlatformViewController extends PlatformViewController {
_id = id; _id = id;
} }
/// Events that are dispatched;
List<PointerEvent> dispatchedPointerEvents = <PointerEvent>[];
int _id; int _id;
@override @override
int get viewId => _id; int get viewId => _id;
@override
void dispatchPointerEvent(PointerEvent event) {
dispatchedPointerEvents.add(event);
}
void clearTestingVariables() {
dispatchedPointerEvents.clear();
}
} }
class FakeAndroidPlatformViewsController { class FakeAndroidPlatformViewsController {
......
...@@ -1676,12 +1676,261 @@ void main() { ...@@ -1676,12 +1676,261 @@ void main() {
}); });
testWidgets('PlatformViewSurface should create platform view layer', (WidgetTester tester) async { testWidgets('PlatformViewSurface should create platform view layer', (WidgetTester tester) async {
final PlatformViewSurface surface = PlatformViewSurface(controller: controller); final PlatformViewSurface surface = PlatformViewSurface(
controller: controller,
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},);
await tester.pumpWidget(surface); await tester.pumpWidget(surface);
final PlatformViewLayer layer = tester.layers.firstWhere((Layer layer){ final PlatformViewLayer layer = tester.layers.firstWhere((Layer layer){
return layer is PlatformViewLayer; return layer is PlatformViewLayer;
}); });
expect(layer, isNotNull); expect(layer, isNotNull);
}); });
testWidgets('PlatformViewSurface can lose gesture arenas', (WidgetTester tester) async {
bool verticalDragAcceptedByParent = false;
await tester.pumpWidget(
Align(
alignment: Alignment.topLeft,
child: Container(
margin: const EdgeInsets.all(10.0),
child: GestureDetector(
onVerticalDragStart: (DragStartDetails d) {
verticalDragAcceptedByParent = true;
},
child: SizedBox(
width: 200.0,
height: 100.0,
child: PlatformViewSurface(
controller: controller,
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
hitTestBehavior: PlatformViewHitTestBehavior.opaque),
),
),
),
),
);
final TestGesture gesture = await tester.startGesture(const Offset(50.0, 50.0));
await gesture.moveBy(const Offset(0.0, 100.0));
await gesture.up();
expect(verticalDragAcceptedByParent, true);
expect(
controller.dispatchedPointerEvents,
isEmpty,
);
});
testWidgets('PlatformViewSurface gesture recognizers dispatch events', (WidgetTester tester) async {
bool verticalDragAcceptedByParent = false;
await tester.pumpWidget(
Align(
alignment: Alignment.topLeft,
child: GestureDetector(
onVerticalDragStart: (DragStartDetails d) {
verticalDragAcceptedByParent = true;
},
child: SizedBox(
width: 200.0,
height: 100.0,
child: PlatformViewSurface(
controller: controller,
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{
Factory<VerticalDragGestureRecognizer>(
() {
return VerticalDragGestureRecognizer()
..onStart = (_) {}; // Add callback to enable recognizer
},
),
},
),
),
),
),
);
final TestGesture gesture = await tester.startGesture(const Offset(50.0, 50.0));
await gesture.moveBy(const Offset(0.0, 100.0));
await gesture.up();
expect(verticalDragAcceptedByParent, false);
expect(
controller.dispatchedPointerEvents.length,
3,
);
});
testWidgets(
'PlatformViewSurface can claim gesture after all pointers are up', (WidgetTester tester) async {
bool verticalDragAcceptedByParent = false;
// The long press recognizer rejects the gesture after the PlatformViewSurface gets the pointer up event.
// This test makes sure that the PlatformViewSurface can win the gesture after it got the pointer up event.
await tester.pumpWidget(
Align(
alignment: Alignment.topLeft,
child: GestureDetector(
onVerticalDragStart: (DragStartDetails d) {
verticalDragAcceptedByParent = true;
},
onLongPress: () { },
child: SizedBox(
width: 200.0,
height: 100.0,
child: PlatformViewSurface(
controller: controller,
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
),
),
),
),
);
final TestGesture gesture = await tester.startGesture(const Offset(50.0, 50.0));
await gesture.up();
expect(verticalDragAcceptedByParent, false);
expect(
controller.dispatchedPointerEvents.length,
2,
);
});
testWidgets('PlatformViewSurface rebuilt during gesture', (WidgetTester tester) async {
await tester.pumpWidget(
Align(
alignment: Alignment.topLeft,
child: SizedBox(
width: 200.0,
height: 100.0,
child: PlatformViewSurface(
controller: controller,
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
),
),
),
);
final TestGesture gesture = await tester.startGesture(const Offset(50.0, 50.0));
await gesture.moveBy(const Offset(0.0, 100.0));
await tester.pumpWidget(
Align(
alignment: Alignment.topLeft,
child: SizedBox(
width: 200.0,
height: 100.0,
child: PlatformViewSurface(
controller: controller,
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
),
),
),
);
await gesture.up();
expect(
controller.dispatchedPointerEvents.length,
3,
);
});
testWidgets('PlatformViewSurface with eager gesture recognizer', (WidgetTester tester) async {
await tester.pumpWidget(
Align(
alignment: Alignment.topLeft,
child: GestureDetector(
onVerticalDragStart: (DragStartDetails d) { },
child: SizedBox(
width: 200.0,
height: 100.0,
child: PlatformViewSurface(
controller: controller,
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{
Factory<OneSequenceGestureRecognizer>(
() => EagerGestureRecognizer(),
),
},
),
),
),
),
);
await tester.startGesture(const Offset(50.0, 50.0));
// Normally (without the eager gesture recognizer) after just the pointer down event
// no gesture arena member will claim the arena (so no motion events will be dispatched to
// the PlatformViewSurface). Here we assert that with the eager recognizer in the gesture team the
// pointer down event is immediately dispatched.
expect(
controller.dispatchedPointerEvents.length,
1,
);
});
testWidgets('PlatformViewRenderBox reconstructed with same gestureRecognizers', (WidgetTester tester) async {
int factoryInvocationCount = 0;
final ValueGetter<EagerGestureRecognizer> constructRecognizer = () {
++ factoryInvocationCount;
return EagerGestureRecognizer();
};
final PlatformViewSurface platformViewSurface = PlatformViewSurface(
controller: controller,
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{
Factory<OneSequenceGestureRecognizer>(
constructRecognizer,
),
});
await tester.pumpWidget(platformViewSurface);
await tester.pumpWidget(const SizedBox.shrink());
await tester.pumpWidget(platformViewSurface);
expect(factoryInvocationCount, 2);
});
testWidgets('PlatformViewSurface rebuilt with same gestureRecognizers', (WidgetTester tester) async {
int factoryInvocationCount = 0;
final ValueGetter<EagerGestureRecognizer> constructRecognizer = () {
++ factoryInvocationCount;
return EagerGestureRecognizer();
};
await tester.pumpWidget(
PlatformViewSurface(
controller: controller,
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{
Factory<OneSequenceGestureRecognizer>(
constructRecognizer,
),
})
);
await tester.pumpWidget(
PlatformViewSurface(
controller: controller,
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{
Factory<OneSequenceGestureRecognizer>(
constructRecognizer,
),
})
);
expect(factoryInvocationCount, 1);
});
}); });
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment