| // 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/gestures.dart'; |
| import 'package:flutter/rendering.dart'; |
| import 'package:flutter/services.dart'; |
| |
| import 'basic.dart'; |
| import 'debug.dart'; |
| import 'framework.dart'; |
| |
| /// Embeds an Android view in the Widget hierarchy. |
| /// |
| /// Requires Android API level 20 or greater. |
| /// |
| /// 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. |
| /// |
| /// AndroidView participates in Flutter's [GestureArena]s, and dispatches touch events to the |
| /// Android view iff it won the arena. Specific gestures that should be dispatched to the Android |
| /// view can be specified in [AndroidView.gestureRecognizers]. If |
| /// [AndroidView.gestureRecognizers] is empty, the gesture will be dispatched to the Android |
| /// view iff it was not claimed by any other gesture recognizer. |
| /// |
| /// 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", 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 when 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`, `hitTestBehavior`, and `gestureRecognizers` parameters must not be null. |
| /// If `creationParams` is not null then `creationParamsCodec` must not be null. |
| AndroidView({ // ignore: prefer_const_constructors_in_immutables |
| // TODO(aam): Remove lint ignore above once dartbug.com/34297 is fixed |
| Key key, |
| @required this.viewType, |
| this.onPlatformViewCreated, |
| this.hitTestBehavior = PlatformViewHitTestBehavior.opaque, |
| this.layoutDirection, |
| this.gestureRecognizers = const <OneSequenceGestureRecognizer> [], |
| this.creationParams, |
| this.creationParamsCodec |
| }) : assert(viewType != null), |
| assert(hitTestBehavior != null), |
| assert(gestureRecognizers != null), |
| assert(creationParams == null || creationParamsCodec != 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 after the Android view has been created. |
| /// |
| /// May be null. |
| final PlatformViewCreatedCallback onPlatformViewCreated; |
| |
| /// How this widget should behave during hit testing. |
| /// |
| /// This defaults to [PlatformViewHitTestBehavior.opaque]. |
| final PlatformViewHitTestBehavior hitTestBehavior; |
| |
| /// The text direction to use for the embedded view. |
| /// |
| /// If this is null, the ambient [Directionality] is used instead. |
| final TextDirection layoutDirection; |
| |
| /// Which gestures should be forwarded to the Android view. |
| /// |
| /// The gesture recognizers on this list participate in the gesture arena for each pointer |
| /// that was put down on the widget. If any of the recognizers on this list wins the |
| /// gesture arena, the entire pointer event sequence starting from the pointer down event |
| /// will be dispatched to the Android view. |
| /// |
| /// For example, with the following setup vertical drags will not be dispatched to the Android |
| /// view as the vertical drag gesture is claimed by the parent [GestureDetector]. |
| /// ```dart |
| /// GestureDetector( |
| /// onVerticalDragStart: (DragStartDetails d) {}, |
| /// child: AndroidView( |
| /// viewType: 'webview', |
| /// gestureRecognizers: <OneSequenceGestureRecognizer>[], |
| /// ), |
| /// ) |
| /// ``` |
| /// To get the [AndroidView] to claim the vertical drag gestures we can pass a vertical drag |
| /// gesture recognizer in [gestureRecognizers] e.g: |
| /// ```dart |
| /// GestureDetector( |
| /// onVerticalDragStart: (DragStartDetails d) {}, |
| /// child: SizedBox( |
| /// width: 200.0, |
| /// height: 100.0, |
| /// child: AndroidView( |
| /// viewType: 'webview', |
| /// gestureRecognizers: <OneSequenceGestureRecognizer>[ new VerticalDragGestureRecognizer() ], |
| /// ), |
| /// ), |
| /// ) |
| /// ``` |
| /// |
| /// An [AndroidView] can be configured to consume all pointers that were put down in its bounds |
| /// by passing an [EagerGestureRecognizer] in [gestureRecognizers]. [EagerGestureRecognizer] is a |
| /// special gesture recognizer that immediately claims the gesture after a pointer down event. |
| // We use OneSequenceGestureRecognizers as they support gesture arena teams. |
| // TODO(amirh): get a list of GestureRecognizers here. |
| // https://github.com/flutter/flutter/issues/20953 |
| final List<OneSequenceGestureRecognizer> gestureRecognizers; |
| |
| /// Passed as the args argument of [PlatformViewFactory#create](/javadoc/io/flutter/plugin/platform/PlatformViewFactory.html#create-android.content.Context-int-java.lang.Object-) |
| /// |
| /// This can be used by plugins to pass constructor parameters to the embedded Android view. |
| final dynamic creationParams; |
| |
| /// The codec used to encode `creationParams` before sending it to the |
| /// platform side. It should match the codec passed to the constructor of [PlatformViewFactory](/javadoc/io/flutter/plugin/platform/PlatformViewFactory.html#PlatformViewFactory-io.flutter.plugin.common.MessageCodec-). |
| /// |
| /// This is typically one of: [StandardMessageCodec], [JSONMessageCodec], [StringCodec], or [BinaryCodec]. |
| /// |
| /// This must not be null if [creationParams] is not null. |
| final MessageCodec<dynamic> creationParamsCodec; |
| |
| @override |
| State createState() => _AndroidViewState(); |
| } |
| |
| class _AndroidViewState extends State<AndroidView> { |
| int _id; |
| AndroidViewController _controller; |
| TextDirection _layoutDirection; |
| bool _initialized = false; |
| |
| @override |
| Widget build(BuildContext context) { |
| return _AndroidPlatformView( |
| controller: _controller, |
| hitTestBehavior: widget.hitTestBehavior, |
| gestureRecognizers: widget.gestureRecognizers, |
| ); |
| } |
| |
| void _initializeOnce() { |
| if (_initialized) { |
| return; |
| } |
| _initialized = true; |
| _layoutDirection = _findLayoutDirection(); |
| _createNewAndroidView(); |
| } |
| |
| @override |
| void didChangeDependencies() { |
| super.didChangeDependencies(); |
| _initializeOnce(); |
| |
| final TextDirection newLayoutDirection = _findLayoutDirection(); |
| final bool didChangeLayoutDirection = _layoutDirection != newLayoutDirection; |
| _layoutDirection = newLayoutDirection; |
| |
| if (didChangeLayoutDirection) { |
| // The native view will update asynchronously, in the meantime we don't want |
| // to block the framework. (so this is intentionally not awaiting). |
| _controller.setLayoutDirection(_layoutDirection); |
| } |
| } |
| |
| @override |
| void didUpdateWidget(AndroidView oldWidget) { |
| super.didUpdateWidget(oldWidget); |
| |
| final TextDirection newLayoutDirection = _findLayoutDirection(); |
| final bool didChangeLayoutDirection = _layoutDirection != newLayoutDirection; |
| _layoutDirection = newLayoutDirection; |
| |
| if (widget.viewType != oldWidget.viewType) { |
| _controller.dispose(); |
| _createNewAndroidView(); |
| return; |
| } |
| |
| if (didChangeLayoutDirection) { |
| _controller.setLayoutDirection(_layoutDirection); |
| } |
| } |
| |
| TextDirection _findLayoutDirection() { |
| assert(widget.layoutDirection != null || debugCheckHasDirectionality(context)); |
| return widget.layoutDirection ?? Directionality.of(context); |
| } |
| |
| @override |
| void dispose() { |
| _controller.dispose(); |
| super.dispose(); |
| } |
| |
| void _createNewAndroidView() { |
| _id = platformViewsRegistry.getNextPlatformViewId(); |
| _controller = PlatformViewsService.initAndroidView( |
| id: _id, |
| viewType: widget.viewType, |
| layoutDirection: _layoutDirection, |
| onPlatformViewCreated: widget.onPlatformViewCreated, |
| creationParams: widget.creationParams, |
| creationParamsCodec: widget.creationParamsCodec, |
| ); |
| } |
| } |
| |
| class _AndroidPlatformView extends LeafRenderObjectWidget { |
| const _AndroidPlatformView({ |
| Key key, |
| @required this.controller, |
| @required this.hitTestBehavior, |
| @required this.gestureRecognizers, |
| }) : assert(controller != null), |
| assert(hitTestBehavior != null), |
| assert(gestureRecognizers != null), |
| super(key: key); |
| |
| final AndroidViewController controller; |
| final PlatformViewHitTestBehavior hitTestBehavior; |
| final List<OneSequenceGestureRecognizer> gestureRecognizers; |
| |
| @override |
| RenderObject createRenderObject(BuildContext context) => |
| RenderAndroidView( |
| viewController: controller, |
| hitTestBehavior: hitTestBehavior, |
| gestureRecognizers: gestureRecognizers, |
| ); |
| |
| @override |
| void updateRenderObject(BuildContext context, RenderAndroidView renderObject) { |
| renderObject.viewController = controller; |
| renderObject.hitTestBehavior = hitTestBehavior; |
| renderObject.gestureRecognizers = gestureRecognizers; |
| } |
| } |