From 9553f8daa7c5d384c61e289ebd55bf614acc3410 Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Fri, 2 Aug 2019 10:07:59 -0700 Subject: [PATCH] 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 --- .../lib/src/rendering/platform_view.dart | 66 +++++++++++++++++++ .../lib/src/services/platform_views.dart | 13 ++++ .../lib/src/widgets/platform_view.dart | 42 ++++++++++++ .../test/rendering/platform_view_test.dart | 60 +++++++++++++++++ .../test/services/fake_platform_views.dart | 13 ++++ .../test/widgets/platform_view_test.dart | 17 +++++ 6 files changed, 211 insertions(+) create mode 100644 packages/flutter/test/rendering/platform_view_test.dart diff --git a/packages/flutter/lib/src/rendering/platform_view.dart b/packages/flutter/lib/src/rendering/platform_view.dart index 14090150a4..65b8e6cbea 100644 --- a/packages/flutter/lib/src/rendering/platform_view.dart +++ b/packages/flutter/lib/src/rendering/platform_view.dart @@ -725,3 +725,69 @@ class _MotionEventsDispatcher { 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 790fa5c545..28e4ead6dd 100644 --- a/packages/flutter/lib/src/services/platform_views.dart +++ b/packages/flutter/lib/src/services/platform_views.dart @@ -713,3 +713,16 @@ class UiKitViewController { await SystemChannels.platform_views.invokeMethod('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 463d572332..d727fa1212 100644 --- a/packages/flutter/lib/src/widgets/platform_view.dart +++ b/packages/flutter/lib/src/widgets/platform_view.dart @@ -580,3 +580,45 @@ class _UiKitPlatformView extends LeafRenderObjectWidget { 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 0000000000..71110c9058 --- /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 bc49eae142..9e780e9b46 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:collection/collection.dart'; 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 cadfa7f638..0c141e52b0 100644 --- a/packages/flutter/test/widgets/platform_view_test.dart +++ b/packages/flutter/test/widgets/platform_view_test.dart @@ -1667,4 +1667,21 @@ void main() { 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); + }); + }); }