Introduce an AndroidView widget and a RenderAndroidView render object. (#19565)
RenderAndroidView is responsible for sizing and displaying an embedded Android view. AndroidView is responsible for creating and disposing the Android view and is using RenderAndroidView to display it.
This commit is contained in:
parent
9a4db21129
commit
debd50158e
@ -48,6 +48,7 @@ export 'src/rendering/list_wheel_viewport.dart';
|
|||||||
export 'src/rendering/object.dart';
|
export 'src/rendering/object.dart';
|
||||||
export 'src/rendering/paragraph.dart';
|
export 'src/rendering/paragraph.dart';
|
||||||
export 'src/rendering/performance_overlay.dart';
|
export 'src/rendering/performance_overlay.dart';
|
||||||
|
export 'src/rendering/platform_view.dart';
|
||||||
export 'src/rendering/proxy_box.dart';
|
export 'src/rendering/proxy_box.dart';
|
||||||
export 'src/rendering/rotated_box.dart';
|
export 'src/rendering/rotated_box.dart';
|
||||||
export 'src/rendering/shifted_box.dart';
|
export 'src/rendering/shifted_box.dart';
|
||||||
|
99
packages/flutter/lib/src/rendering/platform_view.dart
Normal file
99
packages/flutter/lib/src/rendering/platform_view.dart
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
// Copyright 2018 The Chromium 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 'dart:async';
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
import 'box.dart';
|
||||||
|
import 'layer.dart';
|
||||||
|
import 'object.dart';
|
||||||
|
|
||||||
|
|
||||||
|
enum _PlatformViewState {
|
||||||
|
uninitialized,
|
||||||
|
resizing,
|
||||||
|
ready,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A render object for an Android view.
|
||||||
|
///
|
||||||
|
/// [RenderAndroidView] is responsible for sizing and displaying an Android [View](https://developer.android.com/reference/android/view/View).
|
||||||
|
///
|
||||||
|
/// The render object's layout behavior is to fill all available space, the parent of this object must
|
||||||
|
/// provide bounded layout constraints
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
/// * [AndroidView] which is a widget that is used to show an Android view.
|
||||||
|
/// * [PlatformViewsService] which is a service for controlling platform views.
|
||||||
|
class RenderAndroidView extends RenderBox {
|
||||||
|
|
||||||
|
/// Creates a render object for an Android view.
|
||||||
|
RenderAndroidView({
|
||||||
|
@required AndroidViewController viewController,
|
||||||
|
}) : assert(viewController != null),
|
||||||
|
_viewController = viewController;
|
||||||
|
|
||||||
|
_PlatformViewState _state = _PlatformViewState.uninitialized;
|
||||||
|
|
||||||
|
/// The Android view controller for the Android view associated with this render object.
|
||||||
|
AndroidViewController get viewcontroller => _viewController;
|
||||||
|
AndroidViewController _viewController;
|
||||||
|
/// Sets a new Android view controller.
|
||||||
|
///
|
||||||
|
/// `viewController` must not be null.
|
||||||
|
set viewController(AndroidViewController viewController) {
|
||||||
|
assert(_viewController != null);
|
||||||
|
_viewController = viewController;
|
||||||
|
_sizePlatformView();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get sizedByParent => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get alwaysNeedsCompositing => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get isRepaintBoundary => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void performResize() {
|
||||||
|
size = constraints.biggest;
|
||||||
|
_sizePlatformView();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Null> _sizePlatformView() async {
|
||||||
|
if (_state == _PlatformViewState.resizing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_state = _PlatformViewState.resizing;
|
||||||
|
|
||||||
|
Size targetSize;
|
||||||
|
do {
|
||||||
|
targetSize = size;
|
||||||
|
await _viewController.setSize(size);
|
||||||
|
// We've resized the platform view to targetSize, but it is possible that
|
||||||
|
// while we were resizing the render object's size was changed again.
|
||||||
|
// In that case we will resize the platform view again.
|
||||||
|
} while (size != targetSize);
|
||||||
|
|
||||||
|
_state = _PlatformViewState.ready;
|
||||||
|
markNeedsPaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(PaintingContext context, Offset offset) {
|
||||||
|
if (_viewController.textureId == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
context.addLayer(new TextureLayer(
|
||||||
|
rect: offset & size,
|
||||||
|
textureId: _viewController.textureId,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
124
packages/flutter/lib/src/widgets/platform_view.dart
Normal file
124
packages/flutter/lib/src/widgets/platform_view.dart
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
// Copyright 2018 The Chromium 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/foundation.dart';
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
import 'framework.dart';
|
||||||
|
|
||||||
|
/// Embeds an Android view in the Widget hierarchy.
|
||||||
|
///
|
||||||
|
/// Embedding Android views is an expensive operation and should be avoided when a Flutter
|
||||||
|
/// equivalent is possible.
|
||||||
|
///
|
||||||
|
/// The embedded Android view is painted just like any other Flutter widget and transformations
|
||||||
|
/// apply to it as well.
|
||||||
|
///
|
||||||
|
/// The widget fill all available space, the parent of this object must provide bounded layout
|
||||||
|
/// constraints.
|
||||||
|
///
|
||||||
|
/// The Android view object is created using a [PlatformViewFactory](/javadoc/io/flutter/plugin/platform/PlatformViewFactory.html).
|
||||||
|
/// Plugins can register platform view factories with [PlatformViewRegistry#registerViewFactory](/javadoc/io/flutter/plugin/platform/PlatformViewRegistry.html#registerViewFactory-java.lang.String-io.flutter.plugin.platform.PlatformViewFactory-).
|
||||||
|
///
|
||||||
|
/// Registration is typically done in the plugin's registerWith method, e.g:
|
||||||
|
///
|
||||||
|
/// ```java
|
||||||
|
/// public static void registerWith(Registrar registrar) {
|
||||||
|
/// registrar.platformViewRegistry().registerViewFactory("webview", new WebViewFactory(registrar.messenger()));
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// The Android view's lifetime is the same as the lifetime of the [State] object for this widget.
|
||||||
|
/// When the [State] is disposed the platform view (and auxiliary resources) are lazily
|
||||||
|
/// released (some resources are immediately released and some by platform garbage collector).
|
||||||
|
/// A stateful widget's state is disposed the the widget is removed from the tree or when it is
|
||||||
|
/// moved within the tree. If the stateful widget has a key and it's only moved relative to its siblings,
|
||||||
|
/// or it has a [GlobalKey] and it's moved within the tree, it will not be disposed.
|
||||||
|
class AndroidView extends StatefulWidget {
|
||||||
|
/// Creates a widget that embeds an Android view.
|
||||||
|
///
|
||||||
|
/// The `viewType` parameter must not be null.
|
||||||
|
const AndroidView({
|
||||||
|
Key key,
|
||||||
|
@required this.viewType,
|
||||||
|
this.onPlatformViewCreated
|
||||||
|
}) : assert(viewType != null),
|
||||||
|
super(key: key);
|
||||||
|
|
||||||
|
/// The unique identifier for Android view type to be embedded by this widget.
|
||||||
|
/// A [PlatformViewFactory](/javadoc/io/flutter/plugin/platform/PlatformViewFactory.html)
|
||||||
|
/// for this type must have been registered.
|
||||||
|
///
|
||||||
|
/// See also: [AndroidView] for an example of registering a platform view factory.
|
||||||
|
final String viewType;
|
||||||
|
|
||||||
|
/// Callback to invoke when after the Android view has been created.
|
||||||
|
///
|
||||||
|
/// May be null.
|
||||||
|
final OnPlatformViewCreated onPlatformViewCreated;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State createState() => new _AndroidViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AndroidViewState extends State<AndroidView> {
|
||||||
|
int _id;
|
||||||
|
AndroidViewController _controller;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return new _AndroidPlatformView(controller: _controller);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_createNewAndroidView();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(AndroidView oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
if (widget.viewType == oldWidget.viewType)
|
||||||
|
return;
|
||||||
|
_controller.dispose();
|
||||||
|
_createNewAndroidView();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_controller.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _createNewAndroidView() {
|
||||||
|
_id = platformViewsRegistry.getNextPlatformViewId();
|
||||||
|
_controller = PlatformViewsService.initAndroidView(
|
||||||
|
id: _id,
|
||||||
|
viewType: widget.viewType,
|
||||||
|
onPlatformViewCreated: widget.onPlatformViewCreated
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AndroidPlatformView extends LeafRenderObjectWidget {
|
||||||
|
const _AndroidPlatformView({
|
||||||
|
Key key,
|
||||||
|
@required this.controller,
|
||||||
|
}) : assert(controller != null),
|
||||||
|
super(key: key);
|
||||||
|
|
||||||
|
final AndroidViewController controller;
|
||||||
|
|
||||||
|
@override
|
||||||
|
RenderObject createRenderObject(BuildContext context) =>
|
||||||
|
new RenderAndroidView(viewController: controller);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void updateRenderObject(BuildContext context, RenderAndroidView renderObject) {
|
||||||
|
renderObject.viewController = controller;
|
||||||
|
}
|
||||||
|
}
|
@ -63,6 +63,7 @@ export 'src/widgets/page_view.dart';
|
|||||||
export 'src/widgets/pages.dart';
|
export 'src/widgets/pages.dart';
|
||||||
export 'src/widgets/performance_overlay.dart';
|
export 'src/widgets/performance_overlay.dart';
|
||||||
export 'src/widgets/placeholder.dart';
|
export 'src/widgets/placeholder.dart';
|
||||||
|
export 'src/widgets/platform_view.dart';
|
||||||
export 'src/widgets/preferred_size.dart';
|
export 'src/widgets/preferred_size.dart';
|
||||||
export 'src/widgets/primary_scroll_controller.dart';
|
export 'src/widgets/primary_scroll_controller.dart';
|
||||||
export 'src/widgets/raw_keyboard_listener.dart';
|
export 'src/widgets/raw_keyboard_listener.dart';
|
||||||
|
163
packages/flutter/test/widgets/platform_view_test.dart
Normal file
163
packages/flutter/test/widgets/platform_view_test.dart
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
// Copyright 2018 The Chromium 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/services.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
import '../services/fake_platform_views.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
|
||||||
|
testWidgets('Create Android view', (WidgetTester tester) async {
|
||||||
|
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
||||||
|
final FakePlatformViewsController viewsController = new FakePlatformViewsController(TargetPlatform.android);
|
||||||
|
viewsController.registerViewType('webview');
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
const Center(
|
||||||
|
child: const SizedBox(
|
||||||
|
width: 200.0,
|
||||||
|
height: 100.0,
|
||||||
|
child: const AndroidView(viewType: 'webview'),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
viewsController.views,
|
||||||
|
unorderedEquals(<FakePlatformView>[
|
||||||
|
new FakePlatformView(currentViewId + 1, 'webview', const Size(200.0, 100.0))
|
||||||
|
])
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Resize Android view', (WidgetTester tester) async {
|
||||||
|
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
||||||
|
final FakePlatformViewsController viewsController = new FakePlatformViewsController(TargetPlatform.android);
|
||||||
|
viewsController.registerViewType('webview');
|
||||||
|
await tester.pumpWidget(
|
||||||
|
const Center(
|
||||||
|
child: const SizedBox(
|
||||||
|
width: 200.0,
|
||||||
|
height: 100.0,
|
||||||
|
child: const AndroidView(viewType: 'webview'),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
const Center(
|
||||||
|
child: const SizedBox(
|
||||||
|
width: 400.0,
|
||||||
|
height: 200.0,
|
||||||
|
child: const AndroidView(viewType: 'webview'),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
viewsController.views,
|
||||||
|
unorderedEquals(<FakePlatformView>[
|
||||||
|
new FakePlatformView(currentViewId + 1, 'webview', const Size(400.0, 200.0))
|
||||||
|
])
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Change Android view type', (WidgetTester tester) async {
|
||||||
|
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
||||||
|
final FakePlatformViewsController viewsController = new FakePlatformViewsController(TargetPlatform.android);
|
||||||
|
viewsController.registerViewType('webview');
|
||||||
|
viewsController.registerViewType('maps');
|
||||||
|
await tester.pumpWidget(
|
||||||
|
const Center(
|
||||||
|
child: const SizedBox(
|
||||||
|
width: 200.0,
|
||||||
|
height: 100.0,
|
||||||
|
child: const AndroidView(viewType: 'webview'),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
const Center(
|
||||||
|
child: const SizedBox(
|
||||||
|
width: 200.0,
|
||||||
|
height: 100.0,
|
||||||
|
child: const AndroidView(viewType: 'maps'),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
viewsController.views,
|
||||||
|
unorderedEquals(<FakePlatformView>[
|
||||||
|
new FakePlatformView(currentViewId + 2, 'maps', const Size(200.0, 100.0))
|
||||||
|
])
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Dispose Android view', (WidgetTester tester) async {
|
||||||
|
final FakePlatformViewsController viewsController = new FakePlatformViewsController(TargetPlatform.android);
|
||||||
|
viewsController.registerViewType('webview');
|
||||||
|
await tester.pumpWidget(
|
||||||
|
const Center(
|
||||||
|
child: const SizedBox(
|
||||||
|
width: 200.0,
|
||||||
|
height: 100.0,
|
||||||
|
child: const AndroidView(viewType: 'webview'),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
const Center(
|
||||||
|
child: const SizedBox(
|
||||||
|
width: 200.0,
|
||||||
|
height: 100.0,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
viewsController.views,
|
||||||
|
isEmpty,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Android view survives widget tree change', (WidgetTester tester) async {
|
||||||
|
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
||||||
|
final FakePlatformViewsController viewsController = new FakePlatformViewsController(TargetPlatform.android);
|
||||||
|
viewsController.registerViewType('webview');
|
||||||
|
final GlobalKey key = new GlobalKey();
|
||||||
|
await tester.pumpWidget(
|
||||||
|
new Center(
|
||||||
|
child: new SizedBox(
|
||||||
|
width: 200.0,
|
||||||
|
height: 100.0,
|
||||||
|
child: new AndroidView(viewType: 'webview', key: key),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
new Center(
|
||||||
|
child: new Container(
|
||||||
|
child: new SizedBox(
|
||||||
|
width: 200.0,
|
||||||
|
height: 100.0,
|
||||||
|
child: new AndroidView(viewType: 'webview', key: key),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
viewsController.views,
|
||||||
|
unorderedEquals(<FakePlatformView>[
|
||||||
|
new FakePlatformView(currentViewId + 1, 'webview', const Size(200.0, 100.0))
|
||||||
|
])
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user