Extract common PlatformView functionality: Painting and Semantics (#36955)
* painting and semantics
* more comments
* fixing ci
* review fixes
* add assert for id
* rename custom layer factory to layer builder
* review updates
* partial review fixes
* some doc updates
* more doc updates
* only expose getter for id in PlatformViewController
* doc updates/removing all the references
* remove extra
* more doc updates
* some doc updates
* more doc fixes
* review fixes
diff --git a/packages/flutter/lib/src/rendering/platform_view.dart b/packages/flutter/lib/src/rendering/platform_view.dart
index 1409015..65b8e6c 100644
--- a/packages/flutter/lib/src/rendering/platform_view.dart
+++ b/packages/flutter/lib/src/rendering/platform_view.dart
@@ -725,3 +725,69 @@
bool isSinglePointerAction(PointerEvent event) =>
!(event is PointerDownEvent) && !(event is PointerUpEvent);
}
+
+/// A render object for embedding a platform view.
+///
+/// [PlatformViewRenderBox] presents a platform view by adding a [PlatformViewLayer] layer, integrates it with the gesture arenas system
+/// and adds relevant semantic nodes to the semantics tree.
+class PlatformViewRenderBox extends RenderBox {
+
+ /// Creating a render object for a [PlatformViewSurface].
+ ///
+ /// The `controller` parameter must not be null.
+ PlatformViewRenderBox({
+ @required PlatformViewController controller,
+
+ }) : assert(controller != null && controller.viewId != null && controller.viewId > -1),
+ _controller = controller;
+
+ /// Sets the [controller] for this render object.
+ ///
+ /// This value must not be null, and setting it to a new value will result in a repaint.
+ set controller(PlatformViewController controller) {
+ assert(controller != null);
+ assert(controller.viewId != null && controller.viewId > -1);
+
+ if ( _controller == controller) {
+ return;
+ }
+ final bool needsSemanticsUpdate = _controller.viewId != controller.viewId;
+ _controller = controller;
+ markNeedsPaint();
+ if (needsSemanticsUpdate) {
+ markNeedsSemanticsUpdate();
+ }
+ }
+
+ PlatformViewController _controller;
+
+ @override
+ bool get sizedByParent => true;
+
+ @override
+ bool get alwaysNeedsCompositing => true;
+
+ @override
+ bool get isRepaintBoundary => true;
+
+ @override
+ void performResize() {
+ size = constraints.biggest;
+ }
+
+ @override
+ void paint(PaintingContext context, Offset offset) {
+ assert(_controller.viewId != null);
+ context.addLayer(PlatformViewLayer(
+ rect: offset & size,
+ viewId: _controller.viewId));
+ }
+
+ @override
+ void describeSemanticsConfiguration (SemanticsConfiguration config) {
+ super.describeSemanticsConfiguration(config);
+ assert(_controller.viewId != null);
+ config.isSemanticBoundary = true;
+ config.platformViewId = _controller.viewId;
+ }
+}
diff --git a/packages/flutter/lib/src/services/platform_views.dart b/packages/flutter/lib/src/services/platform_views.dart
index 790fa5c..28e4ead 100644
--- a/packages/flutter/lib/src/services/platform_views.dart
+++ b/packages/flutter/lib/src/services/platform_views.dart
@@ -713,3 +713,16 @@
await SystemChannels.platform_views.invokeMethod<void>('dispose', id);
}
}
+
+/// An interface for a controlling a single platform view.
+///
+/// Used by [PlatformViewSurface] to interface with the platform view it embeds.
+abstract class PlatformViewController {
+
+ /// The viewId associated with this controller.
+ ///
+ /// The viewId should always be unique and non-negative. And it must not be null.
+ ///
+ /// See also [PlatformViewRegistry] which is a helper for managing platform view ids.
+ int get viewId;
+}
diff --git a/packages/flutter/lib/src/widgets/platform_view.dart b/packages/flutter/lib/src/widgets/platform_view.dart
index 463d572..d727fa1 100644
--- a/packages/flutter/lib/src/widgets/platform_view.dart
+++ b/packages/flutter/lib/src/widgets/platform_view.dart
@@ -580,3 +580,45 @@
renderObject.updateGestureRecognizers(gestureRecognizers);
}
}
+
+/// Integrates a platform 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 (e.g on Android platform views are composited using a [TextureLayer]).
+/// Custom Flutter embedders can support [PlatformViewLayer]s by implementing a SystemCompositor.
+///
+/// The widget fills all available space, the parent of this object must provide bounded layout
+/// constraints.
+///
+/// If the associated platform view is not created the [PlatformViewSurface] does not paint any contents.
+///
+/// See also:
+/// * [AndroidView] which embeds an Android platform view in the widget hierarchy.
+/// * [UIKitView] which embeds an iOS platform view in the widget hierarchy.
+// TODO(amirh): Link to the embedder's system compositor documentation once available.
+class PlatformViewSurface extends LeafRenderObjectWidget {
+
+ /// Construct a `PlatformViewSurface`.
+ ///
+ /// The [controller] must not be null.
+ const PlatformViewSurface({
+ @required this.controller,
+ }) : assert(controller != null);
+
+ /// The controller for the platform view integrated by this [PlatformViewSurface].
+ ///
+ /// [PlatformViewController] is used for dispatching touch events to the platform view.
+ /// [PlatformViewController.viewId] identifies the platform view whose contents are painted by this widget.
+ final PlatformViewController controller;
+
+ @override
+ RenderObject createRenderObject(BuildContext context) {
+ return PlatformViewRenderBox(controller: controller);
+ }
+
+ @override
+ void updateRenderObject(BuildContext context, PlatformViewRenderBox renderObject) {
+ renderObject
+ ..controller = controller;
+ }
+}
diff --git a/packages/flutter/test/rendering/platform_view_test.dart b/packages/flutter/test/rendering/platform_view_test.dart
new file mode 100644
index 0000000..71110c9
--- /dev/null
+++ b/packages/flutter/test/rendering/platform_view_test.dart
@@ -0,0 +1,60 @@
+// Copyright 2019 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/rendering.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter/material.dart';
+import '../services/fake_platform_views.dart';
+import 'rendering_tester.dart';
+
+void main() {
+
+ group('PlatformViewRenderBox', () {
+ FakePlatformViewController fakePlatformViewController;
+ PlatformViewRenderBox platformViewRenderBox;
+ setUp((){
+ fakePlatformViewController = FakePlatformViewController(0);
+ platformViewRenderBox = PlatformViewRenderBox(controller: fakePlatformViewController);
+ });
+
+ test('layout should size to max constraint', () {
+ layout(platformViewRenderBox);
+ platformViewRenderBox.layout(const BoxConstraints(minWidth: 50, minHeight: 50, maxWidth: 100, maxHeight: 100));
+ expect(platformViewRenderBox.size, const Size(100, 100));
+ });
+
+ test('send semantics update if id is changed', (){
+ final RenderObject tree = RenderConstrainedBox(
+ additionalConstraints: const BoxConstraints.tightFor(height: 20.0, width: 20.0),
+ child: platformViewRenderBox,
+ );
+ int semanticsUpdateCount = 0;
+ final SemanticsHandle semanticsHandle = renderer.pipelineOwner.ensureSemantics(
+ listener: () {
+ ++semanticsUpdateCount;
+ }
+ );
+ layout(tree, phase: EnginePhase.flushSemantics);
+ // Initial semantics update
+ expect(semanticsUpdateCount, 1);
+
+ semanticsUpdateCount = 0;
+
+ // Request semantics update even though nothing changed.
+ platformViewRenderBox.markNeedsSemanticsUpdate();
+ pumpFrame(phase: EnginePhase.flushSemantics);
+ expect(semanticsUpdateCount, 0);
+
+ semanticsUpdateCount = 0;
+
+ final FakePlatformViewController updatedFakePlatformViewController = FakePlatformViewController(10);
+ platformViewRenderBox.controller = updatedFakePlatformViewController;
+ pumpFrame(phase: EnginePhase.flushSemantics);
+ // Update id should update the semantics.
+ expect(semanticsUpdateCount, 1);
+
+ semanticsHandle.dispose();
+ });
+ });
+}
diff --git a/packages/flutter/test/services/fake_platform_views.dart b/packages/flutter/test/services/fake_platform_views.dart
index bc49eae..9e780e9 100644
--- a/packages/flutter/test/services/fake_platform_views.dart
+++ b/packages/flutter/test/services/fake_platform_views.dart
@@ -9,6 +9,19 @@
import 'package:flutter/painting.dart';
import 'package:flutter/services.dart';
+/// Used in internal testing.
+class FakePlatformViewController extends PlatformViewController {
+
+ FakePlatformViewController(int id) {
+ _id = id;
+ }
+
+ int _id;
+
+ @override
+ int get viewId => _id;
+}
+
class FakeAndroidPlatformViewsController {
FakeAndroidPlatformViewsController() {
SystemChannels.platform_views.setMockMethodCallHandler(_onMethodCall);
diff --git a/packages/flutter/test/widgets/platform_view_test.dart b/packages/flutter/test/widgets/platform_view_test.dart
index cadfa7f..0c141e5 100644
--- a/packages/flutter/test/widgets/platform_view_test.dart
+++ b/packages/flutter/test/widgets/platform_view_test.dart
@@ -1667,4 +1667,21 @@
handle.dispose();
});
});
+
+ group('Common PlatformView', () {
+ FakePlatformViewController controller;
+
+ setUp((){
+ controller = FakePlatformViewController(0);
+ });
+
+ testWidgets('PlatformViewSurface should create platform view layer', (WidgetTester tester) async {
+ final PlatformViewSurface surface = PlatformViewSurface(controller: controller);
+ await tester.pumpWidget(surface);
+ final PlatformViewLayer layer = tester.layers.firstWhere((Layer layer){
+ return layer is PlatformViewLayer;
+ });
+ expect(layer, isNotNull);
+ });
+ });
}