mirror of
https://github.com/flutter/flutter
synced 2024-08-28 04:21:14 +00:00
Allow Hybrid Composition fallback for Android platform views (#109161)
This commit is contained in:
parent
de51709373
commit
bc994c7ffd
|
@ -107,6 +107,10 @@ class PlatformViewsService {
|
|||
/// The `id, `viewType, and `layoutDirection` parameters must not be null.
|
||||
/// If `creationParams` is non null then `creationParamsCodec` must not be null.
|
||||
/// {@endtemplate}
|
||||
///
|
||||
/// This attempts to use the newest and most efficient platform view
|
||||
/// implementation when possible. In cases where that is not supported, it
|
||||
/// falls back to using Virtual Display.
|
||||
static AndroidViewController initAndroidView({
|
||||
required int id,
|
||||
required String viewType,
|
||||
|
@ -134,9 +138,10 @@ class PlatformViewsService {
|
|||
|
||||
/// {@macro flutter.services.PlatformViewsService.initAndroidView}
|
||||
///
|
||||
/// Alias for [initAndroidView].
|
||||
/// This factory is provided for backward compatibility purposes.
|
||||
/// In the future, this method will be deprecated.
|
||||
/// This attempts to use the newest and most efficient platform view
|
||||
/// implementation when possible. In cases where that is not supported, it
|
||||
/// falls back to using Hybrid Composition, which is the mode used by
|
||||
/// [initExpensiveAndroidView].
|
||||
static SurfaceAndroidViewController initSurfaceAndroidView({
|
||||
required int id,
|
||||
required String viewType,
|
||||
|
@ -163,11 +168,12 @@ class PlatformViewsService {
|
|||
|
||||
/// {@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].
|
||||
/// When this factory is used, the Android view and Flutter widgets are
|
||||
/// composed at the Android view hierarchy level.
|
||||
///
|
||||
/// Using this method has a performance cost on devices running Android 9 or
|
||||
/// earlier, or on underpowered devices. In most situations, you should use
|
||||
/// [initAndroidView] or [initSurfaceAndroidView] instead.
|
||||
static ExpensiveAndroidViewController initExpensiveAndroidView({
|
||||
required int id,
|
||||
required String viewType,
|
||||
|
@ -668,6 +674,12 @@ class _AndroidMotionEventConverter {
|
|||
event is! PointerDownEvent && event is! PointerUpEvent;
|
||||
}
|
||||
|
||||
class _CreationParams {
|
||||
const _CreationParams(this.data, this.codec);
|
||||
final dynamic data;
|
||||
final MessageCodec<dynamic> codec;
|
||||
}
|
||||
|
||||
/// Controls an Android view that is composed using a GL texture.
|
||||
///
|
||||
/// Typically created with [PlatformViewsService.initAndroidView].
|
||||
|
@ -685,8 +697,7 @@ abstract class AndroidViewController extends PlatformViewController {
|
|||
assert(creationParams == null || creationParamsCodec != null),
|
||||
_viewType = viewType,
|
||||
_layoutDirection = layoutDirection,
|
||||
_creationParams = creationParams,
|
||||
_creationParamsCodec = creationParamsCodec;
|
||||
_creationParams = creationParams == null ? null : _CreationParams(creationParams, creationParamsCodec!);
|
||||
|
||||
/// Action code for when a primary pointer touched the screen.
|
||||
///
|
||||
|
@ -738,9 +749,7 @@ abstract class AndroidViewController extends PlatformViewController {
|
|||
|
||||
_AndroidViewState _state = _AndroidViewState.waitingForSize;
|
||||
|
||||
final dynamic _creationParams;
|
||||
|
||||
final MessageCodec<dynamic>? _creationParamsCodec;
|
||||
final _CreationParams? _creationParams;
|
||||
|
||||
final List<PlatformViewCreatedCallback> _platformViewCreatedCallbacks =
|
||||
<PlatformViewCreatedCallback>[];
|
||||
|
@ -833,10 +842,17 @@ abstract class AndroidViewController extends PlatformViewController {
|
|||
|
||||
/// 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.
|
||||
/// Returns null if the Android view has not been successfully created, if it has been
|
||||
/// disposed, or if the implementation does not use textures.
|
||||
int? get textureId;
|
||||
|
||||
/// True if the view requires native view composition rather than using a
|
||||
/// texture to render.
|
||||
///
|
||||
/// This value may change during [create], but will not change after that
|
||||
/// call's future has completed.
|
||||
bool get requiresViewComposition => false;
|
||||
|
||||
/// Sends an Android [MotionEvent](https://developer.android.com/reference/android/view/MotionEvent)
|
||||
/// to the view.
|
||||
///
|
||||
|
@ -979,7 +995,7 @@ abstract class AndroidViewController extends PlatformViewController {
|
|||
/// 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{
|
||||
class SurfaceAndroidViewController extends AndroidViewController {
|
||||
SurfaceAndroidViewController._({
|
||||
required super.viewId,
|
||||
required super.viewType,
|
||||
|
@ -987,6 +1003,60 @@ class SurfaceAndroidViewController extends TextureAndroidViewController{
|
|||
super.creationParams,
|
||||
super.creationParamsCodec,
|
||||
}) : super._();
|
||||
|
||||
// By default, assume the implementation will be texture-based.
|
||||
_AndroidViewControllerInternals _internals = _TextureAndroidViewControllerInternals();
|
||||
|
||||
@override
|
||||
bool get _createRequiresSize => true;
|
||||
|
||||
@override
|
||||
Future<bool> _sendCreateMessage({required Size size}) async {
|
||||
assert(!size.isEmpty, 'trying to create $TextureAndroidViewController without setting a valid size.');
|
||||
|
||||
final dynamic response = await _AndroidViewControllerInternals.sendCreateMessage(
|
||||
viewId: viewId,
|
||||
viewType: _viewType,
|
||||
hybrid: false,
|
||||
hybridFallback: true,
|
||||
layoutDirection: _layoutDirection,
|
||||
creationParams: _creationParams,
|
||||
size: size,
|
||||
);
|
||||
if (response is int) {
|
||||
(_internals as _TextureAndroidViewControllerInternals).textureId = response;
|
||||
} else {
|
||||
// A null response indicates fallback to Hybrid Composition, so swap out
|
||||
// the implementation.
|
||||
_internals = _HybridAndroidViewControllerInternals();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
int? get textureId {
|
||||
return _internals.textureId;
|
||||
}
|
||||
|
||||
@override
|
||||
bool get requiresViewComposition {
|
||||
return _internals.requiresViewComposition;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> _sendDisposeMessage() {
|
||||
return _internals.sendDisposeMessage(viewId: viewId);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Size> _sendResizeMessage(Size size) {
|
||||
return _internals.setSize(size, viewId: viewId, viewState: _state);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setOffset(Offset off) {
|
||||
return _internals.setOffset(off, viewId: viewId, viewState: _state);
|
||||
}
|
||||
}
|
||||
|
||||
/// Controls an Android view that is composed using the Android view hierarchy.
|
||||
|
@ -1000,50 +1070,45 @@ class ExpensiveAndroidViewController extends AndroidViewController {
|
|||
super.creationParamsCodec,
|
||||
}) : super._();
|
||||
|
||||
final _AndroidViewControllerInternals _internals = _HybridAndroidViewControllerInternals();
|
||||
|
||||
@override
|
||||
bool get _createRequiresSize => false;
|
||||
|
||||
@override
|
||||
Future<void> _sendCreateMessage({required Size? size}) async {
|
||||
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,
|
||||
);
|
||||
}
|
||||
await SystemChannels.platform_views.invokeMethod<void>('create', args);
|
||||
await _AndroidViewControllerInternals.sendCreateMessage(
|
||||
viewId: viewId,
|
||||
viewType: _viewType,
|
||||
hybrid: true,
|
||||
layoutDirection: _layoutDirection,
|
||||
creationParams: _creationParams,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
int get textureId {
|
||||
throw UnimplementedError('Not supported for $SurfaceAndroidViewController.');
|
||||
int? get textureId {
|
||||
return _internals.textureId;
|
||||
}
|
||||
|
||||
@override
|
||||
bool get requiresViewComposition {
|
||||
return _internals.requiresViewComposition;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> _sendDisposeMessage() {
|
||||
return SystemChannels.platform_views.invokeMethod<void>('dispose', <String, dynamic>{
|
||||
'id': viewId,
|
||||
'hybrid': true,
|
||||
});
|
||||
return _internals.sendDisposeMessage(viewId: viewId);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Size> _sendResizeMessage(Size size) {
|
||||
throw UnimplementedError('Not supported for $SurfaceAndroidViewController.');
|
||||
return _internals.setSize(size, viewId: viewId, viewState: _state);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setOffset(Offset off) {
|
||||
throw UnimplementedError('Not supported for $SurfaceAndroidViewController.');
|
||||
return _internals.setOffset(off, viewId: viewId, viewState: _state);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1062,23 +1127,132 @@ class TextureAndroidViewController extends AndroidViewController {
|
|||
super.creationParamsCodec,
|
||||
}) : super._();
|
||||
|
||||
/// The texture entry id into which the Android view is rendered.
|
||||
int? _textureId;
|
||||
final _TextureAndroidViewControllerInternals _internals = _TextureAndroidViewControllerInternals();
|
||||
|
||||
/// 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;
|
||||
bool get _createRequiresSize => true;
|
||||
|
||||
@override
|
||||
Future<void> _sendCreateMessage({required Size size}) async {
|
||||
assert(!size.isEmpty, 'trying to create $TextureAndroidViewController without setting a valid size.');
|
||||
|
||||
_internals.textureId = await _AndroidViewControllerInternals.sendCreateMessage(
|
||||
viewId: viewId,
|
||||
viewType: _viewType,
|
||||
hybrid: false,
|
||||
layoutDirection: _layoutDirection,
|
||||
creationParams: _creationParams,
|
||||
size: size,
|
||||
) as int;
|
||||
}
|
||||
|
||||
@override
|
||||
int? get textureId {
|
||||
return _internals.textureId;
|
||||
}
|
||||
|
||||
@override
|
||||
bool get requiresViewComposition {
|
||||
return _internals.requiresViewComposition;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> _sendDisposeMessage() {
|
||||
return _internals.sendDisposeMessage(viewId: viewId);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Size> _sendResizeMessage(Size size) {
|
||||
return _internals.setSize(size, viewId: viewId, viewState: _state);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setOffset(Offset off) {
|
||||
return _internals.setOffset(off, viewId: viewId, viewState: _state);
|
||||
}
|
||||
}
|
||||
|
||||
// The base class for an implementation of AndroidViewController.
|
||||
//
|
||||
// Subclasses should correspond to different rendering modes for platform
|
||||
// views, and match different mode logic on the engine side.
|
||||
abstract class _AndroidViewControllerInternals {
|
||||
// Sends a create message with the given parameters, and returns the result
|
||||
// if any.
|
||||
//
|
||||
// This uses a dynamic return because depending on the mode that is selected
|
||||
// on the native side, the return type is different. Callers should cast
|
||||
// depending on the possible return types for their arguments.
|
||||
static Future<dynamic> sendCreateMessage({
|
||||
required int viewId,
|
||||
required String viewType,
|
||||
required TextDirection layoutDirection,
|
||||
required bool hybrid,
|
||||
bool hybridFallback = false,
|
||||
_CreationParams? creationParams,
|
||||
Size? size}) {
|
||||
final Map<String, dynamic> args = <String, dynamic>{
|
||||
'id': viewId,
|
||||
'viewType': viewType,
|
||||
'direction': AndroidViewController._getAndroidDirection(layoutDirection),
|
||||
if (hybrid == true) 'hybrid': hybrid,
|
||||
if (size != null) 'width': size.width,
|
||||
if (size != null) 'height': size.height,
|
||||
if (hybridFallback == true) 'hybridFallback': hybridFallback,
|
||||
};
|
||||
if (creationParams != null) {
|
||||
final ByteData paramsByteData = creationParams.codec.encodeMessage(creationParams.data)!;
|
||||
args['params'] = Uint8List.view(
|
||||
paramsByteData.buffer,
|
||||
0,
|
||||
paramsByteData.lengthInBytes,
|
||||
);
|
||||
}
|
||||
return SystemChannels.platform_views.invokeMethod<dynamic>('create', args);
|
||||
}
|
||||
|
||||
int? get textureId;
|
||||
|
||||
bool get requiresViewComposition;
|
||||
|
||||
Future<Size> setSize(
|
||||
Size size, {
|
||||
required int viewId,
|
||||
required _AndroidViewState viewState,
|
||||
});
|
||||
|
||||
Future<void> setOffset(
|
||||
Offset offset, {
|
||||
required int viewId,
|
||||
required _AndroidViewState viewState,
|
||||
});
|
||||
|
||||
Future<void> sendDisposeMessage({required int viewId});
|
||||
}
|
||||
|
||||
// An AndroidViewController implementation for views whose contents are
|
||||
// displayed via a texture rather than directly in a native view.
|
||||
//
|
||||
// This is used for both Virtual Display and Texture Layer Hybrid Composition.
|
||||
class _TextureAndroidViewControllerInternals extends _AndroidViewControllerInternals {
|
||||
_TextureAndroidViewControllerInternals();
|
||||
|
||||
/// The current offset of the platform view.
|
||||
Offset _off = Offset.zero;
|
||||
Offset _offset = Offset.zero;
|
||||
|
||||
@override
|
||||
Future<Size> _sendResizeMessage(Size size) async {
|
||||
assert(_state != _AndroidViewState.waitingForSize, 'Android view must have an initial size. View id: $viewId');
|
||||
assert(size != null);
|
||||
int? textureId;
|
||||
|
||||
@override
|
||||
bool get requiresViewComposition => false;
|
||||
|
||||
@override
|
||||
Future<Size> setSize(
|
||||
Size size, {
|
||||
required int viewId,
|
||||
required _AndroidViewState viewState,
|
||||
}) async {
|
||||
assert(viewState != _AndroidViewState.waitingForSize, 'Android view must have an initial size. View id: $viewId');
|
||||
assert(!size.isEmpty);
|
||||
|
||||
final Map<Object?, Object?>? meta = await SystemChannels.platform_views.invokeMapMethod<Object?, Object?>(
|
||||
|
@ -1096,58 +1270,36 @@ class TextureAndroidViewController extends AndroidViewController {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<void> setOffset(Offset off) async {
|
||||
if (off == _off) {
|
||||
Future<void> setOffset(
|
||||
Offset offset, {
|
||||
required int viewId,
|
||||
required _AndroidViewState viewState,
|
||||
}) async {
|
||||
if (offset == _offset) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't set the offset unless the Android view has been created.
|
||||
// The implementation of this method channel throws if the Android view for this viewId
|
||||
// isn't addressable.
|
||||
if (_state != _AndroidViewState.created) {
|
||||
if (viewState != _AndroidViewState.created) {
|
||||
return;
|
||||
}
|
||||
|
||||
_off = off;
|
||||
_offset = offset;
|
||||
|
||||
await SystemChannels.platform_views.invokeMethod<void>(
|
||||
'offset',
|
||||
<String, dynamic>{
|
||||
'id': viewId,
|
||||
'top': off.dy,
|
||||
'left': off.dx,
|
||||
'top': offset.dy,
|
||||
'left': offset.dx,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool get _createRequiresSize => true;
|
||||
|
||||
@override
|
||||
// Size is non-nullable due to _createRequiresSize returning true.
|
||||
Future<void> _sendCreateMessage({required Size size}) async {
|
||||
assert(!size.isEmpty, 'trying to create $TextureAndroidViewController without setting a valid size.');
|
||||
|
||||
final Map<String, dynamic> args = <String, dynamic>{
|
||||
'id': viewId,
|
||||
'viewType': _viewType,
|
||||
'width': size.width,
|
||||
'height': size.height,
|
||||
'direction': AndroidViewController._getAndroidDirection(_layoutDirection),
|
||||
};
|
||||
if (_creationParams != null) {
|
||||
final ByteData paramsByteData = _creationParamsCodec!.encodeMessage(_creationParams)!;
|
||||
args['params'] = Uint8List.view(
|
||||
paramsByteData.buffer,
|
||||
0,
|
||||
paramsByteData.lengthInBytes,
|
||||
);
|
||||
}
|
||||
_textureId = await SystemChannels.platform_views.invokeMethod<int>('create', args);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> _sendDisposeMessage() {
|
||||
Future<void> sendDisposeMessage({required int viewId}) {
|
||||
return SystemChannels
|
||||
.platform_views.invokeMethod<void>('dispose', <String, dynamic>{
|
||||
'id': viewId,
|
||||
|
@ -1156,6 +1308,46 @@ class TextureAndroidViewController extends AndroidViewController {
|
|||
}
|
||||
}
|
||||
|
||||
// An AndroidViewController implementation for views whose contents are
|
||||
// displayed directly in a native view.
|
||||
//
|
||||
// This is used for Hybrid Composition.
|
||||
class _HybridAndroidViewControllerInternals extends _AndroidViewControllerInternals {
|
||||
@override
|
||||
int get textureId {
|
||||
throw UnimplementedError('Not supported for hybrid composition.');
|
||||
}
|
||||
|
||||
@override
|
||||
bool get requiresViewComposition => true;
|
||||
|
||||
@override
|
||||
Future<Size> setSize(
|
||||
Size size, {
|
||||
required int viewId,
|
||||
required _AndroidViewState viewState,
|
||||
}) {
|
||||
throw UnimplementedError('Not supported for hybrid composition.');
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setOffset(
|
||||
Offset offset, {
|
||||
required int viewId,
|
||||
required _AndroidViewState viewState,
|
||||
}) {
|
||||
throw UnimplementedError('Not supported for hybrid composition.');
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> sendDisposeMessage({required int viewId}) {
|
||||
return SystemChannels.platform_views.invokeMethod<void>('dispose', <String, dynamic>{
|
||||
'id': viewId,
|
||||
'hybrid': true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Controls an iOS UIView.
|
||||
///
|
||||
/// Typically created with [PlatformViewsService.initUiKitView].
|
||||
|
|
|
@ -1070,10 +1070,79 @@ class PlatformViewSurface extends LeafRenderObjectWidget {
|
|||
///
|
||||
/// * [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 {
|
||||
class AndroidViewSurface extends StatefulWidget {
|
||||
/// Construct an `AndroidPlatformViewSurface`.
|
||||
const AndroidViewSurface({
|
||||
super.key,
|
||||
required this.controller,
|
||||
required this.hitTestBehavior,
|
||||
required this.gestureRecognizers,
|
||||
}) : assert(controller != null),
|
||||
assert(hitTestBehavior != null),
|
||||
assert(gestureRecognizers != null);
|
||||
|
||||
/// The controller for the platform view integrated by this [AndroidViewSurface].
|
||||
///
|
||||
/// See [PlatformViewSurface.controller] for details.
|
||||
final AndroidViewController controller;
|
||||
|
||||
/// Which gestures should be forwarded to the PlatformView.
|
||||
///
|
||||
/// See [PlatformViewSurface.gestureRecognizers] for details.
|
||||
final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers;
|
||||
|
||||
/// {@macro flutter.widgets.AndroidView.hitTestBehavior}
|
||||
final PlatformViewHitTestBehavior hitTestBehavior;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return _AndroidViewSurfaceState();
|
||||
}
|
||||
}
|
||||
|
||||
class _AndroidViewSurfaceState extends State<AndroidViewSurface> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (!widget.controller.isCreated) {
|
||||
// Schedule a rebuild once creation is complete and the final dislay
|
||||
// type is known.
|
||||
widget.controller.addOnPlatformViewCreatedListener(_onPlatformViewCreated);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.controller.removeOnPlatformViewCreatedListener(_onPlatformViewCreated);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.controller.requiresViewComposition) {
|
||||
return _PlatformLayerBasedAndroidViewSurface(
|
||||
controller: widget.controller,
|
||||
hitTestBehavior: widget.hitTestBehavior,
|
||||
gestureRecognizers: widget.gestureRecognizers,
|
||||
);
|
||||
} else {
|
||||
return _TextureBasedAndroidViewSurface(
|
||||
controller: widget.controller,
|
||||
hitTestBehavior: widget.hitTestBehavior,
|
||||
gestureRecognizers: widget.gestureRecognizers,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _onPlatformViewCreated(int _) {
|
||||
// Trigger a re-build based on the current controller state.
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
// Displays an Android platform view via GL texture.
|
||||
class _TextureBasedAndroidViewSurface extends PlatformViewSurface {
|
||||
const _TextureBasedAndroidViewSurface({
|
||||
required AndroidViewController super.controller,
|
||||
required super.hitTestBehavior,
|
||||
required super.gestureRecognizers,
|
||||
|
@ -1084,16 +1153,6 @@ class AndroidViewSurface extends PlatformViewSurface {
|
|||
@override
|
||||
RenderObject createRenderObject(BuildContext context) {
|
||||
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(
|
||||
|
@ -1107,6 +1166,26 @@ class AndroidViewSurface extends PlatformViewSurface {
|
|||
}
|
||||
}
|
||||
|
||||
class _PlatformLayerBasedAndroidViewSurface extends PlatformViewSurface {
|
||||
const _PlatformLayerBasedAndroidViewSurface({
|
||||
required AndroidViewController super.controller,
|
||||
required super.hitTestBehavior,
|
||||
required super.gestureRecognizers,
|
||||
}) : assert(controller != null),
|
||||
assert(hitTestBehavior != null),
|
||||
assert(gestureRecognizers != null);
|
||||
|
||||
@override
|
||||
RenderObject createRenderObject(BuildContext context) {
|
||||
final AndroidViewController viewController = controller as AndroidViewController;
|
||||
final PlatformViewRenderBox renderBox =
|
||||
super.createRenderObject(context) as PlatformViewRenderBox;
|
||||
viewController.pointTransformer =
|
||||
(Offset position) => renderBox.globalToLocal(position);
|
||||
return renderBox;
|
||||
}
|
||||
}
|
||||
|
||||
/// A callback used to notify the size of the platform view placeholder.
|
||||
/// This size is the initial size of the platform view.
|
||||
typedef _OnLayoutCallback = void Function(Size size);
|
||||
|
|
|
@ -45,7 +45,11 @@ class FakePlatformViewController extends PlatformViewController {
|
|||
}
|
||||
|
||||
class FakeAndroidViewController implements AndroidViewController {
|
||||
FakeAndroidViewController(this.viewId, {this.requiresSize = false});
|
||||
FakeAndroidViewController(
|
||||
this.viewId, {
|
||||
this.requiresSize = false,
|
||||
this.requiresViewComposition = false,
|
||||
});
|
||||
|
||||
bool disposed = false;
|
||||
bool focusCleared = false;
|
||||
|
@ -56,6 +60,8 @@ class FakeAndroidViewController implements AndroidViewController {
|
|||
|
||||
bool _createCalledSuccessfully = false;
|
||||
|
||||
final List<PlatformViewCreatedCallback> _createdCallbacks = <PlatformViewCreatedCallback>[];
|
||||
|
||||
/// Events that are dispatched.
|
||||
List<PointerEvent> dispatchedPointerEvents = <PointerEvent>[];
|
||||
|
||||
|
@ -106,10 +112,13 @@ class FakeAndroidViewController implements AndroidViewController {
|
|||
@override
|
||||
void addOnPlatformViewCreatedListener(PlatformViewCreatedCallback listener) {
|
||||
created = true;
|
||||
createdCallbacks.add(listener);
|
||||
}
|
||||
|
||||
@override
|
||||
void removeOnPlatformViewCreatedListener(PlatformViewCreatedCallback listener) {}
|
||||
void removeOnPlatformViewCreatedListener(PlatformViewCreatedCallback listener) {
|
||||
createdCallbacks.remove(listener);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> sendMotionEvent(AndroidMotionEvent event) {
|
||||
|
@ -128,7 +137,10 @@ class FakeAndroidViewController implements AndroidViewController {
|
|||
}
|
||||
|
||||
@override
|
||||
List<PlatformViewCreatedCallback> get createdCallbacks => <PlatformViewCreatedCallback>[];
|
||||
List<PlatformViewCreatedCallback> get createdCallbacks => _createdCallbacks;
|
||||
|
||||
@override
|
||||
bool requiresViewComposition;
|
||||
}
|
||||
|
||||
class FakeAndroidPlatformViewsController {
|
||||
|
@ -153,6 +165,11 @@ class FakeAndroidPlatformViewsController {
|
|||
|
||||
Map<int, Offset> offsets = <int, Offset>{};
|
||||
|
||||
/// True if Texture Layer Hybrid Composition mode should be enabled.
|
||||
///
|
||||
/// When false, `create` will simulate the engine's fallback mode.
|
||||
bool allowTextureLayerMode = true;
|
||||
|
||||
void registerViewType(String viewType) {
|
||||
_registeredViewTypes.add(viewType);
|
||||
}
|
||||
|
@ -192,6 +209,7 @@ class FakeAndroidPlatformViewsController {
|
|||
final double? height = args['height'] as double?;
|
||||
final int layoutDirection = args['direction'] as int;
|
||||
final bool? hybrid = args['hybrid'] as bool?;
|
||||
final bool? hybridFallback = args['hybridFallback'] as bool?;
|
||||
final Uint8List? creationParams = args['params'] as Uint8List?;
|
||||
|
||||
if (_views.containsKey(id)) {
|
||||
|
@ -215,11 +233,21 @@ class FakeAndroidPlatformViewsController {
|
|||
_views[id] = FakeAndroidPlatformView(id, viewType,
|
||||
width != null && height != null ? Size(width, height) : null,
|
||||
layoutDirection,
|
||||
hybrid,
|
||||
creationParams,
|
||||
hybrid: hybrid,
|
||||
hybridFallback: hybridFallback,
|
||||
creationParams: creationParams,
|
||||
);
|
||||
// Return a hybrid result (null rather than a texture ID) if:
|
||||
final bool hybridResult =
|
||||
// hybrid was explicitly requested, or
|
||||
(hybrid ?? false) ||
|
||||
// hybrid fallback was requested and simulated.
|
||||
(!allowTextureLayerMode && (hybridFallback ?? false));
|
||||
if (hybridResult) {
|
||||
return Future<void>.value();
|
||||
}
|
||||
final int textureId = _textureCounter++;
|
||||
return Future<int>.sync(() => textureId);
|
||||
return Future<int>.value(textureId);
|
||||
}
|
||||
|
||||
Future<dynamic> _dispose(MethodCall call) {
|
||||
|
@ -506,7 +534,8 @@ class FakeHtmlPlatformViewsController {
|
|||
|
||||
@immutable
|
||||
class FakeAndroidPlatformView {
|
||||
const FakeAndroidPlatformView(this.id, this.type, this.size, this.layoutDirection, this.hybrid, [this.creationParams]);
|
||||
const FakeAndroidPlatformView(this.id, this.type, this.size, this.layoutDirection,
|
||||
{this.hybrid, this.hybridFallback, this.creationParams});
|
||||
|
||||
final int id;
|
||||
final String type;
|
||||
|
@ -514,14 +543,16 @@ class FakeAndroidPlatformView {
|
|||
final Size? size;
|
||||
final int layoutDirection;
|
||||
final bool? hybrid;
|
||||
final bool? hybridFallback;
|
||||
|
||||
FakeAndroidPlatformView copyWith({Size? size, int? layoutDirection}) => FakeAndroidPlatformView(
|
||||
id,
|
||||
type,
|
||||
size ?? this.size,
|
||||
layoutDirection ?? this.layoutDirection,
|
||||
hybrid,
|
||||
creationParams,
|
||||
hybrid: hybrid,
|
||||
hybridFallback: hybridFallback,
|
||||
creationParams: creationParams,
|
||||
);
|
||||
|
||||
@override
|
||||
|
@ -535,6 +566,7 @@ class FakeAndroidPlatformView {
|
|||
&& listEquals<int>(other.creationParams, creationParams)
|
||||
&& other.size == size
|
||||
&& other.hybrid == hybrid
|
||||
&& other.hybridFallback == hybridFallback
|
||||
&& other.layoutDirection == layoutDirection;
|
||||
}
|
||||
|
||||
|
@ -546,11 +578,14 @@ class FakeAndroidPlatformView {
|
|||
size,
|
||||
layoutDirection,
|
||||
hybrid,
|
||||
hybridFallback,
|
||||
);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'FakeAndroidPlatformView(id: $id, type: $type, size: $size, layoutDirection: $layoutDirection, hybrid: $hybrid, creationParams: $creationParams)';
|
||||
return 'FakeAndroidPlatformView(id: $id, type: $type, size: $size, '
|
||||
'layoutDirection: $layoutDirection, hybrid: $hybrid, '
|
||||
'hybridFallback: $hybridFallback, creationParams: $creationParams)';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ void main() {
|
|||
}
|
||||
});
|
||||
|
||||
test('create Android views', () async {
|
||||
test('create VD-fallback Android views', () async {
|
||||
viewsController.registerViewType('webview');
|
||||
await PlatformViewsService.initAndroidView(id: 0, viewType: 'webview', layoutDirection: TextDirection.ltr)
|
||||
.create(size: const Size(100.0, 100.0));
|
||||
|
@ -57,12 +57,85 @@ void main() {
|
|||
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(0, 'webview', Size(100.0, 100.0), AndroidViewController.kAndroidLayoutDirectionLtr),
|
||||
const FakeAndroidPlatformView(1, 'webview', Size(200.0, 300.0), AndroidViewController.kAndroidLayoutDirectionRtl),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
test('create HC-fallback Android views', () async {
|
||||
viewsController.registerViewType('webview');
|
||||
await PlatformViewsService.initSurfaceAndroidView(id: 0, viewType: 'webview', layoutDirection: TextDirection.ltr)
|
||||
.create(size: const Size(100.0, 100.0));
|
||||
await PlatformViewsService.initSurfaceAndroidView( id: 1, viewType: 'webview', layoutDirection: TextDirection.rtl)
|
||||
.create(size: const Size(200.0, 300.0));
|
||||
expect(
|
||||
viewsController.views,
|
||||
unorderedEquals(<FakeAndroidPlatformView>[
|
||||
const FakeAndroidPlatformView(0, 'webview', Size(100.0, 100.0), AndroidViewController.kAndroidLayoutDirectionLtr,
|
||||
hybridFallback: true),
|
||||
const FakeAndroidPlatformView(1, 'webview', Size(200.0, 300.0), AndroidViewController.kAndroidLayoutDirectionRtl,
|
||||
hybridFallback: true),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
test('create HC-only Android views', () async {
|
||||
viewsController.registerViewType('webview');
|
||||
await PlatformViewsService.initExpensiveAndroidView(id: 0, viewType: 'webview', layoutDirection: TextDirection.ltr)
|
||||
.create(size: const Size(100.0, 100.0));
|
||||
await PlatformViewsService.initExpensiveAndroidView( id: 1, viewType: 'webview', layoutDirection: TextDirection.rtl)
|
||||
.create(size: const Size(200.0, 300.0));
|
||||
expect(
|
||||
viewsController.views,
|
||||
unorderedEquals(<FakeAndroidPlatformView>[
|
||||
const FakeAndroidPlatformView(0, 'webview', null, AndroidViewController.kAndroidLayoutDirectionLtr,
|
||||
hybrid: true),
|
||||
const FakeAndroidPlatformView(1, 'webview', null, AndroidViewController.kAndroidLayoutDirectionRtl,
|
||||
hybrid: true),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
test('default view does not use view composition by default', () async {
|
||||
viewsController.registerViewType('webview');
|
||||
final AndroidViewController controller = PlatformViewsService.initAndroidView(id: 0, viewType: 'webview', layoutDirection: TextDirection.ltr);
|
||||
await controller.create(size: const Size(100.0, 100.0));
|
||||
expect(controller.requiresViewComposition, false);
|
||||
});
|
||||
|
||||
test('default view does not use view composition in fallback mode', () async {
|
||||
viewsController.registerViewType('webview');
|
||||
viewsController.allowTextureLayerMode = false;
|
||||
final AndroidViewController controller = PlatformViewsService.initAndroidView(id: 0, viewType: 'webview', layoutDirection: TextDirection.ltr);
|
||||
await controller.create(size: const Size(100.0, 100.0));
|
||||
viewsController.allowTextureLayerMode = true;
|
||||
expect(controller.requiresViewComposition, false);
|
||||
});
|
||||
|
||||
test('surface view does not use view composition by default', () async {
|
||||
viewsController.registerViewType('webview');
|
||||
final AndroidViewController controller = PlatformViewsService.initSurfaceAndroidView(id: 0, viewType: 'webview', layoutDirection: TextDirection.ltr);
|
||||
await controller.create(size: const Size(100.0, 100.0));
|
||||
expect(controller.requiresViewComposition, false);
|
||||
});
|
||||
|
||||
test('surface view does uses view composition in fallback mode', () async {
|
||||
viewsController.registerViewType('webview');
|
||||
viewsController.allowTextureLayerMode = false;
|
||||
final AndroidViewController controller = PlatformViewsService.initSurfaceAndroidView(id: 0, viewType: 'webview', layoutDirection: TextDirection.ltr);
|
||||
await controller.create(size: const Size(100.0, 100.0));
|
||||
viewsController.allowTextureLayerMode = true;
|
||||
expect(controller.requiresViewComposition, true);
|
||||
});
|
||||
|
||||
test('expensive view uses view composition', () async {
|
||||
viewsController.registerViewType('webview');
|
||||
final AndroidViewController controller = PlatformViewsService.initExpensiveAndroidView(id: 0, viewType: 'webview', layoutDirection: TextDirection.ltr);
|
||||
await controller.create(size: const Size(100.0, 100.0));
|
||||
expect(controller.requiresViewComposition, true);
|
||||
});
|
||||
|
||||
test('reuse Android view id', () async {
|
||||
viewsController.registerViewType('webview');
|
||||
final AndroidViewController controller = PlatformViewsService.initAndroidView(
|
||||
|
@ -111,7 +184,7 @@ void main() {
|
|||
expect(
|
||||
viewsController.views,
|
||||
unorderedEquals(<FakeAndroidPlatformView>[
|
||||
const FakeAndroidPlatformView(0, 'webview', Size(100.0, 100.0), AndroidViewController.kAndroidLayoutDirectionLtr, null),
|
||||
const FakeAndroidPlatformView(0, 'webview', Size(100.0, 100.0), AndroidViewController.kAndroidLayoutDirectionLtr),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
@ -164,8 +237,8 @@ void main() {
|
|||
expect(
|
||||
viewsController.views,
|
||||
unorderedEquals(<FakeAndroidPlatformView>[
|
||||
const FakeAndroidPlatformView(0, 'webview', Size(100.0, 100.0), AndroidViewController.kAndroidLayoutDirectionLtr, null),
|
||||
const FakeAndroidPlatformView(1, 'webview', Size(500.0, 500.0), AndroidViewController.kAndroidLayoutDirectionLtr, null),
|
||||
const FakeAndroidPlatformView(0, 'webview', Size(100.0, 100.0), AndroidViewController.kAndroidLayoutDirectionLtr),
|
||||
const FakeAndroidPlatformView(1, 'webview', Size(500.0, 500.0), AndroidViewController.kAndroidLayoutDirectionLtr),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
@ -209,7 +282,7 @@ void main() {
|
|||
expect(
|
||||
viewsController.views,
|
||||
unorderedEquals(<FakeAndroidPlatformView>[
|
||||
const FakeAndroidPlatformView(0, 'webview', Size(100.0, 100.0), AndroidViewController.kAndroidLayoutDirectionLtr, null),
|
||||
const FakeAndroidPlatformView(0, 'webview', Size(100.0, 100.0), AndroidViewController.kAndroidLayoutDirectionLtr),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
@ -226,7 +299,7 @@ void main() {
|
|||
expect(
|
||||
viewsController.views,
|
||||
unorderedEquals(<FakeAndroidPlatformView>[
|
||||
const FakeAndroidPlatformView(0, 'webview', Size(100.0, 100.0), AndroidViewController.kAndroidLayoutDirectionRtl, null),
|
||||
const FakeAndroidPlatformView(0, 'webview', Size(100.0, 100.0), AndroidViewController.kAndroidLayoutDirectionRtl),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
|
|
@ -41,7 +41,6 @@ void main() {
|
|||
'webview',
|
||||
const Size(200.0, 100.0),
|
||||
AndroidViewController.kAndroidLayoutDirectionLtr,
|
||||
null,
|
||||
),
|
||||
]),
|
||||
);
|
||||
|
@ -85,8 +84,7 @@ void main() {
|
|||
'webview',
|
||||
const Size(200.0, 100.0),
|
||||
AndroidViewController.kAndroidLayoutDirectionLtr,
|
||||
null,
|
||||
fakeView.creationParams,
|
||||
creationParams: fakeView.creationParams,
|
||||
),
|
||||
]),
|
||||
);
|
||||
|
@ -150,7 +148,6 @@ void main() {
|
|||
'webview',
|
||||
const Size(200.0, 100.0),
|
||||
AndroidViewController.kAndroidLayoutDirectionLtr,
|
||||
null,
|
||||
),
|
||||
]),
|
||||
);
|
||||
|
@ -166,7 +163,6 @@ void main() {
|
|||
'webview',
|
||||
const Size(100.0, 50.0),
|
||||
AndroidViewController.kAndroidLayoutDirectionLtr,
|
||||
null,
|
||||
),
|
||||
]),
|
||||
);
|
||||
|
@ -205,7 +201,6 @@ void main() {
|
|||
'maps',
|
||||
const Size(200.0, 100.0),
|
||||
AndroidViewController.kAndroidLayoutDirectionLtr,
|
||||
null,
|
||||
),
|
||||
]),
|
||||
);
|
||||
|
@ -272,7 +267,6 @@ void main() {
|
|||
'webview',
|
||||
const Size(200.0, 100.0),
|
||||
AndroidViewController.kAndroidLayoutDirectionLtr,
|
||||
null,
|
||||
),
|
||||
]),
|
||||
);
|
||||
|
@ -495,7 +489,6 @@ void main() {
|
|||
'maps',
|
||||
const Size(200.0, 100.0),
|
||||
AndroidViewController.kAndroidLayoutDirectionRtl,
|
||||
null,
|
||||
),
|
||||
]),
|
||||
);
|
||||
|
@ -518,7 +511,6 @@ void main() {
|
|||
'maps',
|
||||
const Size(200.0, 100.0),
|
||||
AndroidViewController.kAndroidLayoutDirectionLtr,
|
||||
null,
|
||||
),
|
||||
]),
|
||||
);
|
||||
|
@ -549,7 +541,6 @@ void main() {
|
|||
'maps',
|
||||
const Size(200.0, 100.0),
|
||||
AndroidViewController.kAndroidLayoutDirectionRtl,
|
||||
null,
|
||||
),
|
||||
]),
|
||||
);
|
||||
|
@ -575,7 +566,6 @@ void main() {
|
|||
'maps',
|
||||
const Size(200.0, 100.0),
|
||||
AndroidViewController.kAndroidLayoutDirectionLtr,
|
||||
null,
|
||||
),
|
||||
]),
|
||||
);
|
||||
|
@ -1257,6 +1247,63 @@ void main() {
|
|||
await tester.pumpWidget(surface);
|
||||
expect(controller.pointTransformer, isNotNull);
|
||||
});
|
||||
|
||||
testWidgets('AndroidViewSurface defaults to texture-based rendering', (WidgetTester tester) async {
|
||||
final AndroidViewSurface surface = AndroidViewSurface(
|
||||
controller: controller,
|
||||
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
|
||||
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
|
||||
);
|
||||
await tester.pumpWidget(surface);
|
||||
|
||||
expect(find.byWidgetPredicate(
|
||||
(Widget widget) => widget.runtimeType.toString() == '_TextureBasedAndroidViewSurface',
|
||||
), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('AndroidViewSurface uses view-based rendering when initially required', (WidgetTester tester) async {
|
||||
controller.requiresViewComposition = true;
|
||||
final AndroidViewSurface surface = AndroidViewSurface(
|
||||
controller: controller,
|
||||
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
|
||||
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
|
||||
);
|
||||
await tester.pumpWidget(surface);
|
||||
|
||||
expect(find.byWidgetPredicate(
|
||||
(Widget widget) => widget.runtimeType.toString() == '_PlatformLayerBasedAndroidViewSurface',
|
||||
), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('AndroidViewSurface can switch to view-based rendering after creation', (WidgetTester tester) async {
|
||||
final AndroidViewSurface surface = AndroidViewSurface(
|
||||
controller: controller,
|
||||
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
|
||||
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
|
||||
);
|
||||
await tester.pumpWidget(surface);
|
||||
|
||||
expect(find.byWidgetPredicate(
|
||||
(Widget widget) => widget.runtimeType.toString() == '_TextureBasedAndroidViewSurface',
|
||||
), findsOneWidget);
|
||||
expect(find.byWidgetPredicate(
|
||||
(Widget widget) => widget.runtimeType.toString() == '_PlatformLayerBasedAndroidViewSurface',
|
||||
), findsNothing);
|
||||
|
||||
// Simulate a creation-time switch to view composition.
|
||||
controller.requiresViewComposition = true;
|
||||
for (final PlatformViewCreatedCallback callback in controller.createdCallbacks) {
|
||||
callback(controller.viewId);
|
||||
}
|
||||
await tester.pumpWidget(surface);
|
||||
|
||||
expect(find.byWidgetPredicate(
|
||||
(Widget widget) => widget.runtimeType.toString() == '_TextureBasedAndroidViewSurface',
|
||||
), findsNothing);
|
||||
expect(find.byWidgetPredicate(
|
||||
(Widget widget) => widget.runtimeType.toString() == '_PlatformLayerBasedAndroidViewSurface',
|
||||
), findsOneWidget);
|
||||
});
|
||||
});
|
||||
|
||||
group('UiKitView', () {
|
||||
|
|
Loading…
Reference in a new issue