Unverified Commit debd5015 authored by amirh's avatar amirh Committed by GitHub

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.
parent 9a4db211
...@@ -48,6 +48,7 @@ export 'src/rendering/list_wheel_viewport.dart'; ...@@ -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';
......
// 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,
));
}
}
// 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'; ...@@ -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';
......
// 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))
])
);
});
}
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