Unverified Commit 95ace11a authored by Maurice Parrish's avatar Maurice Parrish Committed by GitHub

Include initial offset when using PlatformViewSurface (#114103)

* add position to layout creation

* make position nullable

* fix tests

* test

* clear test size

* clear device pixel ratio

* add more documentaiton

* add comment about localToGlobal
parent acf01eb3
...@@ -192,7 +192,7 @@ class RenderAndroidView extends PlatformViewRenderBox { ...@@ -192,7 +192,7 @@ class RenderAndroidView extends PlatformViewRenderBox {
markNeedsPaint(); markNeedsPaint();
} }
// Sets the offset of the underlaying platform view on the platform side. // Sets the offset of the underlying platform view on the platform side.
// //
// This allows the Android native view to draw the a11y highlights in the same // This allows the Android native view to draw the a11y highlights in the same
// location on the screen as the platform view widget in the Flutter framework. // location on the screen as the platform view widget in the Flutter framework.
......
...@@ -779,7 +779,7 @@ abstract class AndroidViewController extends PlatformViewController { ...@@ -779,7 +779,7 @@ abstract class AndroidViewController extends PlatformViewController {
/// ///
/// If [_createRequiresSize] is true, `size` is non-nullable, and the call /// If [_createRequiresSize] is true, `size` is non-nullable, and the call
/// should instead be deferred until the size is available. /// should instead be deferred until the size is available.
Future<void> _sendCreateMessage({required covariant Size? size}); Future<void> _sendCreateMessage({required covariant Size? size, Offset? position});
/// Sends the message to resize the platform view to [size]. /// Sends the message to resize the platform view to [size].
Future<Size> _sendResizeMessage(Size size); Future<Size> _sendResizeMessage(Size size);
...@@ -788,7 +788,7 @@ abstract class AndroidViewController extends PlatformViewController { ...@@ -788,7 +788,7 @@ abstract class AndroidViewController extends PlatformViewController {
bool get awaitingCreation => _state == _AndroidViewState.waitingForSize; bool get awaitingCreation => _state == _AndroidViewState.waitingForSize;
@override @override
Future<void> create({Size? size}) async { Future<void> create({Size? size, Offset? position}) async {
assert(_state != _AndroidViewState.disposed, 'trying to create a disposed Android view'); assert(_state != _AndroidViewState.disposed, 'trying to create a disposed Android view');
assert(_state == _AndroidViewState.waitingForSize, 'Android view is already sized. View id: $viewId'); assert(_state == _AndroidViewState.waitingForSize, 'Android view is already sized. View id: $viewId');
...@@ -798,7 +798,7 @@ abstract class AndroidViewController extends PlatformViewController { ...@@ -798,7 +798,7 @@ abstract class AndroidViewController extends PlatformViewController {
} }
_state = _AndroidViewState.creating; _state = _AndroidViewState.creating;
await _sendCreateMessage(size: size); await _sendCreateMessage(size: size, position: position);
_state = _AndroidViewState.created; _state = _AndroidViewState.created;
for (final PlatformViewCreatedCallback callback in _platformViewCreatedCallbacks) { for (final PlatformViewCreatedCallback callback in _platformViewCreatedCallbacks) {
...@@ -1011,7 +1011,7 @@ class SurfaceAndroidViewController extends AndroidViewController { ...@@ -1011,7 +1011,7 @@ class SurfaceAndroidViewController extends AndroidViewController {
bool get _createRequiresSize => true; bool get _createRequiresSize => true;
@override @override
Future<bool> _sendCreateMessage({required Size size}) async { Future<bool> _sendCreateMessage({required Size size, Offset? position}) async {
assert(!size.isEmpty, 'trying to create $TextureAndroidViewController without setting a valid size.'); assert(!size.isEmpty, 'trying to create $TextureAndroidViewController without setting a valid size.');
final dynamic response = await _AndroidViewControllerInternals.sendCreateMessage( final dynamic response = await _AndroidViewControllerInternals.sendCreateMessage(
...@@ -1022,6 +1022,7 @@ class SurfaceAndroidViewController extends AndroidViewController { ...@@ -1022,6 +1022,7 @@ class SurfaceAndroidViewController extends AndroidViewController {
layoutDirection: _layoutDirection, layoutDirection: _layoutDirection,
creationParams: _creationParams, creationParams: _creationParams,
size: size, size: size,
position: position,
); );
if (response is int) { if (response is int) {
(_internals as _TextureAndroidViewControllerInternals).textureId = response; (_internals as _TextureAndroidViewControllerInternals).textureId = response;
...@@ -1076,13 +1077,14 @@ class ExpensiveAndroidViewController extends AndroidViewController { ...@@ -1076,13 +1077,14 @@ class ExpensiveAndroidViewController extends AndroidViewController {
bool get _createRequiresSize => false; bool get _createRequiresSize => false;
@override @override
Future<void> _sendCreateMessage({required Size? size}) async { Future<void> _sendCreateMessage({required Size? size, Offset? position}) async {
await _AndroidViewControllerInternals.sendCreateMessage( await _AndroidViewControllerInternals.sendCreateMessage(
viewId: viewId, viewId: viewId,
viewType: _viewType, viewType: _viewType,
hybrid: true, hybrid: true,
layoutDirection: _layoutDirection, layoutDirection: _layoutDirection,
creationParams: _creationParams, creationParams: _creationParams,
position: position,
); );
} }
...@@ -1133,7 +1135,7 @@ class TextureAndroidViewController extends AndroidViewController { ...@@ -1133,7 +1135,7 @@ class TextureAndroidViewController extends AndroidViewController {
bool get _createRequiresSize => true; bool get _createRequiresSize => true;
@override @override
Future<void> _sendCreateMessage({required Size size}) async { Future<void> _sendCreateMessage({required Size size, Offset? position}) async {
assert(!size.isEmpty, 'trying to create $TextureAndroidViewController without setting a valid size.'); assert(!size.isEmpty, 'trying to create $TextureAndroidViewController without setting a valid size.');
_internals.textureId = await _AndroidViewControllerInternals.sendCreateMessage( _internals.textureId = await _AndroidViewControllerInternals.sendCreateMessage(
...@@ -1143,6 +1145,7 @@ class TextureAndroidViewController extends AndroidViewController { ...@@ -1143,6 +1145,7 @@ class TextureAndroidViewController extends AndroidViewController {
layoutDirection: _layoutDirection, layoutDirection: _layoutDirection,
creationParams: _creationParams, creationParams: _creationParams,
size: size, size: size,
position: position,
) as int; ) as int;
} }
...@@ -1190,7 +1193,8 @@ abstract class _AndroidViewControllerInternals { ...@@ -1190,7 +1193,8 @@ abstract class _AndroidViewControllerInternals {
required bool hybrid, required bool hybrid,
bool hybridFallback = false, bool hybridFallback = false,
_CreationParams? creationParams, _CreationParams? creationParams,
Size? size}) { Size? size,
Offset? position}) {
final Map<String, dynamic> args = <String, dynamic>{ final Map<String, dynamic> args = <String, dynamic>{
'id': viewId, 'id': viewId,
'viewType': viewType, 'viewType': viewType,
...@@ -1199,6 +1203,8 @@ abstract class _AndroidViewControllerInternals { ...@@ -1199,6 +1203,8 @@ abstract class _AndroidViewControllerInternals {
if (size != null) 'width': size.width, if (size != null) 'width': size.width,
if (size != null) 'height': size.height, if (size != null) 'height': size.height,
if (hybridFallback == true) 'hybridFallback': hybridFallback, if (hybridFallback == true) 'hybridFallback': hybridFallback,
if (position != null) 'left': position.dx,
if (position != null) 'top': position.dy,
}; };
if (creationParams != null) { if (creationParams != null) {
final ByteData paramsByteData = creationParams.codec.encodeMessage(creationParams.data)!; final ByteData paramsByteData = creationParams.codec.encodeMessage(creationParams.data)!;
...@@ -1449,7 +1455,11 @@ abstract class PlatformViewController { ...@@ -1449,7 +1455,11 @@ abstract class PlatformViewController {
/// [size] is the view's initial size in logical pixel. /// [size] is the view's initial size in logical pixel.
/// [size] can be omitted if the concrete implementation doesn't require an initial size /// [size] can be omitted if the concrete implementation doesn't require an initial size
/// to create the platform view. /// to create the platform view.
Future<void> create({Size? size}) async {} ///
/// [position] is the view's initial position in logical pixels.
/// [position] can be omitted if the concrete implementation doesn't require
/// an initial position.
Future<void> create({Size? size, Offset? position}) async {}
/// Disposes the platform view. /// Disposes the platform view.
/// ///
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'basic.dart'; import 'basic.dart';
...@@ -879,9 +880,9 @@ class _PlatformViewLinkState extends State<PlatformViewLink> { ...@@ -879,9 +880,9 @@ class _PlatformViewLinkState extends State<PlatformViewLink> {
if (!_platformViewCreated) { if (!_platformViewCreated) {
// Depending on the implementation, the first non-empty size can be used // Depending on the implementation, the first non-empty size can be used
// to size the platform view. // to size the platform view.
return _PlatformViewPlaceHolder(onLayout: (Size size) { return _PlatformViewPlaceHolder(onLayout: (Size size, Offset position) {
if (controller.awaitingCreation && !size.isEmpty) { if (controller.awaitingCreation && !size.isEmpty) {
controller.create(size: size); controller.create(size: size, position: position);
} }
}); });
} }
...@@ -1188,7 +1189,7 @@ class _PlatformLayerBasedAndroidViewSurface extends PlatformViewSurface { ...@@ -1188,7 +1189,7 @@ class _PlatformLayerBasedAndroidViewSurface extends PlatformViewSurface {
/// A callback used to notify the size of the platform view placeholder. /// A callback used to notify the size of the platform view placeholder.
/// This size is the initial size of the platform view. /// This size is the initial size of the platform view.
typedef _OnLayoutCallback = void Function(Size size); typedef _OnLayoutCallback = void Function(Size size, Offset position);
/// A [RenderBox] that notifies its size to the owner after a layout. /// A [RenderBox] that notifies its size to the owner after a layout.
class _PlatformViewPlaceholderBox extends RenderConstrainedBox { class _PlatformViewPlaceholderBox extends RenderConstrainedBox {
...@@ -1204,7 +1205,10 @@ class _PlatformViewPlaceholderBox extends RenderConstrainedBox { ...@@ -1204,7 +1205,10 @@ class _PlatformViewPlaceholderBox extends RenderConstrainedBox {
@override @override
void performLayout() { void performLayout() {
super.performLayout(); super.performLayout();
onLayout(size); // A call to `localToGlobal` requires waiting for a frame to render first.
SchedulerBinding.instance.addPostFrameCallback((_) {
onLayout(size, localToGlobal(Offset.zero));
});
} }
} }
......
...@@ -60,6 +60,8 @@ class FakeAndroidViewController implements AndroidViewController { ...@@ -60,6 +60,8 @@ class FakeAndroidViewController implements AndroidViewController {
bool _createCalledSuccessfully = false; bool _createCalledSuccessfully = false;
Offset? createPosition;
final List<PlatformViewCreatedCallback> _createdCallbacks = <PlatformViewCreatedCallback>[]; final List<PlatformViewCreatedCallback> _createdCallbacks = <PlatformViewCreatedCallback>[];
/// Events that are dispatched. /// Events that are dispatched.
...@@ -131,12 +133,13 @@ class FakeAndroidViewController implements AndroidViewController { ...@@ -131,12 +133,13 @@ class FakeAndroidViewController implements AndroidViewController {
} }
@override @override
Future<void> create({Size? size}) async { Future<void> create({Size? size, Offset? position}) async {
assert(!_createCalledSuccessfully); assert(!_createCalledSuccessfully);
if (requiresSize && size != null) { if (requiresSize && size != null) {
assert(!size.isEmpty); assert(!size.isEmpty);
} }
_createCalledSuccessfully = size != null || !requiresSize; _createCalledSuccessfully = size != null && position != null || !requiresSize;
createPosition = position;
} }
@override @override
...@@ -214,6 +217,8 @@ class FakeAndroidPlatformViewsController { ...@@ -214,6 +217,8 @@ class FakeAndroidPlatformViewsController {
final bool? hybrid = args['hybrid'] as bool?; final bool? hybrid = args['hybrid'] as bool?;
final bool? hybridFallback = args['hybridFallback'] as bool?; final bool? hybridFallback = args['hybridFallback'] as bool?;
final Uint8List? creationParams = args['params'] as Uint8List?; final Uint8List? creationParams = args['params'] as Uint8List?;
final double? top = args['top'] as double?;
final double? left = args['left'] as double?;
if (_views.containsKey(id)) { if (_views.containsKey(id)) {
throw PlatformException( throw PlatformException(
...@@ -239,6 +244,7 @@ class FakeAndroidPlatformViewsController { ...@@ -239,6 +244,7 @@ class FakeAndroidPlatformViewsController {
hybrid: hybrid, hybrid: hybrid,
hybridFallback: hybridFallback, hybridFallback: hybridFallback,
creationParams: creationParams, creationParams: creationParams,
position: left != null && top != null ? Offset(left, top) : null,
); );
// Return a hybrid result (null rather than a texture ID) if: // Return a hybrid result (null rather than a texture ID) if:
final bool hybridResult = final bool hybridResult =
...@@ -538,7 +544,7 @@ class FakeHtmlPlatformViewsController { ...@@ -538,7 +544,7 @@ class FakeHtmlPlatformViewsController {
@immutable @immutable
class FakeAndroidPlatformView { class FakeAndroidPlatformView {
const FakeAndroidPlatformView(this.id, this.type, this.size, this.layoutDirection, const FakeAndroidPlatformView(this.id, this.type, this.size, this.layoutDirection,
{this.hybrid, this.hybridFallback, this.creationParams}); {this.hybrid, this.hybridFallback, this.creationParams, this.position});
final int id; final int id;
final String type; final String type;
...@@ -547,6 +553,7 @@ class FakeAndroidPlatformView { ...@@ -547,6 +553,7 @@ class FakeAndroidPlatformView {
final int layoutDirection; final int layoutDirection;
final bool? hybrid; final bool? hybrid;
final bool? hybridFallback; final bool? hybridFallback;
final Offset? position;
FakeAndroidPlatformView copyWith({Size? size, int? layoutDirection}) => FakeAndroidPlatformView( FakeAndroidPlatformView copyWith({Size? size, int? layoutDirection}) => FakeAndroidPlatformView(
id, id,
...@@ -556,6 +563,7 @@ class FakeAndroidPlatformView { ...@@ -556,6 +563,7 @@ class FakeAndroidPlatformView {
hybrid: hybrid, hybrid: hybrid,
hybridFallback: hybridFallback, hybridFallback: hybridFallback,
creationParams: creationParams, creationParams: creationParams,
position: position,
); );
@override @override
...@@ -570,7 +578,8 @@ class FakeAndroidPlatformView { ...@@ -570,7 +578,8 @@ class FakeAndroidPlatformView {
&& other.size == size && other.size == size
&& other.hybrid == hybrid && other.hybrid == hybrid
&& other.hybridFallback == hybridFallback && other.hybridFallback == hybridFallback
&& other.layoutDirection == layoutDirection; && other.layoutDirection == layoutDirection
&& other.position == position;
} }
@override @override
...@@ -582,13 +591,14 @@ class FakeAndroidPlatformView { ...@@ -582,13 +591,14 @@ class FakeAndroidPlatformView {
layoutDirection, layoutDirection,
hybrid, hybrid,
hybridFallback, hybridFallback,
position,
); );
@override @override
String toString() { String toString() {
return 'FakeAndroidPlatformView(id: $id, type: $type, size: $size, ' return 'FakeAndroidPlatformView(id: $id, type: $type, size: $size, '
'layoutDirection: $layoutDirection, hybrid: $hybrid, ' 'layoutDirection: $layoutDirection, hybrid: $hybrid, '
'hybridFallback: $hybridFallback, creationParams: $creationParams)'; 'hybridFallback: $hybridFallback, creationParams: $creationParams, position: $position)';
} }
} }
......
...@@ -2599,6 +2599,51 @@ void main() { ...@@ -2599,6 +2599,51 @@ void main() {
}, },
); );
testWidgets('PlatformViewLink includes offset in create call when using texture layer', (WidgetTester tester) async {
late FakeAndroidViewController controller;
final PlatformViewLink platformViewLink = PlatformViewLink(
viewType: 'webview',
onCreatePlatformView: (PlatformViewCreationParams params) {
controller = FakeAndroidViewController(params.id, requiresSize: true);
controller.create();
// This test should be simulating one of the texture-based display
// modes, where `create` is a no-op when not provided a size, and
// creation is triggered via a later call to setSize, or to `create`
// with a size.
expect(controller.awaitingCreation, true);
return controller;
},
surfaceFactory: (BuildContext context, PlatformViewController controller) {
return PlatformViewSurface(
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
controller: controller,
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
);
},
);
TestWidgetsFlutterBinding.instance.window.physicalSizeTestValue = const Size(400, 200);
TestWidgetsFlutterBinding.instance.window.devicePixelRatioTestValue = 1.0;
await tester.pumpWidget(
Container(
constraints: const BoxConstraints.expand(),
alignment: Alignment.center,
child: SizedBox(
width: 100,
height: 50,
child: platformViewLink,
),
)
);
expect(controller.createPosition, const Offset(150, 75));
TestWidgetsFlutterBinding.instance.window.clearPhysicalSizeTestValue();
TestWidgetsFlutterBinding.instance.window.clearDevicePixelRatioTestValue();
});
testWidgets( testWidgets(
'PlatformViewLink does not double-call create for Android Hybrid Composition', 'PlatformViewLink does not double-call create for Android Hybrid Composition',
(WidgetTester tester) async { (WidgetTester tester) async {
...@@ -2616,7 +2661,7 @@ void main() { ...@@ -2616,7 +2661,7 @@ void main() {
controller = FakeAndroidViewController(params.id); controller = FakeAndroidViewController(params.id);
controller.create(); controller.create();
// This test should be simulating Hybrid Composition mode, where // This test should be simulating Hybrid Composition mode, where
// `create` takes effect immidately. // `create` takes effect immediately.
expect(controller.awaitingCreation, false); expect(controller.awaitingCreation, false);
return controller; return controller;
}, },
......
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