Unverified Commit dcbc694b authored by Maurice Parrish's avatar Maurice Parrish Committed by GitHub

Have AndroidViewController extend PlatformViewController and add support for...

Have AndroidViewController extend PlatformViewController and add support for hybrid platform views (#60320)
parent 462b0ea7
...@@ -27,61 +27,23 @@ class AndroidPlatformView extends StatelessWidget { ...@@ -27,61 +27,23 @@ class AndroidPlatformView extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return PlatformViewLink( return PlatformViewLink(
viewType: viewType, viewType: viewType,
onCreatePlatformView: _onCreateAndroidView, surfaceFactory:
surfaceFactory: (BuildContext context, PlatformViewController controller) { (BuildContext context, PlatformViewController controller) {
return PlatformViewSurface( return AndroidViewSurface(
controller: controller, controller: controller as AndroidViewController,
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{}, gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
hitTestBehavior: PlatformViewHitTestBehavior.opaque, hitTestBehavior: PlatformViewHitTestBehavior.opaque,
); );
}, },
onCreatePlatformView: (PlatformViewCreationParams params) {
return PlatformViewsService.initAndroidView(
id: params.id,
viewType: params.viewType,
layoutDirection: TextDirection.ltr,
)
..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
..create();
},
); );
} }
PlatformViewController _onCreateAndroidView(PlatformViewCreationParams params) {
final _AndroidViewController controller = _AndroidViewController(params.id, viewType);
controller._initialize().then((_) { params.onPlatformViewCreated(params.id); });
return controller;
}
}
// TODO(egarciad): The Android view controller should be defined in the framework.
// https://github.com/flutter/flutter/issues/55904
class _AndroidViewController extends PlatformViewController {
_AndroidViewController(
this.viewId,
this.viewType,
);
@override
final int viewId;
/// The unique identifier for the Android view type to be embedded by this widget.
///
/// A PlatformViewFactory for this type must have been registered.
final String viewType;
bool _initialized = false;
Future<void> _initialize() async {
// TODO(egarciad): Initialize platform view.
_initialized = true;
}
@override
void clearFocus() {
// TODO(egarciad): Implement clear focus.
}
@override
void dispatchPointerEvent(PointerEvent event) {
// TODO(egarciad): Implement dispatchPointerEvent
}
@override
void dispose() {
if (_initialized) {
// TODO(egarciad): Dispose the android view.
}
}
} }
...@@ -90,7 +90,7 @@ class RenderAndroidView extends RenderBox with _PlatformViewGestureMixin { ...@@ -90,7 +90,7 @@ class RenderAndroidView extends RenderBox with _PlatformViewGestureMixin {
assert(hitTestBehavior != null), assert(hitTestBehavior != null),
assert(gestureRecognizers != null), assert(gestureRecognizers != null),
_viewController = viewController { _viewController = viewController {
_motionEventsDispatcher = _MotionEventsDispatcher(globalToLocal, viewController); _viewController.pointTransformer = (Offset offset) => globalToLocal(offset);
updateGestureRecognizers(gestureRecognizers); updateGestureRecognizers(gestureRecognizers);
_viewController.addOnPlatformViewCreatedListener(_onPlatformViewCreated); _viewController.addOnPlatformViewCreatedListener(_onPlatformViewCreated);
this.hitTestBehavior = hitTestBehavior; this.hitTestBehavior = hitTestBehavior;
...@@ -139,7 +139,7 @@ class RenderAndroidView extends RenderBox with _PlatformViewGestureMixin { ...@@ -139,7 +139,7 @@ class RenderAndroidView extends RenderBox with _PlatformViewGestureMixin {
/// Any active gesture arena the Android view participates in is rejected when the /// Any active gesture arena the Android view participates in is rejected when the
/// set of gesture recognizers is changed. /// set of gesture recognizers is changed.
void updateGestureRecognizers(Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers) { void updateGestureRecognizers(Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers) {
_updateGestureRecognizersWithCallBack(gestureRecognizers, _motionEventsDispatcher.handlePointerEvent); _updateGestureRecognizersWithCallBack(gestureRecognizers, _viewController.dispatchPointerEvent);
} }
@override @override
...@@ -151,8 +151,6 @@ class RenderAndroidView extends RenderBox with _PlatformViewGestureMixin { ...@@ -151,8 +151,6 @@ class RenderAndroidView extends RenderBox with _PlatformViewGestureMixin {
@override @override
bool get isRepaintBoundary => true; bool get isRepaintBoundary => true;
_MotionEventsDispatcher _motionEventsDispatcher;
@override @override
void performResize() { void performResize() {
size = constraints.biggest; size = constraints.biggest;
...@@ -225,7 +223,7 @@ class RenderAndroidView extends RenderBox with _PlatformViewGestureMixin { ...@@ -225,7 +223,7 @@ class RenderAndroidView extends RenderBox with _PlatformViewGestureMixin {
config.isSemanticBoundary = true; config.isSemanticBoundary = true;
if (_viewController.isCreated) { if (_viewController.isCreated) {
config.platformViewId = _viewController.id; config.platformViewId = _viewController.viewId;
} }
} }
} }
...@@ -451,7 +449,7 @@ class _UiKitViewGestureRecognizer extends OneSequenceGestureRecognizer { ...@@ -451,7 +449,7 @@ class _UiKitViewGestureRecognizer extends OneSequenceGestureRecognizer {
} }
} }
typedef _HandlePointerEvent = void Function(PointerEvent event); typedef _HandlePointerEvent = Future<void> Function(PointerEvent event);
// This recognizer constructs gesture recognizers from a set of gesture recognizer factories // This recognizer constructs gesture recognizers from a set of gesture recognizer factories
// it was give, adds all of them to a gesture arena team with the _PlatformViewGestureRecognizer // it was give, adds all of them to a gesture arena team with the _PlatformViewGestureRecognizer
...@@ -555,144 +553,6 @@ class _PlatformViewGestureRecognizer extends OneSequenceGestureRecognizer { ...@@ -555,144 +553,6 @@ class _PlatformViewGestureRecognizer extends OneSequenceGestureRecognizer {
} }
} }
typedef _GlobalToLocal = Offset Function(Offset point);
// Composes a stream of PointerEvent objects into AndroidMotionEvent objects
// and dispatches them to the associated embedded Android view.
class _MotionEventsDispatcher {
_MotionEventsDispatcher(this.globalToLocal, this.viewController);
final Map<int, AndroidPointerCoords> pointerPositions = <int, AndroidPointerCoords>{};
final Map<int, AndroidPointerProperties> pointerProperties = <int, AndroidPointerProperties>{};
final _GlobalToLocal globalToLocal;
final AndroidViewController viewController;
int nextPointerId = 0;
int downTimeMillis;
void handlePointerEvent(PointerEvent event) {
if (event is PointerDownEvent) {
if (nextPointerId == 0)
downTimeMillis = event.timeStamp.inMilliseconds;
pointerProperties[event.pointer] = propertiesFor(event, nextPointerId++);
}
pointerPositions[event.pointer] = coordsFor(event);
dispatchPointerEvent(event);
if (event is PointerUpEvent) {
pointerPositions.remove(event.pointer);
pointerProperties.remove(event.pointer);
if (pointerProperties.isEmpty) {
nextPointerId = 0;
downTimeMillis = null;
}
}
if (event is PointerCancelEvent) {
pointerPositions.clear();
pointerProperties.clear();
nextPointerId = 0;
downTimeMillis = null;
}
}
void dispatchPointerEvent(PointerEvent event) {
final List<int> pointers = pointerPositions.keys.toList();
final int pointerIdx = pointers.indexOf(event.pointer);
final int numPointers = pointers.length;
// This value must match the value in engine's FlutterView.java.
// This flag indicates whether the original Android pointer events were batched together.
const int kPointerDataFlagBatched = 1;
// Android MotionEvent objects can batch information on multiple pointers.
// Flutter breaks these such batched events into multiple PointerEvent objects.
// When there are multiple active pointers we accumulate the information for all pointers
// as we get PointerEvents, and only send it to the embedded Android view when
// we see the last pointer. This way we achieve the same batching as Android.
if (event.platformData == kPointerDataFlagBatched ||
(isSinglePointerAction(event) && pointerIdx < numPointers - 1))
return;
int action;
switch (event.runtimeType) {
case PointerDownEvent:
action = numPointers == 1 ? AndroidViewController.kActionDown
: AndroidViewController.pointerAction(pointerIdx, AndroidViewController.kActionPointerDown);
break;
case PointerUpEvent:
action = numPointers == 1 ? AndroidViewController.kActionUp
: AndroidViewController.pointerAction(pointerIdx, AndroidViewController.kActionPointerUp);
break;
case PointerMoveEvent:
action = AndroidViewController.kActionMove;
break;
case PointerCancelEvent:
action = AndroidViewController.kActionCancel;
break;
default:
return;
}
final AndroidMotionEvent androidMotionEvent = AndroidMotionEvent(
downTime: downTimeMillis,
eventTime: event.timeStamp.inMilliseconds,
action: action,
pointerCount: pointerPositions.length,
pointerProperties: pointers.map<AndroidPointerProperties>((int i) => pointerProperties[i]).toList(),
pointerCoords: pointers.map<AndroidPointerCoords>((int i) => pointerPositions[i]).toList(),
metaState: 0,
buttonState: 0,
xPrecision: 1.0,
yPrecision: 1.0,
deviceId: 0,
edgeFlags: 0,
source: 0,
flags: 0,
);
viewController.sendMotionEvent(androidMotionEvent);
}
AndroidPointerCoords coordsFor(PointerEvent event) {
final Offset position = globalToLocal(event.position);
return AndroidPointerCoords(
orientation: event.orientation,
pressure: event.pressure,
size: event.size,
toolMajor: event.radiusMajor,
toolMinor: event.radiusMinor,
touchMajor: event.radiusMajor,
touchMinor: event.radiusMinor,
x: position.dx,
y: position.dy,
);
}
AndroidPointerProperties propertiesFor(PointerEvent event, int pointerId) {
int toolType = AndroidPointerProperties.kToolTypeUnknown;
switch(event.kind) {
case PointerDeviceKind.touch:
toolType = AndroidPointerProperties.kToolTypeFinger;
break;
case PointerDeviceKind.mouse:
toolType = AndroidPointerProperties.kToolTypeMouse;
break;
case PointerDeviceKind.stylus:
toolType = AndroidPointerProperties.kToolTypeStylus;
break;
case PointerDeviceKind.invertedStylus:
toolType = AndroidPointerProperties.kToolTypeEraser;
break;
case PointerDeviceKind.unknown:
toolType = AndroidPointerProperties.kToolTypeUnknown;
break;
}
return AndroidPointerProperties(id: pointerId, toolType: toolType);
}
bool isSinglePointerAction(PointerEvent event) => event is! PointerDownEvent && event is! PointerUpEvent;
}
/// A render object for embedding a platform view. /// A render object for embedding a platform view.
/// ///
/// [PlatformViewRenderBox] presents a platform view by adding a [PlatformViewLayer] layer, /// [PlatformViewRenderBox] presents a platform view by adding a [PlatformViewLayer] layer,
......
...@@ -78,7 +78,9 @@ class PlatformViewsService { ...@@ -78,7 +78,9 @@ class PlatformViewsService {
final Map<int, VoidCallback> _focusCallbacks = <int, VoidCallback>{}; final Map<int, VoidCallback> _focusCallbacks = <int, VoidCallback>{};
/// Creates a controller for a new Android view. /// Creates a [TextureAndroidViewController] for a new Android view.
///
/// The view is created after calling [TextureAndroidViewController.setSize].
/// ///
/// `id` is an unused unique identifier generated with [platformViewsRegistry]. /// `id` is an unused unique identifier generated with [platformViewsRegistry].
/// ///
...@@ -102,7 +104,7 @@ class PlatformViewsService { ...@@ -102,7 +104,7 @@ class PlatformViewsService {
/// ///
/// The `id, `viewType, and `layoutDirection` parameters must not be null. /// The `id, `viewType, and `layoutDirection` parameters must not be null.
/// If `creationParams` is non null then `creationParamsCodec` must not be null. /// If `creationParams` is non null then `creationParamsCodec` must not be null.
static AndroidViewController initAndroidView({ static TextureAndroidViewController initAndroidView({
@required int id, @required int id,
@required String viewType, @required String viewType,
@required TextDirection layoutDirection, @required TextDirection layoutDirection,
...@@ -114,13 +116,66 @@ class PlatformViewsService { ...@@ -114,13 +116,66 @@ class PlatformViewsService {
assert(viewType != null); assert(viewType != null);
assert(layoutDirection != null); assert(layoutDirection != null);
assert(creationParams == null || creationParamsCodec != null); assert(creationParams == null || creationParamsCodec != null);
final AndroidViewController controller = AndroidViewController._(
id, final TextureAndroidViewController controller = TextureAndroidViewController._(
viewType, viewId: id,
creationParams, viewType: viewType,
creationParamsCodec, layoutDirection: layoutDirection,
layoutDirection, creationParams: creationParams,
creationParamsCodec: creationParamsCodec,
); );
_instance._focusCallbacks[id] = onFocus ?? () {};
return controller;
}
/// Creates a [SurfaceAndroidViewController] for a new Android view.
///
/// The view is created after calling [AndroidViewController.create].
///
/// `id` is an unused unique identifier generated with [platformViewsRegistry].
///
/// `viewType` is the identifier of the Android view type to be created, a
/// factory for this view type must have been registered on the platform side.
/// Platform view factories are typically registered by plugin code.
/// Plugins can register a platform view factory with
/// [PlatformViewRegistry#registerViewFactory](/javadoc/io/flutter/plugin/platform/PlatformViewRegistry.html#registerViewFactory-java.lang.String-io.flutter.plugin.platform.PlatformViewFactory-).
///
/// `creationParams` will be passed as the args argument of [PlatformViewFactory#create](/javadoc/io/flutter/plugin/platform/PlatformViewFactory.html#create-android.content.Context-int-java.lang.Object-)
///
/// `creationParamsCodec` is the codec used to encode `creationParams` before sending it to the
/// platform side. It should match the codec passed to the constructor of [PlatformViewFactory](/javadoc/io/flutter/plugin/platform/PlatformViewFactory.html#PlatformViewFactory-io.flutter.plugin.common.MessageCodec-).
/// This is typically one of: [StandardMessageCodec], [JSONMessageCodec], [StringCodec], or [BinaryCodec].
///
/// `onFocus` is a callback that will be invoked when the Android View asks to get the
/// input focus.
///
/// The Android view will only be created after [AndroidViewController.setSize] is called for the
/// first time.
///
/// The `id, `viewType, and `layoutDirection` parameters must not be null.
/// If `creationParams` is non null then `creationParamsCodec` must not be null.
static SurfaceAndroidViewController initSurfaceAndroidView({
@required int id,
@required String viewType,
@required TextDirection layoutDirection,
dynamic creationParams,
MessageCodec<dynamic> creationParamsCodec,
VoidCallback onFocus,
}) {
assert(id != null);
assert(viewType != null);
assert(layoutDirection != null);
assert(creationParams == null || creationParamsCodec != null);
final SurfaceAndroidViewController controller = SurfaceAndroidViewController._(
viewId: id,
viewType: viewType,
layoutDirection: layoutDirection,
creationParams: creationParams,
creationParamsCodec: creationParamsCodec,
);
_instance._focusCallbacks[id] = onFocus ?? () {}; _instance._focusCallbacks[id] = onFocus ?? () {};
return controller; return controller;
} }
...@@ -427,25 +482,178 @@ enum _AndroidViewState { ...@@ -427,25 +482,178 @@ enum _AndroidViewState {
disposed, disposed,
} }
// Helper for converting PointerEvents into AndroidMotionEvents.
class _AndroidMotionEventConverter {
_AndroidMotionEventConverter();
final Map<int, AndroidPointerCoords> pointerPositions =
<int, AndroidPointerCoords>{};
final Map<int, AndroidPointerProperties> pointerProperties =
<int, AndroidPointerProperties>{};
Offset Function(Offset position) _pointTransformer;
set pointTransformer(Offset Function(Offset position) transformer) {
assert(transformer != null);
_pointTransformer = transformer;
}
int nextPointerId = 0;
int downTimeMillis;
void handlePointerDownEvent(PointerDownEvent event) {
if (nextPointerId == 0) {
downTimeMillis = event.timeStamp.inMilliseconds;
}
pointerProperties[event.pointer] = propertiesFor(event, nextPointerId++);
}
void updatePointerPositions(PointerEvent event) {
assert(_pointTransformer != null);
final Offset position = _pointTransformer(event.position);
pointerPositions[event.pointer] = AndroidPointerCoords(
orientation: event.orientation,
pressure: event.pressure,
size: event.size,
toolMajor: event.radiusMajor,
toolMinor: event.radiusMinor,
touchMajor: event.radiusMajor,
touchMinor: event.radiusMinor,
x: position.dx,
y: position.dy,
);
}
void handlePointerUpEvent(PointerUpEvent event) {
pointerPositions.remove(event.pointer);
pointerProperties.remove(event.pointer);
if (pointerProperties.isEmpty) {
nextPointerId = 0;
downTimeMillis = null;
}
}
void handlePointerCancelEvent(PointerCancelEvent event) {
pointerPositions.clear();
pointerProperties.clear();
nextPointerId = 0;
downTimeMillis = null;
}
AndroidMotionEvent toAndroidMotionEvent(PointerEvent event) {
final List<int> pointers = pointerPositions.keys.toList();
final int pointerIdx = pointers.indexOf(event.pointer);
final int numPointers = pointers.length;
// This value must match the value in engine's FlutterView.java.
// This flag indicates whether the original Android pointer events were batched together.
const int kPointerDataFlagBatched = 1;
// Android MotionEvent objects can batch information on multiple pointers.
// Flutter breaks these such batched events into multiple PointerEvent objects.
// When there are multiple active pointers we accumulate the information for all pointers
// as we get PointerEvents, and only send it to the embedded Android view when
// we see the last pointer. This way we achieve the same batching as Android.
if (event.platformData == kPointerDataFlagBatched ||
(isSinglePointerAction(event) && pointerIdx < numPointers - 1)) {
return null;
}
int action;
switch (event.runtimeType) {
case PointerDownEvent:
action = numPointers == 1
? AndroidViewController.kActionDown
: AndroidViewController.pointerAction(
pointerIdx, AndroidViewController.kActionPointerDown);
break;
case PointerUpEvent:
action = numPointers == 1
? AndroidViewController.kActionUp
: AndroidViewController.pointerAction(
pointerIdx, AndroidViewController.kActionPointerUp);
break;
case PointerMoveEvent:
action = AndroidViewController.kActionMove;
break;
case PointerCancelEvent:
action = AndroidViewController.kActionCancel;
break;
default:
return null;
}
return AndroidMotionEvent(
downTime: downTimeMillis,
eventTime: event.timeStamp.inMilliseconds,
action: action,
pointerCount: pointerPositions.length,
pointerProperties: pointers
.map<AndroidPointerProperties>((int i) => pointerProperties[i])
.toList(),
pointerCoords: pointers
.map<AndroidPointerCoords>((int i) => pointerPositions[i])
.toList(),
metaState: 0,
buttonState: 0,
xPrecision: 1.0,
yPrecision: 1.0,
deviceId: 0,
edgeFlags: 0,
source: 0,
flags: 0,
);
}
AndroidPointerProperties propertiesFor(PointerEvent event, int pointerId) {
int toolType = AndroidPointerProperties.kToolTypeUnknown;
switch (event.kind) {
case PointerDeviceKind.touch:
toolType = AndroidPointerProperties.kToolTypeFinger;
break;
case PointerDeviceKind.mouse:
toolType = AndroidPointerProperties.kToolTypeMouse;
break;
case PointerDeviceKind.stylus:
toolType = AndroidPointerProperties.kToolTypeStylus;
break;
case PointerDeviceKind.invertedStylus:
toolType = AndroidPointerProperties.kToolTypeEraser;
break;
case PointerDeviceKind.unknown:
toolType = AndroidPointerProperties.kToolTypeUnknown;
break;
}
return AndroidPointerProperties(id: pointerId, toolType: toolType);
}
bool isSinglePointerAction(PointerEvent event) =>
event is! PointerDownEvent && event is! PointerUpEvent;
}
/// Controls an Android view. /// Controls an Android view.
/// ///
/// Typically created with [PlatformViewsService.initAndroidView]. /// Typically created with [PlatformViewsService.initAndroidView].
class AndroidViewController { // TODO(bparrishMines): Remove abstract methods that are not required by all subclasses.
AndroidViewController._( abstract class AndroidViewController extends PlatformViewController {
this.id, AndroidViewController._({
String viewType, @required this.viewId,
@required String viewType,
@required TextDirection layoutDirection,
dynamic creationParams, dynamic creationParams,
MessageCodec<dynamic> creationParamsCodec, MessageCodec<dynamic> creationParamsCodec,
TextDirection layoutDirection, bool waitingForSize = false,
) : assert(id != null), }) : assert(viewId != null),
assert(viewType != null), assert(viewType != null),
assert(layoutDirection != null), assert(layoutDirection != null),
assert(creationParams == null || creationParamsCodec != null), assert(creationParams == null || creationParamsCodec != null),
_viewType = viewType, _viewType = viewType,
_layoutDirection = layoutDirection,
_creationParams = creationParams, _creationParams = creationParams,
_creationParamsCodec = creationParamsCodec, _creationParamsCodec = creationParamsCodec,
_layoutDirection = layoutDirection, _state = waitingForSize
_state = _AndroidViewState.waitingForSize; ? _AndroidViewState.waitingForSize
: _AndroidViewState.creating;
/// Action code for when a primary pointer touched the screen. /// Action code for when a primary pointer touched the screen.
/// ///
...@@ -484,18 +692,14 @@ class AndroidViewController { ...@@ -484,18 +692,14 @@ class AndroidViewController {
static const int kAndroidLayoutDirectionRtl = 1; static const int kAndroidLayoutDirectionRtl = 1;
/// The unique identifier of the Android view controlled by this controller. /// The unique identifier of the Android view controlled by this controller.
final int id; @override
final int viewId;
final String _viewType; final String _viewType;
/// The texture entry id into which the Android view is rendered. // Helps convert PointerEvents to AndroidMotionEvents.
int _textureId; final _AndroidMotionEventConverter _motionEventConverter =
_AndroidMotionEventConverter();
/// Returns the texture entry id that the Android view is rendering into.
///
/// Returns null if the Android view has not been successfully created, or if it has been
/// disposed.
int get textureId => _textureId;
TextDirection _layoutDirection; TextDirection _layoutDirection;
...@@ -505,36 +709,40 @@ class AndroidViewController { ...@@ -505,36 +709,40 @@ class AndroidViewController {
final MessageCodec<dynamic> _creationParamsCodec; final MessageCodec<dynamic> _creationParamsCodec;
final List<PlatformViewCreatedCallback> _platformViewCreatedCallbacks = <PlatformViewCreatedCallback>[]; final List<PlatformViewCreatedCallback> _platformViewCreatedCallbacks =
<PlatformViewCreatedCallback>[];
/// Whether the platform view has already been created. static int _getAndroidDirection(TextDirection direction) {
bool get isCreated => _state == _AndroidViewState.created; assert(direction != null);
switch (direction) {
/// Adds a callback that will get invoke after the platform view has been case TextDirection.ltr:
/// created. return kAndroidLayoutDirectionLtr;
void addOnPlatformViewCreatedListener(PlatformViewCreatedCallback listener) { case TextDirection.rtl:
assert(listener != null); return kAndroidLayoutDirectionRtl;
assert(_state != _AndroidViewState.disposed); }
_platformViewCreatedCallbacks.add(listener); return null;
} }
/// Removes a callback added with [addOnPlatformViewCreatedListener]. /// Creates a masked Android MotionEvent action value for an indexed pointer.
void removeOnPlatformViewCreatedListener(PlatformViewCreatedCallback listener) { static int pointerAction(int pointerId, int action) {
assert(_state != _AndroidViewState.disposed); return ((pointerId << 8) & 0xff00) | (action & 0xff);
_platformViewCreatedCallbacks.remove(listener);
} }
/// Disposes the Android view. Future<void> _sendDisposeMessage();
Future<void> _sendCreateMessage();
/// Creates the Android View.
/// ///
/// The [AndroidViewController] object is unusable after calling this. /// Throws an [AssertionError] if view was already disposed.
/// The identifier of the platform view cannot be reused after the view is Future<void> create() async {
/// disposed. assert(_state != _AndroidViewState.disposed, 'trying to create a disposed Android view');
Future<void> dispose() async {
if (_state == _AndroidViewState.creating || _state == _AndroidViewState.created) await _sendCreateMessage();
await SystemChannels.platform_views.invokeMethod<void>('dispose', id);
_platformViewCreatedCallbacks.clear(); _state = _AndroidViewState.created;
_state = _AndroidViewState.disposed; for (final PlatformViewCreatedCallback callback in _platformViewCreatedCallbacks) {
PlatformViewsService._instance._focusCallbacks.remove(id); callback(viewId);
}
} }
/// Sizes the Android View. /// Sizes the Android View.
...@@ -543,25 +751,68 @@ class AndroidViewController { ...@@ -543,25 +751,68 @@ class AndroidViewController {
/// be bigger than zero. /// be bigger than zero.
/// ///
/// The first time a size is set triggers the creation of the Android view. /// The first time a size is set triggers the creation of the Android view.
Future<void> setSize(Size size) async { Future<void> setSize(Size size);
assert(_state != _AndroidViewState.disposed, 'trying to size a disposed Android View. View id: $id');
assert(size != null); /// Returns the texture entry id that the Android view is rendering into.
assert(!size.isEmpty); ///
/// Returns null if the Android view has not been successfully created, or if it has been
/// disposed.
int get textureId;
if (_state == _AndroidViewState.waitingForSize) /// The unique identifier of the Android view controlled by this controller.
return _create(size); @Deprecated(
'Call `controller.viewId` instead. '
'This feature was deprecated after v1.20.0-2.0.pre.'
)
int get id => viewId;
await SystemChannels.platform_views.invokeMethod<void>('resize', <String, dynamic>{ /// Sends an Android [MotionEvent](https://developer.android.com/reference/android/view/MotionEvent)
'id': id, /// to the view.
'width': size.width, ///
'height': size.height, /// The Android MotionEvent object is created with [MotionEvent.obtain](https://developer.android.com/reference/android/view/MotionEvent.html#obtain(long,%20long,%20int,%20float,%20float,%20float,%20float,%20int,%20float,%20float,%20int,%20int)).
}); /// See documentation of [MotionEvent.obtain](https://developer.android.com/reference/android/view/MotionEvent.html#obtain(long,%20long,%20int,%20float,%20float,%20float,%20float,%20int,%20float,%20float,%20int,%20int))
/// for description of the parameters.
///
/// See [AndroidViewController.dispatchPointerEvent] for sending a
/// [PointerEvent].
Future<void> sendMotionEvent(AndroidMotionEvent event) async {
await SystemChannels.platform_views.invokeMethod<dynamic>(
'touch',
event._asList(viewId),
);
}
/// Converts a given point from the global coordinate system in logical pixels to the local coordinate system for this box.
///
/// This is required to convert a [PointerEvent] to an [AndroidMotionEvent].
/// It is typically provided by using [RenderBox.globalToLocal].
set pointTransformer(Offset Function(Offset position) transformer) {
assert(transformer != null);
_motionEventConverter._pointTransformer = transformer;
}
/// Whether the platform view has already been created.
bool get isCreated => _state == _AndroidViewState.created;
/// Adds a callback that will get invoke after the platform view has been
/// created.
void addOnPlatformViewCreatedListener(PlatformViewCreatedCallback listener) {
assert(listener != null);
assert(_state != _AndroidViewState.disposed);
_platformViewCreatedCallbacks.add(listener);
}
/// Removes a callback added with [addOnPlatformViewCreatedListener].
void removeOnPlatformViewCreatedListener(
PlatformViewCreatedCallback listener) {
assert(_state != _AndroidViewState.disposed);
_platformViewCreatedCallbacks.remove(listener);
} }
/// Sets the layout direction for the Android view. /// Sets the layout direction for the Android view.
Future<void> setLayoutDirection(TextDirection layoutDirection) async { Future<void> setLayoutDirection(TextDirection layoutDirection) async {
assert(_state != _AndroidViewState.disposed,'trying to set a layout direction for a disposed UIView. View id: $id'); assert(_state != _AndroidViewState.disposed,
'trying to set a layout direction for a disposed UIView. View id: $viewId');
if (layoutDirection == _layoutDirection) if (layoutDirection == _layoutDirection)
return; return;
...@@ -574,56 +825,202 @@ class AndroidViewController { ...@@ -574,56 +825,202 @@ class AndroidViewController {
if (_state == _AndroidViewState.waitingForSize) if (_state == _AndroidViewState.waitingForSize)
return; return;
await SystemChannels.platform_views.invokeMethod<void>('setDirection', <String, dynamic>{ await SystemChannels.platform_views
'id': id, .invokeMethod<void>('setDirection', <String, dynamic>{
'id': viewId,
'direction': _getAndroidDirection(layoutDirection), 'direction': _getAndroidDirection(layoutDirection),
}); });
} }
/// Converts the [PointerEvent] and sends an Android [MotionEvent](https://developer.android.com/reference/android/view/MotionEvent)
/// to the view.
///
/// This method can only be used if a [PointTransformer] is provided to
/// [AndroidViewController.pointTransformer]. Otherwise, an [AssertionError]
/// is thrown. See [AndroidViewController.sendMotionEvent] for sending a
/// `MotionEvent` without a [PointTransformer].
///
/// The Android MotionEvent object is created with [MotionEvent.obtain](https://developer.android.com/reference/android/view/MotionEvent.html#obtain(long,%20long,%20int,%20float,%20float,%20float,%20float,%20int,%20float,%20float,%20int,%20int)).
/// See documentation of [MotionEvent.obtain](https://developer.android.com/reference/android/view/MotionEvent.html#obtain(long,%20long,%20int,%20float,%20float,%20float,%20float,%20int,%20float,%20float,%20int,%20int))
/// for description of the parameters.
@override
Future<void> dispatchPointerEvent(PointerEvent event) async {
assert(_motionEventConverter._pointTransformer != null);
if (event is PointerDownEvent) {
_motionEventConverter.handlePointerDownEvent(event);
}
_motionEventConverter.updatePointerPositions(event);
final AndroidMotionEvent androidEvent =
_motionEventConverter.toAndroidMotionEvent(event);
if (event is PointerUpEvent) {
_motionEventConverter.handlePointerUpEvent(event);
} else if (event is PointerCancelEvent) {
_motionEventConverter.handlePointerCancelEvent(event);
}
if (androidEvent != null) {
await sendMotionEvent(androidEvent);
}
}
/// Clears the focus from the Android View if it is focused. /// Clears the focus from the Android View if it is focused.
@override
Future<void> clearFocus() { Future<void> clearFocus() {
if (_state != _AndroidViewState.created) { if (_state != _AndroidViewState.created) {
return null; return null;
} }
return SystemChannels.platform_views.invokeMethod<void>('clearFocus', id); return SystemChannels.platform_views.invokeMethod<void>('clearFocus', viewId);
} }
static int _getAndroidDirection(TextDirection direction) { /// Disposes the Android view.
assert(direction != null); ///
switch (direction) { /// The [AndroidViewController] object is unusable after calling this.
case TextDirection.ltr: /// The identifier of the platform view cannot be reused after the view is
return kAndroidLayoutDirectionLtr; /// disposed.
case TextDirection.rtl: @override
return kAndroidLayoutDirectionRtl; Future<void> dispose() async {
if (_state == _AndroidViewState.creating || _state == _AndroidViewState.created)
await _sendDisposeMessage();
_platformViewCreatedCallbacks.clear();
_state = _AndroidViewState.disposed;
PlatformViewsService._instance._focusCallbacks.remove(viewId);
} }
return null; }
/// Controls an Android view by rendering to an [AndroidViewSurface].
///
/// Typically created with [PlatformViewsService.initAndroidView].
class SurfaceAndroidViewController extends AndroidViewController {
SurfaceAndroidViewController._({
@required int viewId,
@required String viewType,
@required TextDirection layoutDirection,
dynamic creationParams,
MessageCodec<dynamic> creationParamsCodec,
}) : super._(
viewId: viewId,
viewType: viewType,
layoutDirection: layoutDirection,
creationParams: creationParams,
creationParamsCodec: creationParamsCodec);
@override
Future<void> _sendCreateMessage() {
final Map<String, dynamic> args = <String, dynamic>{
'id': viewId,
'viewType': _viewType,
'direction': AndroidViewController._getAndroidDirection(_layoutDirection),
'hybrid': true,
};
if (_creationParams != null) {
final ByteData paramsByteData =
_creationParamsCodec.encodeMessage(_creationParams);
args['params'] = Uint8List.view(
paramsByteData.buffer,
0,
paramsByteData.lengthInBytes,
);
}
return SystemChannels.platform_views.invokeMethod<void>('create', args);
} }
/// Sends an Android [MotionEvent](https://developer.android.com/reference/android/view/MotionEvent) @override
/// to the view. int get textureId {
/// throw UnimplementedError('Not supported for $SurfaceAndroidViewController.');
/// The Android MotionEvent object is created with [MotionEvent.obtain](https://developer.android.com/reference/android/view/MotionEvent.html#obtain(long,%20long,%20int,%20float,%20float,%20float,%20float,%20int,%20float,%20float,%20int,%20int)). }
/// See documentation of [MotionEvent.obtain](https://developer.android.com/reference/android/view/MotionEvent.html#obtain(long,%20long,%20int,%20float,%20float,%20float,%20float,%20int,%20float,%20float,%20int,%20int))
/// for description of the parameters. @override
Future<void> sendMotionEvent(AndroidMotionEvent event) async { Future<void> _sendDisposeMessage() {
await SystemChannels.platform_views.invokeMethod<dynamic>( return SystemChannels.platform_views
'touch', .invokeMethod<void>('dispose', <String, dynamic>{
event._asList(id), 'id': viewId,
'hybrid': true,
});
}
@override
Future<void> setSize(Size size) {
throw UnimplementedError('Not supported for $SurfaceAndroidViewController.');
}
}
/// Controls an Android view that is rendered to a texture.
///
/// This is typically used by [AndroidView] to display an Android View in a
/// [VirtualDisplay](https://developer.android.com/reference/android/hardware/display/VirtualDisplay).
///
/// Typically created with [PlatformViewsService.initAndroidView].
class TextureAndroidViewController extends AndroidViewController {
TextureAndroidViewController._({
@required int viewId,
@required String viewType,
@required TextDirection layoutDirection,
dynamic creationParams,
MessageCodec<dynamic> creationParamsCodec,
}) : super._(
viewId: viewId,
viewType: viewType,
layoutDirection: layoutDirection,
creationParams: creationParams,
creationParamsCodec: creationParamsCodec,
waitingForSize: true,
); );
/// The texture entry id into which the Android view is rendered.
int _textureId;
/// Returns the texture entry id that the Android view is rendering into.
///
/// Returns null if the Android view has not been successfully created, or if it has been
/// disposed.
@override
int get textureId => _textureId;
Size _size;
@override
Future<void> setSize(Size size) async {
assert(_state != _AndroidViewState.disposed,
'trying to size a disposed Android View. View id: $viewId');
assert(size != null);
assert(!size.isEmpty);
if (_state == _AndroidViewState.waitingForSize) {
_size = size;
return create();
} }
/// Creates a masked Android MotionEvent action value for an indexed pointer. await SystemChannels.platform_views.invokeMethod<void>('resize', <String, dynamic>{
static int pointerAction(int pointerId, int action) { 'id': viewId,
return ((pointerId << 8) & 0xff00) | (action & 0xff); 'width': size.width,
'height': size.height,
});
} }
Future<void> _create(Size size) async { /// Creates the Android View.
///
/// This should not be called before [AndroidViewController.setSize].
///
/// Throws an [AssertionError] if view was already disposed.
@override
Future<void> create() => super.create();
@override
Future<void> _sendCreateMessage() async {
assert(_size != null && !_size.isEmpty,
'trying to create $TextureAndroidViewController without setting a valid size.');
final Map<String, dynamic> args = <String, dynamic>{ final Map<String, dynamic> args = <String, dynamic>{
'id': id, 'id': viewId,
'viewType': _viewType, 'viewType': _viewType,
'width': size.width, 'width': _size.width,
'height': size.height, 'height': _size.height,
'direction': _getAndroidDirection(_layoutDirection), 'direction': AndroidViewController._getAndroidDirection(_layoutDirection),
}; };
if (_creationParams != null) { if (_creationParams != null) {
final ByteData paramsByteData = _creationParamsCodec.encodeMessage(_creationParams); final ByteData paramsByteData = _creationParamsCodec.encodeMessage(_creationParams);
...@@ -633,11 +1030,12 @@ class AndroidViewController { ...@@ -633,11 +1030,12 @@ class AndroidViewController {
paramsByteData.lengthInBytes, paramsByteData.lengthInBytes,
); );
} }
_textureId = await SystemChannels.platform_views.invokeMethod('create', args); _textureId = await SystemChannels.platform_views.invokeMethod<int>('create', args);
_state = _AndroidViewState.created;
for (final PlatformViewCreatedCallback callback in _platformViewCreatedCallbacks) {
callback(id);
} }
@override
Future<void> _sendDisposeMessage() {
return SystemChannels.platform_views.invokeMethod<void>('dispose', viewId);
} }
} }
...@@ -726,13 +1124,13 @@ abstract class PlatformViewController { ...@@ -726,13 +1124,13 @@ abstract class PlatformViewController {
int get viewId; int get viewId;
/// Dispatches the `event` to the platform view. /// Dispatches the `event` to the platform view.
void dispatchPointerEvent(PointerEvent event); Future<void> dispatchPointerEvent(PointerEvent event);
/// Disposes the platform view. /// Disposes the platform view.
/// ///
/// The [PlatformViewController] is unusable after calling dispose. /// The [PlatformViewController] is unusable after calling dispose.
void dispose(); Future<void> dispose();
/// Clears the view's focus on the platform side. /// Clears the view's focus on the platform side.
void clearFocus(); Future<void> clearFocus();
} }
...@@ -398,22 +398,21 @@ class _HtmlElementViewController extends PlatformViewController { ...@@ -398,22 +398,21 @@ class _HtmlElementViewController extends PlatformViewController {
} }
@override @override
void clearFocus() { Future<void> clearFocus() async {
// Currently this does nothing on Flutter Web. // Currently this does nothing on Flutter Web.
// TODO(het): Implement this. See https://github.com/flutter/flutter/issues/39496 // TODO(het): Implement this. See https://github.com/flutter/flutter/issues/39496
} }
@override @override
void dispatchPointerEvent(PointerEvent event) { Future<void> dispatchPointerEvent(PointerEvent event) async {
// We do not dispatch pointer events to HTML views because they may contain // We do not dispatch pointer events to HTML views because they may contain
// cross-origin iframes, which only accept user-generated events. // cross-origin iframes, which only accept user-generated events.
} }
@override @override
void dispose() { Future<void> dispose() async {
if (_initialized) { if (_initialized) {
// Asynchronously dispose this view. await SystemChannels.platform_views.invokeMethod<void>('dispose', viewId);
SystemChannels.platform_views.invokeMethod<void>('dispose', viewId);
} }
} }
} }
...@@ -904,8 +903,9 @@ class _PlatformViewLinkState extends State<PlatformViewLink> { ...@@ -904,8 +903,9 @@ class _PlatformViewLinkState extends State<PlatformViewLink> {
/// Integrates a platform view with Flutter's compositor, touch, and semantics subsystems. /// Integrates a platform view with Flutter's compositor, touch, and semantics subsystems.
/// ///
/// The compositor integration is done by adding a [PlatformViewLayer] to the layer tree. [PlatformViewLayer] /// The compositor integration is done by adding a [PlatformViewLayer] to the layer tree. [PlatformViewSurface]
/// isn't supported on all platforms (e.g on Android platform views are composited using a [TextureLayer]). /// isn't supported on all platforms (e.g on Android platform views can be composited by using a [TextureLayer] or
/// [AndroidViewSurface]).
/// Custom Flutter embedders can support [PlatformViewLayer]s by implementing a SystemCompositor. /// Custom Flutter embedders can support [PlatformViewLayer]s by implementing a SystemCompositor.
/// ///
/// The widget fills all available space, the parent of this object must provide bounded layout /// The widget fills all available space, the parent of this object must provide bounded layout
...@@ -915,7 +915,7 @@ class _PlatformViewLinkState extends State<PlatformViewLink> { ...@@ -915,7 +915,7 @@ class _PlatformViewLinkState extends State<PlatformViewLink> {
/// ///
/// See also: /// See also:
/// ///
/// * [AndroidView] which embeds an Android platform view in the widget hierarchy. /// * [AndroidView] which embeds an Android platform view in the widget hierarchy using a [TextureLayer].
/// * [UIKitView] which embeds an iOS platform view in the widget hierarchy. /// * [UIKitView] which embeds an iOS platform view in the widget hierarchy.
// TODO(amirh): Link to the embedder's system compositor documentation once available. // TODO(amirh): Link to the embedder's system compositor documentation once available.
class PlatformViewSurface extends LeafRenderObjectWidget { class PlatformViewSurface extends LeafRenderObjectWidget {
...@@ -996,3 +996,46 @@ class PlatformViewSurface extends LeafRenderObjectWidget { ...@@ -996,3 +996,46 @@ class PlatformViewSurface extends LeafRenderObjectWidget {
..updateGestureRecognizers(gestureRecognizers); ..updateGestureRecognizers(gestureRecognizers);
} }
} }
/// Integrates an Android view with Flutter's compositor, touch, and semantics subsystems.
///
/// The compositor integration is done by adding a [PlatformViewLayer] to the layer tree. [PlatformViewLayer]
/// isn't supported on all platforms. Custom Flutter embedders can support
/// [PlatformViewLayer]s by implementing a SystemCompositor.
///
/// The widget fills all available space, the parent of this object must provide bounded layout
/// constraints.
///
/// If the associated platform view is not created, the [AndroidViewSurface] does not paint any contents.
///
/// See also:
///
/// * [AndroidView] which embeds an Android platform view in the widget hierarchy using a [TextureLayer].
/// * [UIKitView] which embeds an iOS platform view in the widget hierarchy.
class AndroidViewSurface extends PlatformViewSurface {
/// Construct an `AndroidPlatformViewSurface`.
const AndroidViewSurface({
Key key,
@required AndroidViewController controller,
@required PlatformViewHitTestBehavior hitTestBehavior,
@required Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers,
}) : assert(controller != null),
assert(hitTestBehavior != null),
assert(gestureRecognizers != null),
super(
key: key,
controller: controller,
hitTestBehavior: hitTestBehavior,
gestureRecognizers: gestureRecognizers);
@override
RenderObject createRenderObject(BuildContext context) {
final PlatformViewRenderBox renderBox =
super.createRenderObject(context) as PlatformViewRenderBox;
(controller as AndroidViewController).pointTransformer =
(Offset position) => renderBox.globalToLocal(position);
return renderBox;
}
}
...@@ -31,7 +31,7 @@ class FakePlatformViewController extends PlatformViewController { ...@@ -31,7 +31,7 @@ class FakePlatformViewController extends PlatformViewController {
int get viewId => _id; int get viewId => _id;
@override @override
void dispatchPointerEvent(PointerEvent event) { Future<void> dispatchPointerEvent(PointerEvent event) async {
dispatchedPointerEvents.add(event); dispatchedPointerEvents.add(event);
} }
...@@ -42,16 +42,92 @@ class FakePlatformViewController extends PlatformViewController { ...@@ -42,16 +42,92 @@ class FakePlatformViewController extends PlatformViewController {
} }
@override @override
void dispose() { Future<void> dispose() async {
disposed = true; disposed = true;
} }
@override @override
void clearFocus() { Future<void> clearFocus() async {
focusCleared = true; focusCleared = true;
} }
} }
class FakeAndroidViewController implements AndroidViewController {
FakeAndroidViewController(this.viewId);
bool disposed = false;
bool focusCleared = false;
bool created = false;
/// Events that are dispatched;
List<PointerEvent> dispatchedPointerEvents = <PointerEvent>[];
@override
final int viewId;
@override
Offset Function(Offset position) pointTransformer;
@override
Future<void> dispatchPointerEvent(PointerEvent event) async {
dispatchedPointerEvents.add(event);
}
void clearTestingVariables() {
dispatchedPointerEvents.clear();
disposed = false;
focusCleared = false;
}
@override
Future<void> dispose() async {
disposed = true;
}
@override
Future<void> clearFocus() async {
focusCleared = true;
}
@override
Future<void> setSize(Size size) {
throw UnimplementedError();
}
@override
int get textureId => throw UnimplementedError();
@override
bool isCreated;
@override
void addOnPlatformViewCreatedListener(PlatformViewCreatedCallback listener) =>
throw UnimplementedError();
@override
int get id => throw UnimplementedError();
@override
void removeOnPlatformViewCreatedListener(PlatformViewCreatedCallback listener) {
throw UnimplementedError();
}
@override
Future<void> sendMotionEvent(AndroidMotionEvent event) {
throw UnimplementedError();
}
@override
Future<void> setLayoutDirection(TextDirection layoutDirection) {
throw UnimplementedError();
}
@override
Future<void> create() async {
created = true;
}
}
class FakeAndroidPlatformViewsController { class FakeAndroidPlatformViewsController {
FakeAndroidPlatformViewsController() { FakeAndroidPlatformViewsController() {
SystemChannels.platform_views.setMockMethodCallHandler(_onMethodCall); SystemChannels.platform_views.setMockMethodCallHandler(_onMethodCall);
...@@ -108,6 +184,7 @@ class FakeAndroidPlatformViewsController { ...@@ -108,6 +184,7 @@ class FakeAndroidPlatformViewsController {
final double width = args['width'] as double; final double width = args['width'] as double;
final double height = args['height'] as double; final double height = args['height'] as double;
final int layoutDirection = args['direction'] as int; final int layoutDirection = args['direction'] as int;
final bool hybrid = args['hybrid'] as bool;
final Uint8List creationParams = args['params'] as Uint8List; final Uint8List creationParams = args['params'] as Uint8List;
if (_views.containsKey(id)) if (_views.containsKey(id))
...@@ -126,13 +203,23 @@ class FakeAndroidPlatformViewsController { ...@@ -126,13 +203,23 @@ class FakeAndroidPlatformViewsController {
await createCompleter.future; await createCompleter.future;
} }
_views[id] = FakeAndroidPlatformView(id, viewType, Size(width, height), layoutDirection, creationParams); _views[id] = FakeAndroidPlatformView(id, viewType,
width != null || height != null ? Size(width, height) : null,
layoutDirection,
hybrid,
creationParams,
);
final int textureId = _textureCounter++; final int textureId = _textureCounter++;
return Future<int>.sync(() => textureId); return Future<int>.sync(() => textureId);
} }
Future<dynamic> _dispose(MethodCall call) { Future<dynamic> _dispose(MethodCall call) {
final int id = call.arguments as int; int id;
if (call.arguments is int) {
id = call.arguments as int;
} else if (call.arguments is Map && call.arguments['hybrid'] == true) {
id = call.arguments['id'] as int;
}
if (!_views.containsKey(id)) if (!_views.containsKey(id))
throw PlatformException( throw PlatformException(
...@@ -381,19 +468,21 @@ class FakeHtmlPlatformViewsController { ...@@ -381,19 +468,21 @@ class FakeHtmlPlatformViewsController {
@immutable @immutable
class FakeAndroidPlatformView { class FakeAndroidPlatformView {
const FakeAndroidPlatformView(this.id, this.type, this.size, this.layoutDirection, [this.creationParams]); const FakeAndroidPlatformView(this.id, this.type, this.size, this.layoutDirection, this.hybrid, [this.creationParams]);
final int id; final int id;
final String type; final String type;
final Uint8List creationParams; final Uint8List creationParams;
final Size size; final Size size;
final int layoutDirection; final int layoutDirection;
final bool hybrid;
FakeAndroidPlatformView copyWith({Size size, int layoutDirection}) => FakeAndroidPlatformView( FakeAndroidPlatformView copyWith({Size size, int layoutDirection}) => FakeAndroidPlatformView(
id, id,
type, type,
size ?? this.size, size ?? this.size,
layoutDirection ?? this.layoutDirection, layoutDirection ?? this.layoutDirection,
hybrid,
creationParams, creationParams,
); );
...@@ -406,15 +495,16 @@ class FakeAndroidPlatformView { ...@@ -406,15 +495,16 @@ class FakeAndroidPlatformView {
&& other.type == type && other.type == type
&& listEquals<int>(other.creationParams, creationParams) && listEquals<int>(other.creationParams, creationParams)
&& other.size == size && other.size == size
&& other.hybrid == hybrid
&& other.layoutDirection == layoutDirection; && other.layoutDirection == layoutDirection;
} }
@override @override
int get hashCode => hashValues(id, type, hashList(creationParams), size, layoutDirection); int get hashCode => hashValues(id, type, hashList(creationParams), size, layoutDirection, hybrid);
@override @override
String toString() { String toString() {
return 'FakeAndroidPlatformView(id: $id, type: $type, size: $size, layoutDirection: $layoutDirection, creationParams: $creationParams)'; return 'FakeAndroidPlatformView(id: $id, type: $type, size: $size, layoutDirection: $layoutDirection, hybrid: $hybrid, creationParams: $creationParams)';
} }
} }
......
...@@ -31,6 +31,17 @@ void main() { ...@@ -31,6 +31,17 @@ void main() {
}, },
throwsA(isA<PlatformException>()), throwsA(isA<PlatformException>()),
); );
expect(
() {
return PlatformViewsService.initSurfaceAndroidView(
id: 0,
viewType: 'web',
layoutDirection: TextDirection.ltr,
).create();
},
throwsA(isA<PlatformException>()),
);
}); });
test('create Android views', () async { test('create Android views', () async {
...@@ -39,11 +50,13 @@ void main() { ...@@ -39,11 +50,13 @@ void main() {
.setSize(const Size(100.0, 100.0)); .setSize(const Size(100.0, 100.0));
await PlatformViewsService.initAndroidView( id: 1, viewType: 'webview', layoutDirection: TextDirection.rtl) await PlatformViewsService.initAndroidView( id: 1, viewType: 'webview', layoutDirection: TextDirection.rtl)
.setSize(const Size(200.0, 300.0)); .setSize(const Size(200.0, 300.0));
await PlatformViewsService.initSurfaceAndroidView(id: 2, viewType: 'webview', layoutDirection: TextDirection.rtl).create();
expect( expect(
viewsController.views, viewsController.views,
unorderedEquals(<FakeAndroidPlatformView>[ unorderedEquals(<FakeAndroidPlatformView>[
const FakeAndroidPlatformView(0, 'webview', Size(100.0, 100.0), AndroidViewController.kAndroidLayoutDirectionLtr), const FakeAndroidPlatformView(0, 'webview', Size(100.0, 100.0), AndroidViewController.kAndroidLayoutDirectionLtr, null),
const FakeAndroidPlatformView(1, 'webview', Size(200.0, 300.0), AndroidViewController.kAndroidLayoutDirectionRtl), const FakeAndroidPlatformView(1, 'webview', Size(200.0, 300.0), AndroidViewController.kAndroidLayoutDirectionRtl, null),
const FakeAndroidPlatformView(2, 'webview', null, AndroidViewController.kAndroidLayoutDirectionRtl, true),
])); ]));
}); });
...@@ -58,6 +71,16 @@ void main() { ...@@ -58,6 +71,16 @@ void main() {
() => PlatformViewsService.initAndroidView( () => PlatformViewsService.initAndroidView(
id: 0, viewType: 'web', layoutDirection: TextDirection.ltr).setSize(const Size(100.0, 100.0)), id: 0, viewType: 'web', layoutDirection: TextDirection.ltr).setSize(const Size(100.0, 100.0)),
throwsA(isA<PlatformException>())); throwsA(isA<PlatformException>()));
await PlatformViewsService.initSurfaceAndroidView(
id: 1,
viewType: 'webview',
layoutDirection: TextDirection.ltr,
).create();
expect(
() => PlatformViewsService.initSurfaceAndroidView(
id: 1, viewType: 'web', layoutDirection: TextDirection.ltr).create(),
throwsA(isA<PlatformException>()));
}); });
test('dispose Android view', () async { test('dispose Android view', () async {
...@@ -67,12 +90,16 @@ void main() { ...@@ -67,12 +90,16 @@ void main() {
final AndroidViewController viewController = final AndroidViewController viewController =
PlatformViewsService.initAndroidView(id: 1, viewType: 'webview', layoutDirection: TextDirection.ltr); PlatformViewsService.initAndroidView(id: 1, viewType: 'webview', layoutDirection: TextDirection.ltr);
await viewController.setSize(const Size(200.0, 300.0)); await viewController.setSize(const Size(200.0, 300.0));
await viewController.dispose(); await viewController.dispose();
final AndroidViewController surfaceViewController = PlatformViewsService.initSurfaceAndroidView(id: 1, viewType: 'webview', layoutDirection: TextDirection.ltr);
await surfaceViewController.create();
await surfaceViewController.dispose();
expect( expect(
viewsController.views, viewsController.views,
unorderedEquals(<FakeAndroidPlatformView>[ unorderedEquals(<FakeAndroidPlatformView>[
const FakeAndroidPlatformView(0, 'webview', Size(100.0, 100.0), AndroidViewController.kAndroidLayoutDirectionLtr), const FakeAndroidPlatformView(0, 'webview', Size(100.0, 100.0), AndroidViewController.kAndroidLayoutDirectionLtr, null),
])); ]));
}); });
...@@ -115,8 +142,8 @@ void main() { ...@@ -115,8 +142,8 @@ void main() {
expect( expect(
viewsController.views, viewsController.views,
unorderedEquals(<FakeAndroidPlatformView>[ unorderedEquals(<FakeAndroidPlatformView>[
const FakeAndroidPlatformView(0, 'webview', Size(100.0, 100.0), AndroidViewController.kAndroidLayoutDirectionLtr), const FakeAndroidPlatformView(0, 'webview', Size(100.0, 100.0), AndroidViewController.kAndroidLayoutDirectionLtr, null),
const FakeAndroidPlatformView(1, 'webview', Size(500.0, 500.0), AndroidViewController.kAndroidLayoutDirectionLtr), const FakeAndroidPlatformView(1, 'webview', Size(500.0, 500.0), AndroidViewController.kAndroidLayoutDirectionLtr, null),
])); ]));
}); });
...@@ -156,7 +183,7 @@ void main() { ...@@ -156,7 +183,7 @@ void main() {
expect( expect(
viewsController.views, viewsController.views,
unorderedEquals(<FakeAndroidPlatformView>[ unorderedEquals(<FakeAndroidPlatformView>[
const FakeAndroidPlatformView(0, 'webview', Size(100.0, 100.0), AndroidViewController.kAndroidLayoutDirectionLtr), const FakeAndroidPlatformView(0, 'webview', Size(100.0, 100.0), AndroidViewController.kAndroidLayoutDirectionLtr, null),
])); ]));
}); });
...@@ -169,7 +196,7 @@ void main() { ...@@ -169,7 +196,7 @@ void main() {
expect( expect(
viewsController.views, viewsController.views,
unorderedEquals(<FakeAndroidPlatformView>[ unorderedEquals(<FakeAndroidPlatformView>[
const FakeAndroidPlatformView(0, 'webview', Size(100.0, 100.0), AndroidViewController.kAndroidLayoutDirectionRtl), const FakeAndroidPlatformView(0, 'webview', Size(100.0, 100.0), AndroidViewController.kAndroidLayoutDirectionRtl, null),
])); ]));
}); });
}); });
......
...@@ -39,7 +39,7 @@ void main() { ...@@ -39,7 +39,7 @@ void main() {
viewsController.views, viewsController.views,
unorderedEquals(<FakeAndroidPlatformView>[ unorderedEquals(<FakeAndroidPlatformView>[
FakeAndroidPlatformView(currentViewId + 1, 'webview', const Size(200.0, 100.0), FakeAndroidPlatformView(currentViewId + 1, 'webview', const Size(200.0, 100.0),
AndroidViewController.kAndroidLayoutDirectionLtr), AndroidViewController.kAndroidLayoutDirectionLtr, null),
]), ]),
); );
}); });
...@@ -78,7 +78,7 @@ void main() { ...@@ -78,7 +78,7 @@ void main() {
viewsController.views, viewsController.views,
unorderedEquals(<FakeAndroidPlatformView>[ unorderedEquals(<FakeAndroidPlatformView>[
FakeAndroidPlatformView(currentViewId + 1, 'webview', const Size(200.0, 100.0), FakeAndroidPlatformView(currentViewId + 1, 'webview', const Size(200.0, 100.0),
AndroidViewController.kAndroidLayoutDirectionLtr, fakeView.creationParams), AndroidViewController.kAndroidLayoutDirectionLtr, null, fakeView.creationParams),
]), ]),
); );
}); });
...@@ -137,7 +137,7 @@ void main() { ...@@ -137,7 +137,7 @@ void main() {
viewsController.views, viewsController.views,
unorderedEquals(<FakeAndroidPlatformView>[ unorderedEquals(<FakeAndroidPlatformView>[
FakeAndroidPlatformView(currentViewId + 1, 'webview', const Size(200.0, 100.0), FakeAndroidPlatformView(currentViewId + 1, 'webview', const Size(200.0, 100.0),
AndroidViewController.kAndroidLayoutDirectionLtr), AndroidViewController.kAndroidLayoutDirectionLtr, null),
]), ]),
); );
...@@ -148,7 +148,7 @@ void main() { ...@@ -148,7 +148,7 @@ void main() {
viewsController.views, viewsController.views,
unorderedEquals(<FakeAndroidPlatformView>[ unorderedEquals(<FakeAndroidPlatformView>[
FakeAndroidPlatformView(currentViewId + 1, 'webview', const Size(100.0, 50.0), FakeAndroidPlatformView(currentViewId + 1, 'webview', const Size(100.0, 50.0),
AndroidViewController.kAndroidLayoutDirectionLtr), AndroidViewController.kAndroidLayoutDirectionLtr, null),
]), ]),
); );
}); });
...@@ -182,7 +182,7 @@ void main() { ...@@ -182,7 +182,7 @@ void main() {
viewsController.views, viewsController.views,
unorderedEquals(<FakeAndroidPlatformView>[ unorderedEquals(<FakeAndroidPlatformView>[
FakeAndroidPlatformView(currentViewId + 2, 'maps', const Size(200.0, 100.0), FakeAndroidPlatformView(currentViewId + 2, 'maps', const Size(200.0, 100.0),
AndroidViewController.kAndroidLayoutDirectionLtr), AndroidViewController.kAndroidLayoutDirectionLtr, null),
]), ]),
); );
}); });
...@@ -246,7 +246,7 @@ void main() { ...@@ -246,7 +246,7 @@ void main() {
viewsController.views, viewsController.views,
unorderedEquals(<FakeAndroidPlatformView>[ unorderedEquals(<FakeAndroidPlatformView>[
FakeAndroidPlatformView(currentViewId + 1, 'webview', const Size(200.0, 100.0), FakeAndroidPlatformView(currentViewId + 1, 'webview', const Size(200.0, 100.0),
AndroidViewController.kAndroidLayoutDirectionLtr), AndroidViewController.kAndroidLayoutDirectionLtr, null),
]), ]),
); );
}); });
...@@ -471,7 +471,7 @@ void main() { ...@@ -471,7 +471,7 @@ void main() {
viewsController.views, viewsController.views,
unorderedEquals(<FakeAndroidPlatformView>[ unorderedEquals(<FakeAndroidPlatformView>[
FakeAndroidPlatformView(currentViewId + 1, 'maps', const Size(200.0, 100.0), FakeAndroidPlatformView(currentViewId + 1, 'maps', const Size(200.0, 100.0),
AndroidViewController.kAndroidLayoutDirectionRtl), AndroidViewController.kAndroidLayoutDirectionRtl, null),
]), ]),
); );
...@@ -489,7 +489,7 @@ void main() { ...@@ -489,7 +489,7 @@ void main() {
viewsController.views, viewsController.views,
unorderedEquals(<FakeAndroidPlatformView>[ unorderedEquals(<FakeAndroidPlatformView>[
FakeAndroidPlatformView(currentViewId + 1, 'maps', const Size(200.0, 100.0), FakeAndroidPlatformView(currentViewId + 1, 'maps', const Size(200.0, 100.0),
AndroidViewController.kAndroidLayoutDirectionLtr), AndroidViewController.kAndroidLayoutDirectionLtr, null),
]), ]),
); );
}); });
...@@ -515,7 +515,7 @@ void main() { ...@@ -515,7 +515,7 @@ void main() {
viewsController.views, viewsController.views,
unorderedEquals(<FakeAndroidPlatformView>[ unorderedEquals(<FakeAndroidPlatformView>[
FakeAndroidPlatformView(currentViewId + 1, 'maps', const Size(200.0, 100.0), FakeAndroidPlatformView(currentViewId + 1, 'maps', const Size(200.0, 100.0),
AndroidViewController.kAndroidLayoutDirectionRtl), AndroidViewController.kAndroidLayoutDirectionRtl, null),
]), ]),
); );
...@@ -536,7 +536,7 @@ void main() { ...@@ -536,7 +536,7 @@ void main() {
viewsController.views, viewsController.views,
unorderedEquals(<FakeAndroidPlatformView>[ unorderedEquals(<FakeAndroidPlatformView>[
FakeAndroidPlatformView(currentViewId + 1, 'maps', const Size(200.0, 100.0), FakeAndroidPlatformView(currentViewId + 1, 'maps', const Size(200.0, 100.0),
AndroidViewController.kAndroidLayoutDirectionLtr), AndroidViewController.kAndroidLayoutDirectionLtr, null),
]), ]),
); );
}); });
...@@ -1016,6 +1016,23 @@ void main() { ...@@ -1016,6 +1016,23 @@ void main() {
}); });
}); });
group('AndroidViewSurface', () {
FakeAndroidViewController controller;
setUp(() {
controller = FakeAndroidViewController(0);
});
testWidgets('AndroidViewSurface sets pointTransformer of view controller', (WidgetTester tester) async {
final AndroidViewSurface surface = AndroidViewSurface(
controller: controller,
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},);
await tester.pumpWidget(surface);
expect(controller.pointTransformer, isNotNull);
});
});
group('UiKitView', () { group('UiKitView', () {
testWidgets('Create UIView', (WidgetTester tester) async { testWidgets('Create UIView', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId(); final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
......
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