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
This commit is contained in:
parent
99fe0d078b
commit
9553f8daa7
@ -725,3 +725,69 @@ class _MotionEventsDispatcher {
|
|||||||
bool isSinglePointerAction(PointerEvent event) =>
|
bool isSinglePointerAction(PointerEvent event) =>
|
||||||
!(event is PointerDownEvent) && !(event is PointerUpEvent);
|
!(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);
|
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);
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
60
packages/flutter/test/rendering/platform_view_test.dart
Normal file
60
packages/flutter/test/rendering/platform_view_test.dart
Normal file
@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -9,6 +9,19 @@ import 'package:collection/collection.dart';
|
|||||||
import 'package:flutter/painting.dart';
|
import 'package:flutter/painting.dart';
|
||||||
import 'package:flutter/services.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 {
|
class FakeAndroidPlatformViewsController {
|
||||||
FakeAndroidPlatformViewsController() {
|
FakeAndroidPlatformViewsController() {
|
||||||
SystemChannels.platform_views.setMockMethodCallHandler(_onMethodCall);
|
SystemChannels.platform_views.setMockMethodCallHandler(_onMethodCall);
|
||||||
|
@ -1667,4 +1667,21 @@ void main() {
|
|||||||
handle.dispose();
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user