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 {
Widget build(BuildContext context) {
return PlatformViewLink(
viewType: viewType,
onCreatePlatformView: _onCreateAndroidView,
surfaceFactory: (BuildContext context, PlatformViewController controller) {
return PlatformViewSurface(
controller: controller,
surfaceFactory:
(BuildContext context, PlatformViewController controller) {
return AndroidViewSurface(
controller: controller as AndroidViewController,
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
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 {
assert(hitTestBehavior != null),
assert(gestureRecognizers != null),
_viewController = viewController {
_motionEventsDispatcher = _MotionEventsDispatcher(globalToLocal, viewController);
_viewController.pointTransformer = (Offset offset) => globalToLocal(offset);
updateGestureRecognizers(gestureRecognizers);
_viewController.addOnPlatformViewCreatedListener(_onPlatformViewCreated);
this.hitTestBehavior = hitTestBehavior;
......@@ -139,7 +139,7 @@ class RenderAndroidView extends RenderBox with _PlatformViewGestureMixin {
/// Any active gesture arena the Android view participates in is rejected when the
/// set of gesture recognizers is changed.
void updateGestureRecognizers(Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers) {
_updateGestureRecognizersWithCallBack(gestureRecognizers, _motionEventsDispatcher.handlePointerEvent);
_updateGestureRecognizersWithCallBack(gestureRecognizers, _viewController.dispatchPointerEvent);
}
@override
......@@ -151,8 +151,6 @@ class RenderAndroidView extends RenderBox with _PlatformViewGestureMixin {
@override
bool get isRepaintBoundary => true;
_MotionEventsDispatcher _motionEventsDispatcher;
@override
void performResize() {
size = constraints.biggest;
......@@ -225,7 +223,7 @@ class RenderAndroidView extends RenderBox with _PlatformViewGestureMixin {
config.isSemanticBoundary = true;
if (_viewController.isCreated) {
config.platformViewId = _viewController.id;
config.platformViewId = _viewController.viewId;
}
}
}
......@@ -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
// it was give, adds all of them to a gesture arena team with the _PlatformViewGestureRecognizer
......@@ -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.
///
/// [PlatformViewRenderBox] presents a platform view by adding a [PlatformViewLayer] layer,
......
......@@ -78,7 +78,9 @@ class PlatformViewsService {
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].
///
......@@ -102,7 +104,7 @@ class PlatformViewsService {
///
/// The `id, `viewType, and `layoutDirection` parameters 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 String viewType,
@required TextDirection layoutDirection,
......@@ -114,13 +116,66 @@ class PlatformViewsService {
assert(viewType != null);
assert(layoutDirection != null);
assert(creationParams == null || creationParamsCodec != null);
final AndroidViewController controller = AndroidViewController._(
id,
viewType,
creationParams,
creationParamsCodec,
layoutDirection,
final TextureAndroidViewController controller = TextureAndroidViewController._(
viewId: id,
viewType: viewType,
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 ?? () {};
return controller;
}
......@@ -427,25 +482,178 @@ enum _AndroidViewState {
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.
///
/// Typically created with [PlatformViewsService.initAndroidView].
class AndroidViewController {
AndroidViewController._(
this.id,
String viewType,
// TODO(bparrishMines): Remove abstract methods that are not required by all subclasses.
abstract class AndroidViewController extends PlatformViewController {
AndroidViewController._({
@required this.viewId,
@required String viewType,
@required TextDirection layoutDirection,
dynamic creationParams,
MessageCodec<dynamic> creationParamsCodec,
TextDirection layoutDirection,
) : assert(id != null),
bool waitingForSize = false,
}) : assert(viewId != null),
assert(viewType != null),
assert(layoutDirection != null),
assert(creationParams == null || creationParamsCodec != null),
_viewType = viewType,
_layoutDirection = layoutDirection,
_creationParams = creationParams,
_creationParamsCodec = creationParamsCodec,
_layoutDirection = layoutDirection,
_state = _AndroidViewState.waitingForSize;
_state = waitingForSize
? _AndroidViewState.waitingForSize
: _AndroidViewState.creating;
/// Action code for when a primary pointer touched the screen.
///
......@@ -484,18 +692,14 @@ class AndroidViewController {
static const int kAndroidLayoutDirectionRtl = 1;
/// The unique identifier of the Android view controlled by this controller.
final int id;
@override
final int viewId;
final String _viewType;
/// 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.
int get textureId => _textureId;
// Helps convert PointerEvents to AndroidMotionEvents.
final _AndroidMotionEventConverter _motionEventConverter =
_AndroidMotionEventConverter();
TextDirection _layoutDirection;
......@@ -505,36 +709,40 @@ class AndroidViewController {
final MessageCodec<dynamic> _creationParamsCodec;
final List<PlatformViewCreatedCallback> _platformViewCreatedCallbacks = <PlatformViewCreatedCallback>[];
final List<PlatformViewCreatedCallback> _platformViewCreatedCallbacks =
<PlatformViewCreatedCallback>[];
/// 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);
static int _getAndroidDirection(TextDirection direction) {
assert(direction != null);
switch (direction) {
case TextDirection.ltr:
return kAndroidLayoutDirectionLtr;
case TextDirection.rtl:
return kAndroidLayoutDirectionRtl;
}
return null;
}
/// Removes a callback added with [addOnPlatformViewCreatedListener].
void removeOnPlatformViewCreatedListener(PlatformViewCreatedCallback listener) {
assert(_state != _AndroidViewState.disposed);
_platformViewCreatedCallbacks.remove(listener);
/// Creates a masked Android MotionEvent action value for an indexed pointer.
static int pointerAction(int pointerId, int action) {
return ((pointerId << 8) & 0xff00) | (action & 0xff);
}
/// Disposes the Android view.
Future<void> _sendDisposeMessage();
Future<void> _sendCreateMessage();
/// Creates the Android View.
///
/// The [AndroidViewController] object is unusable after calling this.
/// The identifier of the platform view cannot be reused after the view is
/// disposed.
Future<void> dispose() async {
if (_state == _AndroidViewState.creating || _state == _AndroidViewState.created)
await SystemChannels.platform_views.invokeMethod<void>('dispose', id);
_platformViewCreatedCallbacks.clear();
_state = _AndroidViewState.disposed;
PlatformViewsService._instance._focusCallbacks.remove(id);
/// Throws an [AssertionError] if view was already disposed.
Future<void> create() async {
assert(_state != _AndroidViewState.disposed, 'trying to create a disposed Android view');
await _sendCreateMessage();
_state = _AndroidViewState.created;
for (final PlatformViewCreatedCallback callback in _platformViewCreatedCallbacks) {
callback(viewId);
}
}
/// Sizes the Android View.
......@@ -543,25 +751,68 @@ class AndroidViewController {
/// be bigger than zero.
///
/// The first time a size is set triggers the creation of the Android view.
Future<void> setSize(Size size) async {
assert(_state != _AndroidViewState.disposed, 'trying to size a disposed Android View. View id: $id');
Future<void> setSize(Size size);
assert(size != null);
assert(!size.isEmpty);
/// 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;
if (_state == _AndroidViewState.waitingForSize)
return _create(size);
/// The unique identifier of the Android view controlled by this controller.
@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>{
'id': id,
'width': size.width,
'height': size.height,
});
/// Sends an Android [MotionEvent](https://developer.android.com/reference/android/view/MotionEvent)
/// to the view.
///
/// 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.
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)
return;
......@@ -574,56 +825,202 @@ class AndroidViewController {
if (_state == _AndroidViewState.waitingForSize)
return;
await SystemChannels.platform_views.invokeMethod<void>('setDirection', <String, dynamic>{
'id': id,
await SystemChannels.platform_views
.invokeMethod<void>('setDirection', <String, dynamic>{
'id': viewId,
'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.
@override
Future<void> clearFocus() {
if (_state != _AndroidViewState.created) {
return null;
}
return SystemChannels.platform_views.invokeMethod<void>('clearFocus', id);
return SystemChannels.platform_views.invokeMethod<void>('clearFocus', viewId);
}
static int _getAndroidDirection(TextDirection direction) {
assert(direction != null);
switch (direction) {
case TextDirection.ltr:
return kAndroidLayoutDirectionLtr;
case TextDirection.rtl:
return kAndroidLayoutDirectionRtl;
/// Disposes the Android view.
///
/// The [AndroidViewController] object is unusable after calling this.
/// The identifier of the platform view cannot be reused after the view is
/// disposed.
@override
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)
/// to the view.
///
/// 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.
Future<void> sendMotionEvent(AndroidMotionEvent event) async {
await SystemChannels.platform_views.invokeMethod<dynamic>(
'touch',
event._asList(id),
@override
int get textureId {
throw UnimplementedError('Not supported for $SurfaceAndroidViewController.');
}
@override
Future<void> _sendDisposeMessage() {
return SystemChannels.platform_views
.invokeMethod<void>('dispose', <String, dynamic>{
'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.
static int pointerAction(int pointerId, int action) {
return ((pointerId << 8) & 0xff00) | (action & 0xff);
await SystemChannels.platform_views.invokeMethod<void>('resize', <String, dynamic>{
'id': viewId,
'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>{
'id': id,
'id': viewId,
'viewType': _viewType,
'width': size.width,
'height': size.height,
'direction': _getAndroidDirection(_layoutDirection),
'width': _size.width,
'height': _size.height,
'direction': AndroidViewController._getAndroidDirection(_layoutDirection),
};
if (_creationParams != null) {
final ByteData paramsByteData = _creationParamsCodec.encodeMessage(_creationParams);
......@@ -633,11 +1030,12 @@ class AndroidViewController {
paramsByteData.lengthInBytes,
);
}
_textureId = await SystemChannels.platform_views.invokeMethod('create', args);
_state = _AndroidViewState.created;
for (final PlatformViewCreatedCallback callback in _platformViewCreatedCallbacks) {
callback(id);
_textureId = await SystemChannels.platform_views.invokeMethod<int>('create', args);
}
@override
Future<void> _sendDisposeMessage() {
return SystemChannels.platform_views.invokeMethod<void>('dispose', viewId);
}
}
......@@ -726,13 +1124,13 @@ abstract class PlatformViewController {
int get viewId;
/// Dispatches the `event` to the platform view.
void dispatchPointerEvent(PointerEvent event);
Future<void> dispatchPointerEvent(PointerEvent event);
/// Disposes the platform view.
///
/// The [PlatformViewController] is unusable after calling dispose.
void dispose();
Future<void> dispose();
/// Clears the view's focus on the platform side.
void clearFocus();
Future<void> clearFocus();
}
......@@ -398,22 +398,21 @@ class _HtmlElementViewController extends PlatformViewController {
}
@override
void clearFocus() {
Future<void> clearFocus() async {
// Currently this does nothing on Flutter Web.
// TODO(het): Implement this. See https://github.com/flutter/flutter/issues/39496
}
@override
void dispatchPointerEvent(PointerEvent event) {
Future<void> dispatchPointerEvent(PointerEvent event) async {
// We do not dispatch pointer events to HTML views because they may contain
// cross-origin iframes, which only accept user-generated events.
}
@override
void dispose() {
Future<void> dispose() async {
if (_initialized) {
// Asynchronously dispose this view.
SystemChannels.platform_views.invokeMethod<void>('dispose', viewId);
await SystemChannels.platform_views.invokeMethod<void>('dispose', viewId);
}
}
}
......@@ -904,8 +903,9 @@ class _PlatformViewLinkState extends State<PlatformViewLink> {
/// 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]
/// isn't supported on all platforms (e.g on Android platform views are composited using a [TextureLayer]).
/// 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 can be composited by using a [TextureLayer] or
/// [AndroidViewSurface]).
/// 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
......@@ -915,7 +915,7 @@ class _PlatformViewLinkState extends State<PlatformViewLink> {
///
/// 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.
// TODO(amirh): Link to the embedder's system compositor documentation once available.
class PlatformViewSurface extends LeafRenderObjectWidget {
......@@ -996,3 +996,46 @@ class PlatformViewSurface extends LeafRenderObjectWidget {
..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 {
int get viewId => _id;
@override
void dispatchPointerEvent(PointerEvent event) {
Future<void> dispatchPointerEvent(PointerEvent event) async {
dispatchedPointerEvents.add(event);
}
......@@ -42,16 +42,92 @@ class FakePlatformViewController extends PlatformViewController {
}
@override
void dispose() {
Future<void> dispose() async {
disposed = true;
}
@override
void clearFocus() {
Future<void> clearFocus() async {
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 {
FakeAndroidPlatformViewsController() {
SystemChannels.platform_views.setMockMethodCallHandler(_onMethodCall);
......@@ -108,6 +184,7 @@ class FakeAndroidPlatformViewsController {
final double width = args['width'] as double;
final double height = args['height'] as double;
final int layoutDirection = args['direction'] as int;
final bool hybrid = args['hybrid'] as bool;
final Uint8List creationParams = args['params'] as Uint8List;
if (_views.containsKey(id))
......@@ -126,13 +203,23 @@ class FakeAndroidPlatformViewsController {
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++;
return Future<int>.sync(() => textureId);
}
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))
throw PlatformException(
......@@ -381,19 +468,21 @@ class FakeHtmlPlatformViewsController {
@immutable
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 String type;
final Uint8List creationParams;
final Size size;
final int layoutDirection;
final bool hybrid;
FakeAndroidPlatformView copyWith({Size size, int layoutDirection}) => FakeAndroidPlatformView(
id,
type,
size ?? this.size,
layoutDirection ?? this.layoutDirection,
hybrid,
creationParams,
);
......@@ -406,15 +495,16 @@ class FakeAndroidPlatformView {
&& other.type == type
&& listEquals<int>(other.creationParams, creationParams)
&& other.size == size
&& other.hybrid == hybrid
&& other.layoutDirection == layoutDirection;
}
@override
int get hashCode => hashValues(id, type, hashList(creationParams), size, layoutDirection);
int get hashCode => hashValues(id, type, hashList(creationParams), size, layoutDirection, hybrid);
@override
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() {
},
throwsA(isA<PlatformException>()),
);
expect(
() {
return PlatformViewsService.initSurfaceAndroidView(
id: 0,
viewType: 'web',
layoutDirection: TextDirection.ltr,
).create();
},
throwsA(isA<PlatformException>()),
);
});
test('create Android views', () async {
......@@ -39,11 +50,13 @@ void main() {
.setSize(const Size(100.0, 100.0));
await PlatformViewsService.initAndroidView( id: 1, viewType: 'webview', layoutDirection: TextDirection.rtl)
.setSize(const Size(200.0, 300.0));
await PlatformViewsService.initSurfaceAndroidView(id: 2, viewType: 'webview', layoutDirection: TextDirection.rtl).create();
expect(
viewsController.views,
unorderedEquals(<FakeAndroidPlatformView>[
const FakeAndroidPlatformView(0, 'webview', Size(100.0, 100.0), AndroidViewController.kAndroidLayoutDirectionLtr),
const FakeAndroidPlatformView(1, 'webview', Size(200.0, 300.0), AndroidViewController.kAndroidLayoutDirectionRtl),
const FakeAndroidPlatformView(0, 'webview', Size(100.0, 100.0), AndroidViewController.kAndroidLayoutDirectionLtr, null),
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() {
() => PlatformViewsService.initAndroidView(
id: 0, viewType: 'web', layoutDirection: TextDirection.ltr).setSize(const Size(100.0, 100.0)),
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 {
......@@ -67,12 +90,16 @@ void main() {
final AndroidViewController viewController =
PlatformViewsService.initAndroidView(id: 1, viewType: 'webview', layoutDirection: TextDirection.ltr);
await viewController.setSize(const Size(200.0, 300.0));
await viewController.dispose();
final AndroidViewController surfaceViewController = PlatformViewsService.initSurfaceAndroidView(id: 1, viewType: 'webview', layoutDirection: TextDirection.ltr);
await surfaceViewController.create();
await surfaceViewController.dispose();
expect(
viewsController.views,
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() {
expect(
viewsController.views,
unorderedEquals(<FakeAndroidPlatformView>[
const FakeAndroidPlatformView(0, 'webview', Size(100.0, 100.0), AndroidViewController.kAndroidLayoutDirectionLtr),
const FakeAndroidPlatformView(1, 'webview', Size(500.0, 500.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, null),
]));
});
......@@ -156,7 +183,7 @@ void main() {
expect(
viewsController.views,
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() {
expect(
viewsController.views,
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() {
viewsController.views,
unorderedEquals(<FakeAndroidPlatformView>[
FakeAndroidPlatformView(currentViewId + 1, 'webview', const Size(200.0, 100.0),
AndroidViewController.kAndroidLayoutDirectionLtr),
AndroidViewController.kAndroidLayoutDirectionLtr, null),
]),
);
});
......@@ -78,7 +78,7 @@ void main() {
viewsController.views,
unorderedEquals(<FakeAndroidPlatformView>[
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() {
viewsController.views,
unorderedEquals(<FakeAndroidPlatformView>[
FakeAndroidPlatformView(currentViewId + 1, 'webview', const Size(200.0, 100.0),
AndroidViewController.kAndroidLayoutDirectionLtr),
AndroidViewController.kAndroidLayoutDirectionLtr, null),
]),
);
......@@ -148,7 +148,7 @@ void main() {
viewsController.views,
unorderedEquals(<FakeAndroidPlatformView>[
FakeAndroidPlatformView(currentViewId + 1, 'webview', const Size(100.0, 50.0),
AndroidViewController.kAndroidLayoutDirectionLtr),
AndroidViewController.kAndroidLayoutDirectionLtr, null),
]),
);
});
......@@ -182,7 +182,7 @@ void main() {
viewsController.views,
unorderedEquals(<FakeAndroidPlatformView>[
FakeAndroidPlatformView(currentViewId + 2, 'maps', const Size(200.0, 100.0),
AndroidViewController.kAndroidLayoutDirectionLtr),
AndroidViewController.kAndroidLayoutDirectionLtr, null),
]),
);
});
......@@ -246,7 +246,7 @@ void main() {
viewsController.views,
unorderedEquals(<FakeAndroidPlatformView>[
FakeAndroidPlatformView(currentViewId + 1, 'webview', const Size(200.0, 100.0),
AndroidViewController.kAndroidLayoutDirectionLtr),
AndroidViewController.kAndroidLayoutDirectionLtr, null),
]),
);
});
......@@ -471,7 +471,7 @@ void main() {
viewsController.views,
unorderedEquals(<FakeAndroidPlatformView>[
FakeAndroidPlatformView(currentViewId + 1, 'maps', const Size(200.0, 100.0),
AndroidViewController.kAndroidLayoutDirectionRtl),
AndroidViewController.kAndroidLayoutDirectionRtl, null),
]),
);
......@@ -489,7 +489,7 @@ void main() {
viewsController.views,
unorderedEquals(<FakeAndroidPlatformView>[
FakeAndroidPlatformView(currentViewId + 1, 'maps', const Size(200.0, 100.0),
AndroidViewController.kAndroidLayoutDirectionLtr),
AndroidViewController.kAndroidLayoutDirectionLtr, null),
]),
);
});
......@@ -515,7 +515,7 @@ void main() {
viewsController.views,
unorderedEquals(<FakeAndroidPlatformView>[
FakeAndroidPlatformView(currentViewId + 1, 'maps', const Size(200.0, 100.0),
AndroidViewController.kAndroidLayoutDirectionRtl),
AndroidViewController.kAndroidLayoutDirectionRtl, null),
]),
);
......@@ -536,7 +536,7 @@ void main() {
viewsController.views,
unorderedEquals(<FakeAndroidPlatformView>[
FakeAndroidPlatformView(currentViewId + 1, 'maps', const Size(200.0, 100.0),
AndroidViewController.kAndroidLayoutDirectionLtr),
AndroidViewController.kAndroidLayoutDirectionLtr, null),
]),
);
});
......@@ -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', () {
testWidgets('Create UIView', (WidgetTester tester) async {
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