Reland: "Use texture layer when displaying an Android view" (#100990)

This commit is contained in:
Emmanuel Garcia 2022-03-29 19:40:17 -07:00 committed by GitHub
parent f07f6fef9a
commit 032205eaca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 342 additions and 263 deletions

View File

@ -2023,6 +2023,7 @@ targets:
- name: Linux_android hybrid_android_views_integration_test
recipe: devicelab/devicelab_drone
presubmit: false
bringup: true # https://github.com/flutter/flutter/issues/100991
timeout: 60
properties:
tags: >

View File

@ -16,7 +16,8 @@ found in the LICENSE file. -->
android:launchMode="singleTop"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
android:windowSoftInputMode="adjustResize"
android:exported="true">
<meta-data
android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
android:value="true" />
@ -28,9 +29,5 @@ found in the LICENSE file. -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
<!-- Hybrid composition -->
<meta-data
android:name="io.flutter.embedded_views_preview"
android:value="true" />
</application>
</manifest>

View File

@ -17,6 +17,7 @@ class AndroidPlatformView extends StatelessWidget {
const AndroidPlatformView({
Key? key,
this.onPlatformViewCreated,
this.useHybridComposition = false,
required this.viewType,
}) : assert(viewType != null),
super(key: key);
@ -31,6 +32,9 @@ class AndroidPlatformView extends StatelessWidget {
/// May be null.
final PlatformViewCreatedCallback? onPlatformViewCreated;
// Use hybrid composition.
final bool useHybridComposition;
@override
Widget build(BuildContext context) {
return PlatformViewLink(
@ -44,17 +48,27 @@ class AndroidPlatformView extends StatelessWidget {
);
},
onCreatePlatformView: (PlatformViewCreationParams params) {
final AndroidViewController controller =
PlatformViewsService.initSurfaceAndroidView(
print('useHybridComposition=$useHybridComposition');
late AndroidViewController controller;
if (useHybridComposition) {
controller = PlatformViewsService.initExpensiveAndroidView(
id: params.id,
viewType: params.viewType,
layoutDirection: TextDirection.ltr,
)
..addOnPlatformViewCreatedListener(params.onPlatformViewCreated);
);
} else {
controller = PlatformViewsService.initSurfaceAndroidView(
id: params.id,
viewType: params.viewType,
layoutDirection: TextDirection.ltr,
);
}
if (onPlatformViewCreated != null) {
controller.addOnPlatformViewCreatedListener(onPlatformViewCreated!);
}
return controller..create();
return controller
..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
..create();
},
);
}

View File

@ -39,6 +39,7 @@ class NestedViewEventBodyState extends State<NestedViewEventBody> {
int? id;
int nestedViewClickCount = 0;
bool showPlatformView = true;
bool useHybridComposition = false;
@override
Widget build(BuildContext context) {
@ -55,39 +56,63 @@ class NestedViewEventBodyState extends State<NestedViewEventBody> {
key: const ValueKey<String>('PlatformView'),
viewType: 'simple_view',
onPlatformViewCreated: onPlatformViewCreated,
useHybridComposition: useHybridComposition,
) : null,
),
if (_lastTestStatus != _LastTestStatus.pending) _statusWidget(),
if (viewChannel != null) ... <Widget>[
ElevatedButton(
key: const ValueKey<String>('ShowAlertDialog'),
onPressed: onShowAlertDialogPressed,
child: const Text('SHOW ALERT DIALOG'),
),
ElevatedButton(
key: const ValueKey<String>('TogglePlatformView'),
onPressed: onTogglePlatformView,
child: const Text('TOGGLE PLATFORM VIEW'),
Row(
children: <Widget>[
Expanded(
child: ElevatedButton(
key: const ValueKey<String>('ShowAlertDialog'),
onPressed: onShowAlertDialogPressed,
child: const Text('SHOW ALERT DIALOG'),
),
),
Expanded(
child: ElevatedButton(
key: const ValueKey<String>('TogglePlatformView'),
onPressed: onTogglePlatformView,
child: const Text('TOGGLE PLATFORM VIEW'),
),
),
],
),
Row(
children: <Widget>[
ElevatedButton(
key: const ValueKey<String>('AddChildView'),
onPressed: onChildViewPressed,
child: const Text('ADD CHILD VIEW'),
),
ElevatedButton(
key: const ValueKey<String>('TapChildView'),
onPressed: onTapChildViewPressed,
child: const Text('TAP CHILD VIEW'),
),
if (nestedViewClickCount > 0)
Text(
'Click count: $nestedViewClickCount',
key: const ValueKey<String>('NestedViewClickCount'),
Expanded(
child: ElevatedButton(
key: const ValueKey<String>('ToggleHybridComposition'),
child: const Text('TOGGLE HC'),
onPressed: () {
setState(() {
useHybridComposition = !useHybridComposition;
});
},
),
),
Expanded(
child: ElevatedButton(
key: const ValueKey<String>('AddChildView'),
onPressed: onChildViewPressed,
child: const Text('ADD CHILD VIEW'),
),
),
Expanded(
child: ElevatedButton(
key: const ValueKey<String>('TapChildView'),
onPressed: onTapChildViewPressed,
child: const Text('TAP CHILD VIEW'),
),
),
],
),
if (nestedViewClickCount > 0)
Text(
'Click count: $nestedViewClickCount',
key: const ValueKey<String>('NestedViewClickCount'),
),
],
],
),

View File

@ -59,10 +59,58 @@ Future<void> main() async {
}, timeout: Timeout.none);
});
group('Flutter surface switch', () {
group('Flutter surface without hybrid composition', () {
setUpAll(() async {
final SerializableFinder wmListTile = find.byValueKey('NestedViewEventTile');
await driver.tap(wmListTile);
await driver.tap(find.byValueKey('NestedViewEventTile'));
});
tearDownAll(() async {
await driver.waitFor(find.pageBack());
await driver.tap(find.pageBack());
});
test('Uses FlutterSurfaceView when Android view is on the screen', () async {
await driver.waitFor(find.byValueKey('PlatformView'));
expect(
await driver.requestData('hierarchy'),
'|-FlutterView\n'
' |-FlutterSurfaceView\n' // Flutter UI
' |-ViewGroup\n' // Platform View
' |-ViewGroup\n'
);
// Hide platform view.
final SerializableFinder togglePlatformView = find.byValueKey('TogglePlatformView');
await driver.tap(togglePlatformView);
await driver.waitForAbsent(find.byValueKey('PlatformView'));
expect(
await driver.requestData('hierarchy'),
'|-FlutterView\n'
' |-FlutterSurfaceView\n' // Just the Flutter UI
);
// Show platform view again.
await driver.tap(togglePlatformView);
await driver.waitFor(find.byValueKey('PlatformView'));
expect(
await driver.requestData('hierarchy'),
'|-FlutterView\n'
' |-FlutterSurfaceView\n' // Flutter UI
' |-ViewGroup\n' // Platform View
' |-ViewGroup\n'
);
}, timeout: Timeout.none);
});
group('Flutter surface with hybrid composition', () {
setUpAll(() async {
await driver.tap(find.byValueKey('NestedViewEventTile'));
await driver.tap(find.byValueKey('ToggleHybridComposition'));
await driver.tap(find.byValueKey('TogglePlatformView'));
await driver.tap(find.byValueKey('TogglePlatformView'));
});
tearDownAll(() async {

View File

@ -52,7 +52,7 @@ Set<Type> _factoriesTypeSet<T>(Set<Factory<T>> factories) {
/// A render object for an Android view.
///
/// Requires Android API level 20 or greater.
/// Requires Android API level 23 or greater.
///
/// [RenderAndroidView] is responsible for sizing, displaying and passing touch events to an
/// Android [View](https://developer.android.com/reference/android/view/View).
@ -74,7 +74,7 @@ Set<Type> _factoriesTypeSet<T>(Set<Factory<T>> factories) {
///
/// * [AndroidView] which is a widget that is used to show an Android view.
/// * [PlatformViewsService] which is a service for controlling platform views.
class RenderAndroidView extends RenderBox with _PlatformViewGestureMixin {
class RenderAndroidView extends PlatformViewRenderBox {
/// Creates a render object for an Android view.
RenderAndroidView({
required AndroidViewController viewController,
@ -86,7 +86,8 @@ class RenderAndroidView extends RenderBox with _PlatformViewGestureMixin {
assert(gestureRecognizers != null),
assert(clipBehavior != null),
_viewController = viewController,
_clipBehavior = clipBehavior {
_clipBehavior = clipBehavior,
super(controller: viewController, hitTestBehavior: hitTestBehavior, gestureRecognizers: gestureRecognizers) {
_viewController.pointTransformer = (Offset offset) => globalToLocal(offset);
updateGestureRecognizers(gestureRecognizers);
_viewController.addOnPlatformViewCreatedListener(_onPlatformViewCreated);
@ -101,18 +102,22 @@ class RenderAndroidView extends RenderBox with _PlatformViewGestureMixin {
bool _isDisposed = false;
/// The Android view controller for the Android view associated with this render object.
AndroidViewController get viewController => _viewController;
@override
AndroidViewController get controller => _viewController;
AndroidViewController _viewController;
/// Sets a new Android view controller.
///
/// `viewController` must not be null.
set viewController(AndroidViewController viewController) {
@override
set controller(AndroidViewController controller) {
assert(_viewController != null);
assert(viewController != null);
if (_viewController == viewController)
assert(controller != null);
if (_viewController == controller)
return;
_viewController.removeOnPlatformViewCreatedListener(_onPlatformViewCreated);
_viewController = viewController;
super.controller = controller;
_viewController = controller;
_viewController.pointTransformer = (Offset offset) => globalToLocal(offset);
_sizePlatformView();
if (_viewController.isCreated) {
markNeedsSemanticsUpdate();
@ -138,26 +143,6 @@ class RenderAndroidView extends RenderBox with _PlatformViewGestureMixin {
markNeedsSemanticsUpdate();
}
/// {@template flutter.rendering.RenderAndroidView.updateGestureRecognizers}
/// Updates which gestures should be forwarded to the platform view.
///
/// Gesture recognizers created by factories in this set participate in the gesture arena for each
/// pointer that was put down on the render box. If any of the recognizers on this list wins the
/// gesture arena, the entire pointer event sequence starting from the pointer down event
/// will be dispatched to the Android view.
///
/// The `gestureRecognizers` property must not contain more than one factory with the same [Factory.type].
///
/// Setting a new set of gesture recognizer factories with the same [Factory.type]s as the current
/// set has no effect, because the factories' constructors would have already been called with the previous set.
/// {@endtemplate}
///
/// 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, _viewController.dispatchPointerEvent);
}
@override
bool get sizedByParent => true;
@ -182,9 +167,8 @@ class RenderAndroidView extends RenderBox with _PlatformViewGestureMixin {
// Android virtual displays cannot have a zero size.
// Trying to size it to 0 crashes the app, which was happening when starting the app
// with a locked screen (see: https://github.com/flutter/flutter/issues/20456).
if (_state == _PlatformViewState.resizing || size.isEmpty) {
if (_state == _PlatformViewState.resizing || size.isEmpty)
return;
}
_state = _PlatformViewState.resizing;
markNeedsPaint();
@ -212,7 +196,8 @@ class RenderAndroidView extends RenderBox with _PlatformViewGestureMixin {
void _setOffset() {
SchedulerBinding.instance.addPostFrameCallback((_) async {
if (!_isDisposed) {
await _viewController.setOffset(localToGlobal(Offset.zero));
if (attached)
await _viewController.setOffset(localToGlobal(Offset.zero));
// Schedule a new post frame callback.
_setOffset();
}
@ -221,7 +206,7 @@ class RenderAndroidView extends RenderBox with _PlatformViewGestureMixin {
@override
void paint(PaintingContext context, Offset offset) {
if (_viewController.textureId == null)
if (_viewController.textureId == null || _currentTextureSize == null)
return;
// As resizing the Android view happens asynchronously we don't know exactly when is a
@ -264,14 +249,15 @@ class RenderAndroidView extends RenderBox with _PlatformViewGestureMixin {
context.addLayer(TextureLayer(
rect: offset & _currentTextureSize!,
textureId: viewController.textureId!,
textureId: _viewController.textureId!,
));
}
@override
void describeSemanticsConfiguration (SemanticsConfiguration config) {
super.describeSemanticsConfiguration(config);
void describeSemanticsConfiguration(SemanticsConfiguration config) {
// Don't call the super implementation since `platformViewId` should
// be set only when the platform view is created, but the concept of
// a "created" platform view belongs to this subclass.
config.isSemanticBoundary = true;
if (_viewController.isCreated) {
@ -339,7 +325,7 @@ class RenderUiKitView extends RenderBox {
// any newly arriving events there's nothing we need to invalidate.
PlatformViewHitTestBehavior hitTestBehavior;
/// {@macro flutter.rendering.RenderAndroidView.updateGestureRecognizers}
/// {@macro flutter.rendering.PlatformViewRenderBox.updateGestureRecognizers}
void updateGestureRecognizers(Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers) {
assert(gestureRecognizers != null);
assert(
@ -653,11 +639,11 @@ class PlatformViewRenderBox extends RenderBox with _PlatformViewGestureMixin {
PlatformViewController get controller => _controller;
PlatformViewController _controller;
/// This value must not be null, and setting it to a new value will result in a repaint.
set controller(PlatformViewController controller) {
set controller(covariant PlatformViewController controller) {
assert(controller != null);
assert(controller.viewId != null && controller.viewId > -1);
if ( _controller == controller) {
if (_controller == controller) {
return;
}
final bool needsSemanticsUpdate = _controller.viewId != controller.viewId;
@ -668,7 +654,19 @@ class PlatformViewRenderBox extends RenderBox with _PlatformViewGestureMixin {
}
}
/// {@macro flutter.rendering.RenderAndroidView.updateGestureRecognizers}
/// {@template flutter.rendering.PlatformViewRenderBox.updateGestureRecognizers}
/// Updates which gestures should be forwarded to the platform view.
///
/// Gesture recognizers created by factories in this set participate in the gesture arena for each
/// pointer that was put down on the render box. If any of the recognizers on this list wins the
/// gesture arena, the entire pointer event sequence starting from the pointer down event
/// will be dispatched to the Android view.
///
/// The `gestureRecognizers` property must not contain more than one factory with the same [Factory.type].
///
/// Setting a new set of gesture recognizer factories with the same [Factory.type]s as the current
/// set has no effect, because the factories' constructors would have already been called with the previous set.
/// {@endtemplate}
///
/// Any active gesture arena the `PlatformView` participates in is rejected when the
/// set of gesture recognizers is changed.
@ -700,7 +698,7 @@ class PlatformViewRenderBox extends RenderBox with _PlatformViewGestureMixin {
}
@override
void describeSemanticsConfiguration (SemanticsConfiguration config) {
void describeSemanticsConfiguration(SemanticsConfiguration config) {
super.describeSemanticsConfiguration(config);
assert(_controller.viewId != null);
config.isSemanticBoundary = true;

View File

@ -76,10 +76,8 @@ class PlatformViewsService {
/// The callbacks are invoked when the platform view asks to be focused.
final Map<int, VoidCallback> _focusCallbacks = <int, VoidCallback>{};
/// Creates a [TextureAndroidViewController] for a new Android view.
///
/// The view is created after calling [TextureAndroidViewController.setSize].
/// {@template flutter.services.PlatformViewsService.initAndroidView}
/// Creates a controller for a new Android view.
///
/// `id` is an unused unique identifier generated with [platformViewsRegistry].
///
@ -103,7 +101,8 @@ class PlatformViewsService {
///
/// The `id, `viewType, and `layoutDirection` parameters must not be null.
/// If `creationParams` is non null then `creationParamsCodec` must not be null.
static TextureAndroidViewController initAndroidView({
/// {@endtemplate}
static AndroidViewController initAndroidView({
required int id,
required String viewType,
required TextDirection layoutDirection,
@ -128,32 +127,11 @@ class PlatformViewsService {
return controller;
}
/// Creates a [SurfaceAndroidViewController] for a new Android view.
/// {@macro flutter.services.PlatformViewsService.initAndroidView}
///
/// 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.
/// Alias for [initAndroidView].
/// This factory is provided for backward compatibility purposes.
/// In the future, this method will be deprecated.
static SurfaceAndroidViewController initSurfaceAndroidView({
required int id,
required String viewType,
@ -174,28 +152,43 @@ class PlatformViewsService {
creationParams: creationParams,
creationParamsCodec: creationParamsCodec,
);
_instance._focusCallbacks[id] = onFocus ?? () {};
return controller;
}
/// {@macro flutter.services.PlatformViewsService.initAndroidView}
///
/// When this factory is used, the Android view and Flutter widgets are composed at the
/// Android view hierarchy level.
/// This is only useful if the view is a Android SurfaceView. However, using this method
/// has a performance cost on devices that run below 10, or underpowered devices.
/// In most situations, you should use [initAndroidView].
static ExpensiveAndroidViewController initExpensiveAndroidView({
required int id,
required String viewType,
required TextDirection layoutDirection,
dynamic creationParams,
MessageCodec<dynamic>? creationParamsCodec,
VoidCallback? onFocus,
}) {
final ExpensiveAndroidViewController controller = ExpensiveAndroidViewController._(
viewId: id,
viewType: viewType,
layoutDirection: layoutDirection,
creationParams: creationParams,
creationParamsCodec: creationParamsCodec,
);
_instance._focusCallbacks[id] = onFocus ?? () {};
return controller;
}
/// Whether the render surface of the Android `FlutterView` should be converted to a `FlutterImageView`.
///
/// When adding platform views using
/// [Hybrid Composition](https://flutter.dev/docs/development/platform-integration/platform-views),
/// the engine converts the render surface to a `FlutterImageView` to improve
/// animation synchronization between Flutter widgets and the Android platform
/// views. On Android versions < 10, this can have some performance issues.
/// This flag allows disabling this conversion.
///
/// Defaults to true.
static Future<void> synchronizeToNativeViewHierarchy(bool yes) {
assert(defaultTargetPlatform == TargetPlatform.android);
return SystemChannels.platform_views.invokeMethod<void>(
'synchronizeToNativeViewHierarchy',
yes,
);
}
@Deprecated(
'No longer necessary to improve performance. '
'This feature was deprecated after v2.11.0-0.1.pre.',
)
static Future<void> synchronizeToNativeViewHierarchy(bool yes) async {}
// TODO(amirh): reference the iOS plugin API for registering a UIView factory once it lands.
/// This is work in progress, not yet ready to be used, and requires a custom engine build. Creates a controller for a new iOS UIView.
@ -665,7 +658,7 @@ class _AndroidMotionEventConverter {
event is! PointerDownEvent && event is! PointerUpEvent;
}
/// Controls an Android view.
/// Controls an Android view that is composed using a GL texture.
///
/// Typically created with [PlatformViewsService.initAndroidView].
// TODO(bparrishMines): Remove abstract methods that are not required by all subclasses.
@ -676,7 +669,6 @@ abstract class AndroidViewController extends PlatformViewController {
required TextDirection layoutDirection,
dynamic creationParams,
MessageCodec<dynamic>? creationParamsCodec,
bool waitingForSize = false,
}) : assert(viewId != null),
assert(viewType != null),
assert(layoutDirection != null),
@ -684,10 +676,7 @@ abstract class AndroidViewController extends PlatformViewController {
_viewType = viewType,
_layoutDirection = layoutDirection,
_creationParams = creationParams,
_creationParamsCodec = creationParamsCodec,
_state = waitingForSize
? _AndroidViewState.waitingForSize
: _AndroidViewState.creating;
_creationParamsCodec = creationParamsCodec;
/// Action code for when a primary pointer touched the screen.
///
@ -737,7 +726,7 @@ abstract class AndroidViewController extends PlatformViewController {
TextDirection _layoutDirection;
_AndroidViewState _state;
_AndroidViewState _state = _AndroidViewState.waitingForSize;
final dynamic _creationParams;
@ -848,10 +837,16 @@ abstract class AndroidViewController extends PlatformViewController {
/// Removes a callback added with [addOnPlatformViewCreatedListener].
void removeOnPlatformViewCreatedListener(PlatformViewCreatedCallback listener) {
assert(listener != null);
assert(_state != _AndroidViewState.disposed);
_platformViewCreatedCallbacks.remove(listener);
}
/// The created callbacks that are invoked after the platform view has been
/// created.
@visibleForTesting
List<PlatformViewCreatedCallback> get createdCallbacks => _platformViewCreatedCallbacks;
/// Sets the layout direction for the Android view.
Future<void> setLayoutDirection(TextDirection layoutDirection) async {
assert(
@ -938,11 +933,29 @@ abstract class AndroidViewController extends PlatformViewController {
}
}
/// Controls an Android view by rendering to an [AndroidViewSurface].
///
/// Typically created with [PlatformViewsService.initAndroidView].
class SurfaceAndroidViewController extends AndroidViewController {
SurfaceAndroidViewController._({
/// Controls an Android view that is composed using a GL texture.
/// This controller is created from the [PlatformViewsService.initSurfaceAndroidView] factory,
/// and is defined for backward compatibility.
class SurfaceAndroidViewController extends TextureAndroidViewController{
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,
);
}
/// Controls an Android view that is composed using the Android view hierarchy.
/// This controller is created from the [PlatformViewsService.initExpensiveAndroidView] factory.
class ExpensiveAndroidViewController extends AndroidViewController {
ExpensiveAndroidViewController._({
required int viewId,
required String viewType,
required TextDirection layoutDirection,
@ -1019,7 +1032,6 @@ class TextureAndroidViewController extends AndroidViewController {
layoutDirection: layoutDirection,
creationParams: creationParams,
creationParamsCodec: creationParamsCodec,
waitingForSize: true,
);
/// The texture entry id into which the Android view is rendered.
@ -1032,7 +1044,8 @@ class TextureAndroidViewController extends AndroidViewController {
@override
int? get textureId => _textureId;
late Size _initialSize;
/// The size used to create the platform view.
Size? _initialSize;
/// The current offset of the platform view.
Offset _off = Offset.zero;
@ -1047,7 +1060,7 @@ class TextureAndroidViewController extends AndroidViewController {
if (_state == _AndroidViewState.waitingForSize) {
_initialSize = size;
await create();
return _initialSize;
return size;
}
final Map<Object?, Object?>? meta = await SystemChannels.platform_views.invokeMapMethod<Object?, Object?>(
@ -1093,17 +1106,21 @@ class TextureAndroidViewController extends AndroidViewController {
///
/// Throws an [AssertionError] if view was already disposed.
@override
Future<void> create() => super.create();
Future<void> create() async {
if (_initialSize != null)
return super.create();
}
@override
Future<void> _sendCreateMessage() async {
assert(!_initialSize.isEmpty, 'trying to create $TextureAndroidViewController without setting a valid size.');
assert(_initialSize != null, 'trying to create $TextureAndroidViewController without setting an initial size.');
assert(!_initialSize!.isEmpty, 'trying to create $TextureAndroidViewController without setting a valid size.');
final Map<String, dynamic> args = <String, dynamic>{
'id': viewId,
'viewType': _viewType,
'width': _initialSize.width,
'height': _initialSize.height,
'width': _initialSize!.width,
'height': _initialSize!.height,
'direction': AndroidViewController._getAndroidDirection(_layoutDirection),
};
if (_creationParams != null) {

View File

@ -15,7 +15,7 @@ import 'framework.dart';
/// Embeds an Android view in the Widget hierarchy.
///
/// Requires Android API level 20 or greater.
/// Requires Android API level 23 or greater.
///
/// Embedding Android views is an expensive operation and should be avoided when a Flutter
/// equivalent is possible.
@ -681,7 +681,7 @@ class _AndroidPlatformView extends LeafRenderObjectWidget {
@override
void updateRenderObject(BuildContext context, RenderAndroidView renderObject) {
renderObject.viewController = controller;
renderObject.controller = controller;
renderObject.hitTestBehavior = hitTestBehavior;
renderObject.updateGestureRecognizers(gestureRecognizers);
renderObject.clipBehavior = clipBehavior;
@ -842,15 +842,11 @@ class PlatformViewLink extends StatefulWidget {
class _PlatformViewLinkState extends State<PlatformViewLink> {
int? _id;
PlatformViewController? _controller;
bool _platformViewCreated = false;
Widget? _surface;
FocusNode? _focusNode;
@override
Widget build(BuildContext context) {
if (!_platformViewCreated) {
return const SizedBox.expand();
}
_surface ??= widget._surfaceFactory(context, _controller!);
return Focus(
focusNode: _focusNode,
@ -875,9 +871,6 @@ class _PlatformViewLinkState extends State<PlatformViewLink> {
// The _surface has to be recreated as its controller is disposed.
// Setting _surface to null will trigger its creation in build().
_surface = null;
// We are about to create a new platform view.
_platformViewCreated = false;
_initialize();
}
}
@ -888,16 +881,12 @@ class _PlatformViewLinkState extends State<PlatformViewLink> {
PlatformViewCreationParams._(
id: _id!,
viewType: widget.viewType,
onPlatformViewCreated: _onPlatformViewCreated,
onPlatformViewCreated: (_) {},
onFocusChanged: _handlePlatformFocusChanged,
),
);
}
void _onPlatformViewCreated(int id) {
setState(() { _platformViewCreated = true; });
}
void _handleFrameworkFocusChanged(bool isFocused) {
if (!isFocused) {
_controller?.clearFocus();
@ -1020,18 +1009,18 @@ class PlatformViewSurface extends LeafRenderObjectWidget {
/// 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 compositor integration is done by adding a [TextureLayer] to the layer tree.
///
/// The widget fills all available space, the parent of this object must provide bounded layout
/// constraints.
/// 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.
///
/// When possible, you may want to use [AndroidView] directly, since it requires less boilerplate code
/// than [AndroidViewSurface], and there's no difference in performance, or other trade-off(s).
///
/// See also:
///
/// * [AndroidView] which embeds an Android platform view in the widget hierarchy using a [TextureLayer].
/// * [AndroidView] which embeds an Android platform view in the widget hierarchy.
/// * [UiKitView] which embeds an iOS platform view in the widget hierarchy.
class AndroidViewSurface extends PlatformViewSurface {
/// Construct an `AndroidPlatformViewSurface`.
@ -1052,12 +1041,26 @@ class AndroidViewSurface extends PlatformViewSurface {
@override
RenderObject createRenderObject(BuildContext context) {
final PlatformViewRenderBox renderBox =
super.createRenderObject(context) as PlatformViewRenderBox;
(controller as AndroidViewController).pointTransformer =
final AndroidViewController viewController = controller as AndroidViewController;
// Compose using the Android view hierarchy.
// This is useful when embedding a SurfaceView into a Flutter app.
// SurfaceViews cannot be composed using GL textures.
if (viewController is ExpensiveAndroidViewController) {
final PlatformViewRenderBox renderBox =
super.createRenderObject(context) as PlatformViewRenderBox;
viewController.pointTransformer =
(Offset position) => renderBox.globalToLocal(position);
return renderBox;
}
// Use GL texture based composition.
// App should use GL texture unless they require to embed a SurfaceView.
final RenderAndroidView renderBox = RenderAndroidView(
viewController: viewController,
gestureRecognizers: gestureRecognizers,
hitTestBehavior: hitTestBehavior,
);
viewController.pointTransformer =
(Offset position) => renderBox.globalToLocal(position);
return renderBox;
}
}

View File

@ -151,6 +151,38 @@ void main() {
// Passes if no crashes.
});
test('created callback is reset when controller is changed', () {
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
viewsController.registerViewType('webview');
final AndroidViewController firstController = PlatformViewsService.initAndroidView(
id: 0,
viewType: 'webview',
layoutDirection: TextDirection.rtl,
);
final RenderAndroidView renderBox = RenderAndroidView(
viewController: firstController,
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{},
);
layout(renderBox);
pumpFrame(phase: EnginePhase.flushSemantics);
expect(firstController.createdCallbacks, isNotEmpty);
expect(firstController.createdCallbacks.length, 1);
final AndroidViewController secondController = PlatformViewsService.initAndroidView(
id: 0,
viewType: 'webview',
layoutDirection: TextDirection.rtl,
);
// Reset controller.
renderBox.controller = secondController;
expect(firstController.createdCallbacks, isEmpty);
expect(secondController.createdCallbacks, isNotEmpty);
expect(secondController.createdCallbacks.length, 1);
});
test('render object changed its visual appearance after texture is created', () {
FakeAsync().run((FakeAsync async) {
final AndroidViewController viewController =

View File

@ -84,24 +84,23 @@ class FakeAndroidViewController implements AndroidViewController {
@override
Future<Size> setSize(Size size) {
throw UnimplementedError();
return Future<Size>.value(size);
}
@override
Future<void> setOffset(Offset off) {
throw UnimplementedError();
Future<void> setOffset(Offset off) async {}
@override
int get textureId => 0;
@override
bool get isCreated => created;
@override
void addOnPlatformViewCreatedListener(PlatformViewCreatedCallback listener) {
created = true;
}
@override
int get textureId => throw UnimplementedError();
@override
bool get isCreated => throw UnimplementedError();
@override
void addOnPlatformViewCreatedListener(PlatformViewCreatedCallback listener) =>
throw UnimplementedError();
@override
void removeOnPlatformViewCreatedListener(PlatformViewCreatedCallback listener) {
throw UnimplementedError();
@ -118,9 +117,10 @@ class FakeAndroidViewController implements AndroidViewController {
}
@override
Future<void> create() async {
created = true;
}
Future<void> create() async {}
@override
List<PlatformViewCreatedCallback> get createdCallbacks => <PlatformViewCreatedCallback>[];
}
class FakeAndroidPlatformViewsController {
@ -143,8 +143,6 @@ class FakeAndroidPlatformViewsController {
int? lastClearedFocusViewId;
bool synchronizeToNativeViewHierarchy = true;
Map<int, Offset> offsets = <int, Offset>{};
void registerViewType(String viewType) {
@ -174,8 +172,6 @@ class FakeAndroidPlatformViewsController {
return _clearFocus(call);
case 'offset':
return _offset(call);
case 'synchronizeToNativeViewHierarchy':
return _synchronizeToNativeViewHierarchy(call);
}
return Future<dynamic>.sync(() => null);
}
@ -318,11 +314,6 @@ class FakeAndroidPlatformViewsController {
lastClearedFocusViewId = id;
return Future<dynamic>.sync(() => null);
}
Future<dynamic> _synchronizeToNativeViewHierarchy(MethodCall call) {
synchronizeToNativeViewHierarchy = call.arguments as bool;
return Future<dynamic>.sync(() => null);
}
}
class FakeIosPlatformViewsController {

View File

@ -18,7 +18,7 @@ void main() {
});
test('create Android view of unregistered type', () async {
expect(
expectLater(
() {
return PlatformViewsService.initAndroidView(
id: 0,
@ -29,16 +29,25 @@ void main() {
throwsA(isA<PlatformException>()),
);
expect(
() {
return PlatformViewsService.initSurfaceAndroidView(
id: 0,
viewType: 'web',
layoutDirection: TextDirection.ltr,
).create();
},
throwsA(isA<PlatformException>()),
);
try {
await PlatformViewsService.initSurfaceAndroidView(
id: 0,
viewType: 'web',
layoutDirection: TextDirection.ltr,
).create();
} catch (e) {
expect(false, isTrue, reason: 'did not expected any exception, but instead got `$e`');
}
try {
await PlatformViewsService.initAndroidView(
id: 0,
viewType: 'web',
layoutDirection: TextDirection.ltr,
).create();
} catch (e) {
expect(false, isTrue, reason: 'did not expected any exception, but instead got `$e`');
}
});
test('create Android views', () async {
@ -47,13 +56,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));
// This platform view isn't created until the size is set.
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, null),
const FakeAndroidPlatformView(1, 'webview', Size(200.0, 300.0), AndroidViewController.kAndroidLayoutDirectionRtl, null),
const FakeAndroidPlatformView(2, 'webview', null, AndroidViewController.kAndroidLayoutDirectionRtl, true),
]),
);
});
@ -65,7 +74,7 @@ void main() {
viewType: 'webview',
layoutDirection: TextDirection.ltr,
).setSize(const Size(100.0, 100.0));
expect(
expectLater(
() => PlatformViewsService.initAndroidView(
id: 0,
viewType: 'web',
@ -73,20 +82,6 @@ void main() {
).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 {
@ -240,11 +235,6 @@ void main() {
await viewController.setOffset(const Offset(10, 20));
expect(viewsController.offsets, equals(<int, Offset>{}));
});
test('synchronizeToNativeViewHierarchy', () async {
await PlatformViewsService.synchronizeToNativeViewHierarchy(false);
expect(viewsController.synchronizeToNativeViewHierarchy, false);
});
});
group('iOS', () {

View File

@ -2312,43 +2312,6 @@ void main() {
expect(factoryInvocationCount, 1);
});
testWidgets(
'PlatformViewLink Widget init, should create a SizedBox widget before onPlatformViewCreated and a PlatformViewSurface after',
(WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
late int createdPlatformViewId;
late PlatformViewCreatedCallback onPlatformViewCreatedCallBack;
final PlatformViewLink platformViewLink = PlatformViewLink(
viewType: 'webview',
onCreatePlatformView: (PlatformViewCreationParams params) {
onPlatformViewCreatedCallBack = params.onPlatformViewCreated;
createdPlatformViewId = params.id;
return FakePlatformViewController(params.id);
},
surfaceFactory: (BuildContext context, PlatformViewController controller) {
return PlatformViewSurface(
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
controller: controller,
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
);
},
);
await tester.pumpWidget(platformViewLink);
expect(() => tester.allWidgets.whereType<SizedBox>().first, returnsNormally);
onPlatformViewCreatedCallBack(createdPlatformViewId);
await tester.pump();
expect(() => tester.allWidgets.whereType<PlatformViewSurface>().first, returnsNormally);
expect(createdPlatformViewId, currentViewId + 1);
},
);
testWidgets('PlatformViewLink Widget dispose', (WidgetTester tester) async {
late FakePlatformViewController disposedController;
final PlatformViewLink platformViewLink = PlatformViewLink(