Skip to content

Commit

Permalink
Include initial offset when using PlatformViewSurface (#114103)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
bparrishMines authored Nov 15, 2022
1 parent acf01eb commit 95ace11
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 19 deletions.
2 changes: 1 addition & 1 deletion packages/flutter/lib/src/rendering/platform_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ class RenderAndroidView extends PlatformViewRenderBox {
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
// location on the screen as the platform view widget in the Flutter framework.
Expand Down
26 changes: 18 additions & 8 deletions packages/flutter/lib/src/services/platform_views.dart
Original file line number Diff line number Diff line change
Expand Up @@ -779,7 +779,7 @@ abstract class AndroidViewController extends PlatformViewController {
///
/// If [_createRequiresSize] is true, `size` is non-nullable, and the call
/// 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].
Future<Size> _sendResizeMessage(Size size);
Expand All @@ -788,7 +788,7 @@ abstract class AndroidViewController extends PlatformViewController {
bool get awaitingCreation => _state == _AndroidViewState.waitingForSize;

@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.waitingForSize, 'Android view is already sized. View id: $viewId');

Expand All @@ -798,7 +798,7 @@ abstract class AndroidViewController extends PlatformViewController {
}

_state = _AndroidViewState.creating;
await _sendCreateMessage(size: size);
await _sendCreateMessage(size: size, position: position);
_state = _AndroidViewState.created;

for (final PlatformViewCreatedCallback callback in _platformViewCreatedCallbacks) {
Expand Down Expand Up @@ -1011,7 +1011,7 @@ class SurfaceAndroidViewController extends AndroidViewController {
bool get _createRequiresSize => true;

@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.');

final dynamic response = await _AndroidViewControllerInternals.sendCreateMessage(
Expand All @@ -1022,6 +1022,7 @@ class SurfaceAndroidViewController extends AndroidViewController {
layoutDirection: _layoutDirection,
creationParams: _creationParams,
size: size,
position: position,
);
if (response is int) {
(_internals as _TextureAndroidViewControllerInternals).textureId = response;
Expand Down Expand Up @@ -1076,13 +1077,14 @@ class ExpensiveAndroidViewController extends AndroidViewController {
bool get _createRequiresSize => false;

@override
Future<void> _sendCreateMessage({required Size? size}) async {
Future<void> _sendCreateMessage({required Size? size, Offset? position}) async {
await _AndroidViewControllerInternals.sendCreateMessage(
viewId: viewId,
viewType: _viewType,
hybrid: true,
layoutDirection: _layoutDirection,
creationParams: _creationParams,
position: position,
);
}

Expand Down Expand Up @@ -1133,7 +1135,7 @@ class TextureAndroidViewController extends AndroidViewController {
bool get _createRequiresSize => true;

@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.');

_internals.textureId = await _AndroidViewControllerInternals.sendCreateMessage(
Expand All @@ -1143,6 +1145,7 @@ class TextureAndroidViewController extends AndroidViewController {
layoutDirection: _layoutDirection,
creationParams: _creationParams,
size: size,
position: position,
) as int;
}

Expand Down Expand Up @@ -1190,7 +1193,8 @@ abstract class _AndroidViewControllerInternals {
required bool hybrid,
bool hybridFallback = false,
_CreationParams? creationParams,
Size? size}) {
Size? size,
Offset? position}) {
final Map<String, dynamic> args = <String, dynamic>{
'id': viewId,
'viewType': viewType,
Expand All @@ -1199,6 +1203,8 @@ abstract class _AndroidViewControllerInternals {
if (size != null) 'width': size.width,
if (size != null) 'height': size.height,
if (hybridFallback == true) 'hybridFallback': hybridFallback,
if (position != null) 'left': position.dx,
if (position != null) 'top': position.dy,
};
if (creationParams != null) {
final ByteData paramsByteData = creationParams.codec.encodeMessage(creationParams.data)!;
Expand Down Expand Up @@ -1449,7 +1455,11 @@ abstract class PlatformViewController {
/// [size] is the view's initial size in logical pixel.
/// [size] can be omitted if the concrete implementation doesn't require an initial size
/// 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.
///
Expand Down
12 changes: 8 additions & 4 deletions packages/flutter/lib/src/widgets/platform_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';

import 'basic.dart';
Expand Down Expand Up @@ -879,9 +880,9 @@ class _PlatformViewLinkState extends State<PlatformViewLink> {
if (!_platformViewCreated) {
// Depending on the implementation, the first non-empty size can be used
// to size the platform view.
return _PlatformViewPlaceHolder(onLayout: (Size size) {
return _PlatformViewPlaceHolder(onLayout: (Size size, Offset position) {
if (controller.awaitingCreation && !size.isEmpty) {
controller.create(size: size);
controller.create(size: size, position: position);
}
});
}
Expand Down Expand Up @@ -1188,7 +1189,7 @@ class _PlatformLayerBasedAndroidViewSurface extends PlatformViewSurface {

/// 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);
typedef _OnLayoutCallback = void Function(Size size, Offset position);

/// A [RenderBox] that notifies its size to the owner after a layout.
class _PlatformViewPlaceholderBox extends RenderConstrainedBox {
Expand All @@ -1204,7 +1205,10 @@ class _PlatformViewPlaceholderBox extends RenderConstrainedBox {
@override
void 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));
});
}
}

Expand Down
20 changes: 15 additions & 5 deletions packages/flutter/test/services/fake_platform_views.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ class FakeAndroidViewController implements AndroidViewController {

bool _createCalledSuccessfully = false;

Offset? createPosition;

final List<PlatformViewCreatedCallback> _createdCallbacks = <PlatformViewCreatedCallback>[];

/// Events that are dispatched.
Expand Down Expand Up @@ -131,12 +133,13 @@ class FakeAndroidViewController implements AndroidViewController {
}

@override
Future<void> create({Size? size}) async {
Future<void> create({Size? size, Offset? position}) async {
assert(!_createCalledSuccessfully);
if (requiresSize && size != null) {
assert(!size.isEmpty);
}
_createCalledSuccessfully = size != null || !requiresSize;
_createCalledSuccessfully = size != null && position != null || !requiresSize;
createPosition = position;
}

@override
Expand Down Expand Up @@ -214,6 +217,8 @@ class FakeAndroidPlatformViewsController {
final bool? hybrid = args['hybrid'] as bool?;
final bool? hybridFallback = args['hybridFallback'] as bool?;
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)) {
throw PlatformException(
Expand All @@ -239,6 +244,7 @@ class FakeAndroidPlatformViewsController {
hybrid: hybrid,
hybridFallback: hybridFallback,
creationParams: creationParams,
position: left != null && top != null ? Offset(left, top) : null,
);
// Return a hybrid result (null rather than a texture ID) if:
final bool hybridResult =
Expand Down Expand Up @@ -538,7 +544,7 @@ class FakeHtmlPlatformViewsController {
@immutable
class FakeAndroidPlatformView {
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 String type;
Expand All @@ -547,6 +553,7 @@ class FakeAndroidPlatformView {
final int layoutDirection;
final bool? hybrid;
final bool? hybridFallback;
final Offset? position;

FakeAndroidPlatformView copyWith({Size? size, int? layoutDirection}) => FakeAndroidPlatformView(
id,
Expand All @@ -556,6 +563,7 @@ class FakeAndroidPlatformView {
hybrid: hybrid,
hybridFallback: hybridFallback,
creationParams: creationParams,
position: position,
);

@override
Expand All @@ -570,7 +578,8 @@ class FakeAndroidPlatformView {
&& other.size == size
&& other.hybrid == hybrid
&& other.hybridFallback == hybridFallback
&& other.layoutDirection == layoutDirection;
&& other.layoutDirection == layoutDirection
&& other.position == position;
}

@override
Expand All @@ -582,13 +591,14 @@ class FakeAndroidPlatformView {
layoutDirection,
hybrid,
hybridFallback,
position,
);

@override
String toString() {
return 'FakeAndroidPlatformView(id: $id, type: $type, size: $size, '
'layoutDirection: $layoutDirection, hybrid: $hybrid, '
'hybridFallback: $hybridFallback, creationParams: $creationParams)';
'hybridFallback: $hybridFallback, creationParams: $creationParams, position: $position)';
}
}

Expand Down
47 changes: 46 additions & 1 deletion packages/flutter/test/widgets/platform_view_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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(
'PlatformViewLink does not double-call create for Android Hybrid Composition',
(WidgetTester tester) async {
Expand All @@ -2616,7 +2661,7 @@ void main() {
controller = FakeAndroidViewController(params.id);
controller.create();
// This test should be simulating Hybrid Composition mode, where
// `create` takes effect immidately.
// `create` takes effect immediately.
expect(controller.awaitingCreation, false);
return controller;
},
Expand Down

0 comments on commit 95ace11

Please sign in to comment.