Skip to content

Commit 95ace11

Browse files
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
1 parent acf01eb commit 95ace11

File tree

5 files changed

+88
-19
lines changed

5 files changed

+88
-19
lines changed

packages/flutter/lib/src/rendering/platform_view.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ class RenderAndroidView extends PlatformViewRenderBox {
192192
markNeedsPaint();
193193
}
194194

195-
// Sets the offset of the underlaying platform view on the platform side.
195+
// Sets the offset of the underlying platform view on the platform side.
196196
//
197197
// This allows the Android native view to draw the a11y highlights in the same
198198
// location on the screen as the platform view widget in the Flutter framework.

packages/flutter/lib/src/services/platform_views.dart

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -779,7 +779,7 @@ abstract class AndroidViewController extends PlatformViewController {
779779
///
780780
/// If [_createRequiresSize] is true, `size` is non-nullable, and the call
781781
/// should instead be deferred until the size is available.
782-
Future<void> _sendCreateMessage({required covariant Size? size});
782+
Future<void> _sendCreateMessage({required covariant Size? size, Offset? position});
783783

784784
/// Sends the message to resize the platform view to [size].
785785
Future<Size> _sendResizeMessage(Size size);
@@ -788,7 +788,7 @@ abstract class AndroidViewController extends PlatformViewController {
788788
bool get awaitingCreation => _state == _AndroidViewState.waitingForSize;
789789

790790
@override
791-
Future<void> create({Size? size}) async {
791+
Future<void> create({Size? size, Offset? position}) async {
792792
assert(_state != _AndroidViewState.disposed, 'trying to create a disposed Android view');
793793
assert(_state == _AndroidViewState.waitingForSize, 'Android view is already sized. View id: $viewId');
794794

@@ -798,7 +798,7 @@ abstract class AndroidViewController extends PlatformViewController {
798798
}
799799

800800
_state = _AndroidViewState.creating;
801-
await _sendCreateMessage(size: size);
801+
await _sendCreateMessage(size: size, position: position);
802802
_state = _AndroidViewState.created;
803803

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

10131013
@override
1014-
Future<bool> _sendCreateMessage({required Size size}) async {
1014+
Future<bool> _sendCreateMessage({required Size size, Offset? position}) async {
10151015
assert(!size.isEmpty, 'trying to create $TextureAndroidViewController without setting a valid size.');
10161016

10171017
final dynamic response = await _AndroidViewControllerInternals.sendCreateMessage(
@@ -1022,6 +1022,7 @@ class SurfaceAndroidViewController extends AndroidViewController {
10221022
layoutDirection: _layoutDirection,
10231023
creationParams: _creationParams,
10241024
size: size,
1025+
position: position,
10251026
);
10261027
if (response is int) {
10271028
(_internals as _TextureAndroidViewControllerInternals).textureId = response;
@@ -1076,13 +1077,14 @@ class ExpensiveAndroidViewController extends AndroidViewController {
10761077
bool get _createRequiresSize => false;
10771078

10781079
@override
1079-
Future<void> _sendCreateMessage({required Size? size}) async {
1080+
Future<void> _sendCreateMessage({required Size? size, Offset? position}) async {
10801081
await _AndroidViewControllerInternals.sendCreateMessage(
10811082
viewId: viewId,
10821083
viewType: _viewType,
10831084
hybrid: true,
10841085
layoutDirection: _layoutDirection,
10851086
creationParams: _creationParams,
1087+
position: position,
10861088
);
10871089
}
10881090

@@ -1133,7 +1135,7 @@ class TextureAndroidViewController extends AndroidViewController {
11331135
bool get _createRequiresSize => true;
11341136

11351137
@override
1136-
Future<void> _sendCreateMessage({required Size size}) async {
1138+
Future<void> _sendCreateMessage({required Size size, Offset? position}) async {
11371139
assert(!size.isEmpty, 'trying to create $TextureAndroidViewController without setting a valid size.');
11381140

11391141
_internals.textureId = await _AndroidViewControllerInternals.sendCreateMessage(
@@ -1143,6 +1145,7 @@ class TextureAndroidViewController extends AndroidViewController {
11431145
layoutDirection: _layoutDirection,
11441146
creationParams: _creationParams,
11451147
size: size,
1148+
position: position,
11461149
) as int;
11471150
}
11481151

@@ -1190,7 +1193,8 @@ abstract class _AndroidViewControllerInternals {
11901193
required bool hybrid,
11911194
bool hybridFallback = false,
11921195
_CreationParams? creationParams,
1193-
Size? size}) {
1196+
Size? size,
1197+
Offset? position}) {
11941198
final Map<String, dynamic> args = <String, dynamic>{
11951199
'id': viewId,
11961200
'viewType': viewType,
@@ -1199,6 +1203,8 @@ abstract class _AndroidViewControllerInternals {
11991203
if (size != null) 'width': size.width,
12001204
if (size != null) 'height': size.height,
12011205
if (hybridFallback == true) 'hybridFallback': hybridFallback,
1206+
if (position != null) 'left': position.dx,
1207+
if (position != null) 'top': position.dy,
12021208
};
12031209
if (creationParams != null) {
12041210
final ByteData paramsByteData = creationParams.codec.encodeMessage(creationParams.data)!;
@@ -1449,7 +1455,11 @@ abstract class PlatformViewController {
14491455
/// [size] is the view's initial size in logical pixel.
14501456
/// [size] can be omitted if the concrete implementation doesn't require an initial size
14511457
/// to create the platform view.
1452-
Future<void> create({Size? size}) async {}
1458+
///
1459+
/// [position] is the view's initial position in logical pixels.
1460+
/// [position] can be omitted if the concrete implementation doesn't require
1461+
/// an initial position.
1462+
Future<void> create({Size? size, Offset? position}) async {}
14531463

14541464
/// Disposes the platform view.
14551465
///

packages/flutter/lib/src/widgets/platform_view.dart

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import 'package:flutter/foundation.dart';
66
import 'package:flutter/gestures.dart';
77
import 'package:flutter/rendering.dart';
8+
import 'package:flutter/scheduler.dart';
89
import 'package:flutter/services.dart';
910

1011
import 'basic.dart';
@@ -879,9 +880,9 @@ class _PlatformViewLinkState extends State<PlatformViewLink> {
879880
if (!_platformViewCreated) {
880881
// Depending on the implementation, the first non-empty size can be used
881882
// to size the platform view.
882-
return _PlatformViewPlaceHolder(onLayout: (Size size) {
883+
return _PlatformViewPlaceHolder(onLayout: (Size size, Offset position) {
883884
if (controller.awaitingCreation && !size.isEmpty) {
884-
controller.create(size: size);
885+
controller.create(size: size, position: position);
885886
}
886887
});
887888
}
@@ -1188,7 +1189,7 @@ class _PlatformLayerBasedAndroidViewSurface extends PlatformViewSurface {
11881189

11891190
/// A callback used to notify the size of the platform view placeholder.
11901191
/// This size is the initial size of the platform view.
1191-
typedef _OnLayoutCallback = void Function(Size size);
1192+
typedef _OnLayoutCallback = void Function(Size size, Offset position);
11921193

11931194
/// A [RenderBox] that notifies its size to the owner after a layout.
11941195
class _PlatformViewPlaceholderBox extends RenderConstrainedBox {
@@ -1204,7 +1205,10 @@ class _PlatformViewPlaceholderBox extends RenderConstrainedBox {
12041205
@override
12051206
void performLayout() {
12061207
super.performLayout();
1207-
onLayout(size);
1208+
// A call to `localToGlobal` requires waiting for a frame to render first.
1209+
SchedulerBinding.instance.addPostFrameCallback((_) {
1210+
onLayout(size, localToGlobal(Offset.zero));
1211+
});
12081212
}
12091213
}
12101214

packages/flutter/test/services/fake_platform_views.dart

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ class FakeAndroidViewController implements AndroidViewController {
6060

6161
bool _createCalledSuccessfully = false;
6262

63+
Offset? createPosition;
64+
6365
final List<PlatformViewCreatedCallback> _createdCallbacks = <PlatformViewCreatedCallback>[];
6466

6567
/// Events that are dispatched.
@@ -131,12 +133,13 @@ class FakeAndroidViewController implements AndroidViewController {
131133
}
132134

133135
@override
134-
Future<void> create({Size? size}) async {
136+
Future<void> create({Size? size, Offset? position}) async {
135137
assert(!_createCalledSuccessfully);
136138
if (requiresSize && size != null) {
137139
assert(!size.isEmpty);
138140
}
139-
_createCalledSuccessfully = size != null || !requiresSize;
141+
_createCalledSuccessfully = size != null && position != null || !requiresSize;
142+
createPosition = position;
140143
}
141144

142145
@override
@@ -214,6 +217,8 @@ class FakeAndroidPlatformViewsController {
214217
final bool? hybrid = args['hybrid'] as bool?;
215218
final bool? hybridFallback = args['hybridFallback'] as bool?;
216219
final Uint8List? creationParams = args['params'] as Uint8List?;
220+
final double? top = args['top'] as double?;
221+
final double? left = args['left'] as double?;
217222

218223
if (_views.containsKey(id)) {
219224
throw PlatformException(
@@ -239,6 +244,7 @@ class FakeAndroidPlatformViewsController {
239244
hybrid: hybrid,
240245
hybridFallback: hybridFallback,
241246
creationParams: creationParams,
247+
position: left != null && top != null ? Offset(left, top) : null,
242248
);
243249
// Return a hybrid result (null rather than a texture ID) if:
244250
final bool hybridResult =
@@ -538,7 +544,7 @@ class FakeHtmlPlatformViewsController {
538544
@immutable
539545
class FakeAndroidPlatformView {
540546
const FakeAndroidPlatformView(this.id, this.type, this.size, this.layoutDirection,
541-
{this.hybrid, this.hybridFallback, this.creationParams});
547+
{this.hybrid, this.hybridFallback, this.creationParams, this.position});
542548

543549
final int id;
544550
final String type;
@@ -547,6 +553,7 @@ class FakeAndroidPlatformView {
547553
final int layoutDirection;
548554
final bool? hybrid;
549555
final bool? hybridFallback;
556+
final Offset? position;
550557

551558
FakeAndroidPlatformView copyWith({Size? size, int? layoutDirection}) => FakeAndroidPlatformView(
552559
id,
@@ -556,6 +563,7 @@ class FakeAndroidPlatformView {
556563
hybrid: hybrid,
557564
hybridFallback: hybridFallback,
558565
creationParams: creationParams,
566+
position: position,
559567
);
560568

561569
@override
@@ -570,7 +578,8 @@ class FakeAndroidPlatformView {
570578
&& other.size == size
571579
&& other.hybrid == hybrid
572580
&& other.hybridFallback == hybridFallback
573-
&& other.layoutDirection == layoutDirection;
581+
&& other.layoutDirection == layoutDirection
582+
&& other.position == position;
574583
}
575584

576585
@override
@@ -582,13 +591,14 @@ class FakeAndroidPlatformView {
582591
layoutDirection,
583592
hybrid,
584593
hybridFallback,
594+
position,
585595
);
586596

587597
@override
588598
String toString() {
589599
return 'FakeAndroidPlatformView(id: $id, type: $type, size: $size, '
590600
'layoutDirection: $layoutDirection, hybrid: $hybrid, '
591-
'hybridFallback: $hybridFallback, creationParams: $creationParams)';
601+
'hybridFallback: $hybridFallback, creationParams: $creationParams, position: $position)';
592602
}
593603
}
594604

packages/flutter/test/widgets/platform_view_test.dart

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2599,6 +2599,51 @@ void main() {
25992599
},
26002600
);
26012601

2602+
testWidgets('PlatformViewLink includes offset in create call when using texture layer', (WidgetTester tester) async {
2603+
late FakeAndroidViewController controller;
2604+
2605+
final PlatformViewLink platformViewLink = PlatformViewLink(
2606+
viewType: 'webview',
2607+
onCreatePlatformView: (PlatformViewCreationParams params) {
2608+
controller = FakeAndroidViewController(params.id, requiresSize: true);
2609+
controller.create();
2610+
// This test should be simulating one of the texture-based display
2611+
// modes, where `create` is a no-op when not provided a size, and
2612+
// creation is triggered via a later call to setSize, or to `create`
2613+
// with a size.
2614+
expect(controller.awaitingCreation, true);
2615+
return controller;
2616+
},
2617+
surfaceFactory: (BuildContext context, PlatformViewController controller) {
2618+
return PlatformViewSurface(
2619+
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
2620+
controller: controller,
2621+
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
2622+
);
2623+
},
2624+
);
2625+
2626+
TestWidgetsFlutterBinding.instance.window.physicalSizeTestValue = const Size(400, 200);
2627+
TestWidgetsFlutterBinding.instance.window.devicePixelRatioTestValue = 1.0;
2628+
2629+
await tester.pumpWidget(
2630+
Container(
2631+
constraints: const BoxConstraints.expand(),
2632+
alignment: Alignment.center,
2633+
child: SizedBox(
2634+
width: 100,
2635+
height: 50,
2636+
child: platformViewLink,
2637+
),
2638+
)
2639+
);
2640+
2641+
expect(controller.createPosition, const Offset(150, 75));
2642+
2643+
TestWidgetsFlutterBinding.instance.window.clearPhysicalSizeTestValue();
2644+
TestWidgetsFlutterBinding.instance.window.clearDevicePixelRatioTestValue();
2645+
});
2646+
26022647
testWidgets(
26032648
'PlatformViewLink does not double-call create for Android Hybrid Composition',
26042649
(WidgetTester tester) async {
@@ -2616,7 +2661,7 @@ void main() {
26162661
controller = FakeAndroidViewController(params.id);
26172662
controller.create();
26182663
// This test should be simulating Hybrid Composition mode, where
2619-
// `create` takes effect immidately.
2664+
// `create` takes effect immediately.
26202665
expect(controller.awaitingCreation, false);
26212666
return controller;
26222667
},

0 commit comments

Comments
 (0)