Unverified Commit 9553f8da authored by Chris Yang's avatar Chris Yang Committed by GitHub

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
parent 99fe0d07
......@@ -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;
}
}
......@@ -713,3 +713,16 @@ class UiKitViewController {
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;
}
......@@ -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;
}
}
// 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();
});
});
}
......@@ -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);
......
......@@ -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);
});
});
}
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