1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/// @docImport 'gesture_detector.dart';
6library;
7
8import 'package:flutter/foundation.dart';
9import 'package:flutter/gestures.dart';
10import 'package:flutter/rendering.dart';
11import 'package:flutter/scheduler.dart';
12import 'package:flutter/services.dart';
13
14import '_html_element_view_io.dart' if (dart.library.js_util) '_html_element_view_web.dart';
15import 'basic.dart';
16import 'debug.dart';
17import 'focus_manager.dart';
18import 'focus_scope.dart';
19import 'framework.dart';
20
21// Examples can assume:
22// PlatformViewController createFooWebView(PlatformViewCreationParams params) { return (null as dynamic) as PlatformViewController; }
23// Set> gestureRecognizers = >{};
24// late PlatformViewController _controller;
25// void myOnElementCreated(Object element) {}
26// void myOnPlatformViewCreated(int viewId) {}
27
28/// Embeds an Android view in the Widget hierarchy.
29///
30/// Requires Android API level 23 or greater.
31///
32/// Embedding Android views is an expensive operation and should be avoided when a Flutter
33/// equivalent is possible.
34///
35/// The embedded Android view is painted just like any other Flutter widget and transformations
36/// apply to it as well.
37///
38/// {@template flutter.widgets.AndroidView.layout}
39/// The widget fills all available space, the parent of this object must provide bounded layout
40/// constraints.
41/// {@endtemplate}
42///
43/// {@template flutter.widgets.AndroidView.gestures}
44/// The widget participates in Flutter's gesture arenas, and dispatches touch events to the
45/// platform view iff it won the arena. Specific gestures that should be dispatched to the platform
46/// view can be specified in the `gestureRecognizers` constructor parameter. If
47/// the set of gesture recognizers is empty, a gesture will be dispatched to the platform
48/// view iff it was not claimed by any other gesture recognizer.
49/// {@endtemplate}
50///
51/// The Android view object is created using a [PlatformViewFactory](/javadoc/io/flutter/plugin/platform/PlatformViewFactory.html).
52/// 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-).
53///
54/// Registration is typically done in the plugin's registerWith method, e.g:
55///
56/// ```java
57/// public static void registerWith(Registrar registrar) {
58/// registrar.platformViewRegistry().registerViewFactory("webview", WebViewFactory(registrar.messenger()));
59/// }
60/// ```
61///
62/// {@template flutter.widgets.AndroidView.lifetime}
63/// The platform view's lifetime is the same as the lifetime of the [State] object for this widget.
64/// When the [State] is disposed the platform view (and auxiliary resources) are lazily
65/// released (some resources are immediately released and some by platform garbage collector).
66/// A stateful widget's state is disposed when the widget is removed from the tree or when it is
67/// moved within the tree. If the stateful widget has a key and it's only moved relative to its siblings,
68/// or it has a [GlobalKey] and it's moved within the tree, it will not be disposed.
69/// {@endtemplate}
70class AndroidView extends StatefulWidget {
71 /// Creates a widget that embeds an Android view.
72 ///
73 /// {@template flutter.widgets.AndroidView.constructorArgs}
74 /// If `creationParams` is not null then `creationParamsCodec` must not be null.
75 /// {@endtemplate}
76 const AndroidView({
77 super.key,
78 required this.viewType,
79 this.onPlatformViewCreated,
80 this.hitTestBehavior = PlatformViewHitTestBehavior.opaque,
81 this.layoutDirection,
82 this.gestureRecognizers,
83 this.creationParams,
84 this.creationParamsCodec,
85 this.clipBehavior = Clip.hardEdge,
86 }) : assert(creationParams == null || creationParamsCodec != null);
87
88 /// The unique identifier for Android view type to be embedded by this widget.
89 ///
90 /// A [PlatformViewFactory](/javadoc/io/flutter/plugin/platform/PlatformViewFactory.html)
91 /// for this type must have been registered.
92 ///
93 /// See also:
94 ///
95 /// * [AndroidView] for an example of registering a platform view factory.
96 final String viewType;
97
98 /// {@template flutter.widgets.AndroidView.onPlatformViewCreated}
99 /// Callback to invoke after the platform view has been created.
100 ///
101 /// May be null.
102 /// {@endtemplate}
103 final PlatformViewCreatedCallback? onPlatformViewCreated;
104
105 /// {@template flutter.widgets.AndroidView.hitTestBehavior}
106 /// How this widget should behave during hit testing.
107 ///
108 /// This defaults to [PlatformViewHitTestBehavior.opaque].
109 /// {@endtemplate}
110 final PlatformViewHitTestBehavior hitTestBehavior;
111
112 /// {@template flutter.widgets.AndroidView.layoutDirection}
113 /// The text direction to use for the embedded view.
114 ///
115 /// If this is null, the ambient [Directionality] is used instead.
116 /// {@endtemplate}
117 final TextDirection? layoutDirection;
118
119 /// Which gestures should be forwarded to the Android view.
120 ///
121 /// {@template flutter.widgets.AndroidView.gestureRecognizers.descHead}
122 /// The gesture recognizers built by factories in this set participate in the gesture arena for
123 /// each pointer that was put down on the widget. If any of these recognizers win the
124 /// gesture arena, the entire pointer event sequence starting from the pointer down event
125 /// will be dispatched to the platform view.
126 ///
127 /// When null, an empty set of gesture recognizer factories is used, in which case a pointer event sequence
128 /// will only be dispatched to the platform view if no other member of the arena claimed it.
129 /// {@endtemplate}
130 ///
131 /// For example, with the following setup vertical drags will not be dispatched to the Android
132 /// view as the vertical drag gesture is claimed by the parent [GestureDetector].
133 ///
134 /// ```dart
135 /// GestureDetector(
136 /// onVerticalDragStart: (DragStartDetails d) {},
137 /// child: const AndroidView(
138 /// viewType: 'webview',
139 /// ),
140 /// )
141 /// ```
142 ///
143 /// To get the [AndroidView] to claim the vertical drag gestures we can pass a vertical drag
144 /// gesture recognizer factory in [gestureRecognizers] e.g:
145 ///
146 /// ```dart
147 /// GestureDetector(
148 /// onVerticalDragStart: (DragStartDetails details) {},
149 /// child: SizedBox(
150 /// width: 200.0,
151 /// height: 100.0,
152 /// child: AndroidView(
153 /// viewType: 'webview',
154 /// gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{
155 /// Factory<OneSequenceGestureRecognizer>(
156 /// () => EagerGestureRecognizer(),
157 /// ),
158 /// },
159 /// ),
160 /// ),
161 /// )
162 /// ```
163 ///
164 /// {@template flutter.widgets.AndroidView.gestureRecognizers.descFoot}
165 /// A platform view can be configured to consume all pointers that were put
166 /// down in its bounds by passing a factory for an [EagerGestureRecognizer] in
167 /// [gestureRecognizers]. [EagerGestureRecognizer] is a special gesture
168 /// recognizer that immediately claims the gesture after a pointer down event.
169 ///
170 /// The [gestureRecognizers] property must not contain more than one factory
171 /// with the same [Factory.type].
172 ///
173 /// Changing [gestureRecognizers] results in rejection of any active gesture
174 /// arenas (if the platform view is actively participating in an arena).
175 /// {@endtemplate}
176 // We use OneSequenceGestureRecognizers as they support gesture arena teams.
177 // TODO(amirh): get a list of GestureRecognizers here.
178 // https://github.com/flutter/flutter/issues/20953
179 final Set<Factory<OneSequenceGestureRecognizer>>? gestureRecognizers;
180
181 /// Passed as the args argument of [PlatformViewFactory#create](/javadoc/io/flutter/plugin/platform/PlatformViewFactory.html#create-android.content.Context-int-java.lang.Object-)
182 ///
183 /// This can be used by plugins to pass constructor parameters to the embedded Android view.
184 final dynamic creationParams;
185
186 /// The codec used to encode `creationParams` before sending it to the
187 /// 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-).
188 ///
189 /// This is typically one of: [StandardMessageCodec], [JSONMessageCodec], [StringCodec], or [BinaryCodec].
190 ///
191 /// This must not be null if [creationParams] is not null.
192 final MessageCodec<dynamic>? creationParamsCodec;
193
194 /// {@macro flutter.material.Material.clipBehavior}
195 ///
196 /// Defaults to [Clip.hardEdge].
197 final Clip clipBehavior;
198
199 @override
200 State<AndroidView> createState() => _AndroidViewState();
201}
202
203/// Common superclass for iOS and macOS platform views.
204///
205/// Platform views are used to embed native views in the widget hierarchy, with
206/// support for transforms, clips, and opacity similar to any other Flutter widget.
207abstract class _DarwinView extends StatefulWidget {
208 /// Creates a widget that embeds a platform view.
209 ///
210 /// {@macro flutter.widgets.AndroidView.constructorArgs}
211 const _DarwinView({
212 super.key,
213 required this.viewType,
214 this.onPlatformViewCreated,
215 this.hitTestBehavior = PlatformViewHitTestBehavior.opaque,
216 this.layoutDirection,
217 this.creationParams,
218 this.creationParamsCodec,
219 this.gestureRecognizers,
220 }) : assert(creationParams == null || creationParamsCodec != null);
221
222 // TODO(amirh): reference the iOS API doc once available.
223 /// The unique identifier for iOS view type to be embedded by this widget.
224 ///
225 /// A PlatformViewFactory for this type must have been registered.
226 final String viewType;
227
228 /// {@macro flutter.widgets.AndroidView.onPlatformViewCreated}
229 final PlatformViewCreatedCallback? onPlatformViewCreated;
230
231 /// {@macro flutter.widgets.AndroidView.hitTestBehavior}
232 final PlatformViewHitTestBehavior hitTestBehavior;
233
234 /// {@macro flutter.widgets.AndroidView.layoutDirection}
235 final TextDirection? layoutDirection;
236
237 /// Passed as the `arguments` argument of [-\[FlutterPlatformViewFactory createWithFrame:viewIdentifier:arguments:\]](/ios-embedder/protocol_flutter_platform_view_factory-p.html#a4e3c4390cd6ebd982390635e9bca4edc)
238 ///
239 /// This can be used by plugins to pass constructor parameters to the embedded iOS view.
240 final dynamic creationParams;
241
242 /// The codec used to encode `creationParams` before sending it to the
243 /// platform side. It should match the codec returned by [-\[FlutterPlatformViewFactory createArgsCodec:\]](/ios-embedder/protocol_flutter_platform_view_factory-p.html#a32c3c067cb45a83dfa720c74a0d5c93c)
244 ///
245 /// This is typically one of: [StandardMessageCodec], [JSONMessageCodec], [StringCodec], or [BinaryCodec].
246 ///
247 /// This must not be null if [creationParams] is not null.
248 final MessageCodec<dynamic>? creationParamsCodec;
249
250 /// Which gestures should be forwarded to the UIKit view.
251 ///
252 /// {@macro flutter.widgets.AndroidView.gestureRecognizers.descHead}
253 ///
254 /// For example, with the following setup vertical drags will not be dispatched to the UIKit
255 /// view as the vertical drag gesture is claimed by the parent [GestureDetector].
256 ///
257 /// ```dart
258 /// GestureDetector(
259 /// onVerticalDragStart: (DragStartDetails details) {},
260 /// child: const UiKitView(
261 /// viewType: 'webview',
262 /// ),
263 /// )
264 /// ```
265 ///
266 /// To get the [UiKitView] to claim the vertical drag gestures we can pass a vertical drag
267 /// gesture recognizer factory in [gestureRecognizers] e.g:
268 ///
269 /// ```dart
270 /// GestureDetector(
271 /// onVerticalDragStart: (DragStartDetails details) {},
272 /// child: SizedBox(
273 /// width: 200.0,
274 /// height: 100.0,
275 /// child: UiKitView(
276 /// viewType: 'webview',
277 /// gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{
278 /// Factory<OneSequenceGestureRecognizer>(
279 /// () => EagerGestureRecognizer(),
280 /// ),
281 /// },
282 /// ),
283 /// ),
284 /// )
285 /// ```
286 ///
287 /// {@macro flutter.widgets.AndroidView.gestureRecognizers.descFoot}
288 // We use OneSequenceGestureRecognizers as they support gesture arena teams.
289 // TODO(amirh): get a list of GestureRecognizers here.
290 // https://github.com/flutter/flutter/issues/20953
291 final Set<Factory<OneSequenceGestureRecognizer>>? gestureRecognizers;
292}
293
294// TODO(amirh): describe the embedding mechanism.
295// TODO(ychris): remove the documentation for conic path not supported once https://github.com/flutter/flutter/issues/35062 is resolved.
296/// Embeds an iOS view in the Widget hierarchy.
297///
298/// Embedding iOS views is an expensive operation and should be avoided when a Flutter
299/// equivalent is possible.
300///
301/// {@macro flutter.widgets.AndroidView.layout}
302///
303/// {@macro flutter.widgets.AndroidView.gestures}
304///
305/// {@macro flutter.widgets.AndroidView.lifetime}
306///
307/// Construction of UIViews is done asynchronously, before the UIView is ready this widget paints
308/// nothing while maintaining the same layout constraints.
309///
310/// Clipping operations on a UiKitView can result slow performance.
311/// If a conic path clipping is applied to a UIKitView,
312/// a quad path is used to approximate the clip due to limitation of Quartz.
313class UiKitView extends _DarwinView {
314 /// Creates a widget that embeds an iOS view.
315 ///
316 /// {@macro flutter.widgets.AndroidView.constructorArgs}
317 const UiKitView({
318 super.key,
319 required super.viewType,
320 super.onPlatformViewCreated,
321 super.hitTestBehavior = PlatformViewHitTestBehavior.opaque,
322 super.layoutDirection,
323 super.creationParams,
324 super.creationParamsCodec,
325 super.gestureRecognizers,
326 }) : assert(creationParams == null || creationParamsCodec != null);
327
328 @override
329 State<UiKitView> createState() => _UiKitViewState();
330}
331
332/// Widget that contains a macOS AppKit view.
333///
334/// Embedding macOS views is an expensive operation and should be avoided where
335/// a Flutter equivalent is possible.
336///
337/// The platform view's lifetime is the same as the lifetime of the [State]
338/// object for this widget. When the [State] is disposed the platform view (and
339/// auxiliary resources) are lazily released (some resources are immediately
340/// released and some by platform garbage collector). A stateful widget's state
341/// is disposed when the widget is removed from the tree or when it is moved
342/// within the tree. If the stateful widget has a key and it's only moved
343/// relative to its siblings, or it has a [GlobalKey] and it's moved within the
344/// tree, it will not be disposed.
345///
346/// Construction of AppKitViews is done asynchronously, before the underlying
347/// NSView is ready this widget paints nothing while maintaining the same
348/// layout constraints.
349class AppKitView extends _DarwinView {
350 /// Creates a widget that embeds a macOS AppKit NSView.
351 const AppKitView({
352 super.key,
353 required super.viewType,
354 super.onPlatformViewCreated,
355 super.hitTestBehavior = PlatformViewHitTestBehavior.opaque,
356 super.layoutDirection,
357 super.creationParams,
358 super.creationParamsCodec,
359 super.gestureRecognizers,
360 });
361
362 @override
363 State<AppKitView> createState() => _AppKitViewState();
364}
365
366/// The signature of the function that gets called when the [HtmlElementView]
367/// DOM element is created.
368///
369/// [element] is the DOM element that was created.
370///
371/// This callback is called before [element] is attached to the DOM, so it can
372/// be modified as needed by the Flutter web application.
373///
374/// See [HtmlElementView.fromTagName] that receives a callback of this type.
375///
376/// {@template flutter.widgets.web.JSInterop.object}
377/// Flutter uses type `Object` so this API doesn't force any JS interop API
378/// implementation to Flutter users. This `element` can be cast to any compatible
379/// JS interop type as needed. For example: `JSAny` (from `dart:js_interop`),
380/// `HTMLElement` (from `package:web`) or any custom JS interop definition.
381/// See "Next-generation JS interop": https://dart.dev/interop/js-interop
382/// {@endtemplate}
383typedef ElementCreatedCallback = void Function(Object element);
384
385/// Embeds an HTML element in the Widget hierarchy in Flutter web.
386///
387/// The embedded HTML is laid out like any other Flutter widget and
388/// transformations (like opacity, and clipping) apply to it as well.
389///
390/// {@macro flutter.widgets.AndroidView.layout}
391///
392/// Embedding HTML is a _potentially expensive_ operation and should be avoided
393/// when a Flutter equivalent is possible. (See **`isVisible` parameter** below.)
394/// This widget is useful to integrate native HTML elements to a Flutter web app,
395/// like a `<video>` tag, or a `<div>` where a [Google Map](https://pub.dev/packages/google_maps_flutter)
396/// can be rendered.
397///
398/// This widget **only works on Flutter web.** To embed web content on other
399/// platforms, consider using the [`webview_flutter` plugin](https://pub.dev/packages/webview_flutter).
400///
401/// ## Usage
402///
403/// There's two ways to use the `HtmlElementView` widget:
404///
405/// ### `HtmlElementView.fromTagName`
406///
407/// The [HtmlElementView.fromTagName] constructor creates the HTML element
408/// specified by `tagName`, and passes it to the `onElementCreated` callback
409/// where it can be customized:
410///
411/// ```dart
412/// // In a `build` method...
413/// HtmlElementView.fromTagName(
414/// tagName: 'div',
415/// onElementCreated: myOnElementCreated,
416/// );
417/// ```
418///
419/// The example creates a `<div>` element, then calls the `onElementCreated`
420/// callback with the created `<div>`, so it can be customized **before** it is
421/// attached to the DOM.
422///
423/// (See more details about `onElementCreated` in the **Lifecycle** section below.)
424///
425/// ### Using the `PlatformViewRegistry`
426///
427/// The primitives used to implement [HtmlElementView.fromTagName] are available
428/// for general use through `dart:ui_web`'s `platformViewRegistry`.
429///
430/// Creating an `HtmlElementView` through these primitives is a two step process:
431///
432/// #### 1. `registerViewFactory`
433///
434/// First, a `viewFactory` function needs to be registered for a given `viewType`.
435/// Flutter web will call this factory function to create the `element` that will
436/// be attached later:
437///
438/// ```dart
439/// import 'dart:ui_web' as ui_web;
440/// import 'package:web/web.dart' as web;
441///
442/// void registerRedDivFactory() {
443/// ui_web.platformViewRegistry.registerViewFactory(
444/// 'my-view-type',
445/// (int viewId, {Object? params}) {
446/// // Create and return an HTML Element from here
447/// final web.HTMLDivElement myDiv = web.HTMLDivElement()
448/// ..id = 'some_id_$viewId'
449/// ..style.backgroundColor = 'red'
450/// ..style.width = '100%'
451/// ..style.height = '100%';
452/// return myDiv;
453/// },
454/// );
455/// }
456/// ```
457///
458/// `registerViewFactory` **must** be called outside of `build` methods, so the
459/// registered function is available when `build` happens.
460///
461/// See the different types of functions that can be used as `viewFactory`:
462///
463/// * [`typedef ui_web.PlatformViewFactory`](https://api.flutter.dev/flutter/dart-ui_web/PlatformViewFactory.html)
464/// * [`typedef ui_web.ParameterizedPlatformViewFactory`](https://api.flutter.dev/flutter/dart-ui_web/ParameterizedPlatformViewFactory.html)
465///
466/// #### 2. `HtmlElementView` widget
467///
468/// Once a factory is registered, an `HtmlElementView` widget of `viewType` can
469/// be added to the widget tree, like so:
470///
471/// ```dart
472/// // In a `build` method...
473/// const HtmlElementView(
474/// viewType: 'my-view-type',
475/// onPlatformViewCreated: myOnPlatformViewCreated,
476/// creationParams: <String, Object?>{
477/// 'key': 'someValue',
478/// },
479/// );
480/// ```
481///
482/// [viewType] **must** match the value used to `registerViewFactory` before.
483///
484/// [creationParams] (optional) will be passed to your `viewFactory` function,
485/// if it accepts them.
486///
487/// [onPlatformViewCreated] will be called with the `viewId` of the platform
488/// view (`element`) created by the `viewFactory`, before it gets attached to
489/// the DOM.
490///
491/// The `viewId` can be used to retrieve the created `element` (The same one
492/// passed to `onElementCreated` in [HtmlElementView.fromTagName]) with the
493/// `ui_web.platformViewRegistry.`[`getViewById` method](https://api.flutter.dev/flutter/dart-ui_web/PlatformViewRegistry/getViewById.html).
494///
495/// (See more details about `onPlatformViewCreated` in the **Lifecycle** section
496/// below.)
497///
498/// ## Lifecycle
499///
500/// `HtmlElementView` widgets behave like any other Flutter stateless widget, but
501/// with an additional lifecycle method: `onPlatformViewCreated` / `onElementCreated`
502/// (depending on the constructor, see **Usage** above).
503///
504/// The difference between the two callbacks is the parameter they receive:
505///
506/// * `onPlatformViewCreated` will be called with the created `viewId` as a parameter,
507/// and needs `ui_web.platformViewRegistry.getViewById` to retrieve the created
508/// element (See [PlatformViewCreatedCallback]).
509/// * `onElementCreated` will be called with the created `element` directly,
510/// skipping its `viewId` (See [ElementCreatedCallback]).
511///
512/// Both callbacks are called **after** the HTML `element` has been created, but
513/// **before** it's attached to the DOM.
514///
515/// ### HTML Lifecycle
516///
517/// The Browser DOM APIs have additional HTML lifecycle callbacks for the root
518/// `element` of an `HtmlElementView`.
519///
520/// #### Element Attached To The DOM
521///
522/// It is common for JS code to locate the DOM elements they need with a
523/// selector, rather than accepting said DOM elements directly. In those cases,
524/// the `element` **must** be attached to the DOM for the selector to work.
525///
526/// The example below demonstrates **how to create an `onElementAttached` function**
527/// that gets called when the root `element` is attached to the DOM using a
528/// `ResizeObserver` through `package:web` from the `onElementCreated` lifecycle
529/// method:
530///
531/// ```dart
532/// import 'dart:js_interop';
533/// import 'package:web/web.dart' as web;
534///
535/// // Called after `element` is attached to the DOM.
536/// void onElementAttached(web.HTMLDivElement element) {
537/// final web.Element? located = web.document.querySelector('#someIdThatICanFindLater');
538/// assert(located == element, 'Wrong `element` located!');
539/// // Do things with `element` or `located`, or call your code now...
540/// element.style.backgroundColor = 'green';
541/// }
542///
543/// void onElementCreated(Object element) {
544/// element as web.HTMLDivElement;
545/// element.style.backgroundColor = 'red';
546/// element.id = 'someIdThatICanFindLater';
547///
548/// // Create the observer
549/// final web.ResizeObserver observer = web.ResizeObserver((
550/// JSArray<web.ResizeObserverEntry> entries,
551/// web.ResizeObserver observer,
552/// ) {
553/// if (element.isConnected) {
554/// // The observer is done, disconnect it.
555/// observer.disconnect();
556/// // Call our callback.
557/// onElementAttached(element);
558/// }
559/// }.toJS);
560///
561/// // Connect the observer.
562/// observer.observe(element);
563/// }
564/// ```
565///
566/// * Read more about [`ResizeObserver` in the MDN](https://developer.mozilla.org/en-US/docs/Web/API/Resize_Observer_API).
567///
568/// #### Other Observers
569///
570/// The example above uses a `ResizeObserver` because it can be applied directly
571/// to the `element` that is about to be attached. Another observer that could
572/// be used for this (with a little bit more code) would be a
573/// [`MutationObserver`](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver).
574///
575/// The `MutationObserver` requires the parent element in which the `HtmlElementView`
576/// is going to be inserted. A safe way to retrieve a parent element for the
577/// platform view is to retrieve the `hostElement` of the [FlutterView] where the
578/// `HtmlElementView` is being rendered.
579///
580/// The `hostElement` of the current [FlutterView] can be retrieved through:
581///
582/// ```dart
583/// import 'dart:js_interop';
584/// import 'dart:ui_web' as ui_web;
585/// import 'package:flutter/widgets.dart';
586///
587/// void useHostElement(BuildContext context) {
588/// final int flutterViewId = View.of(context).viewId;
589/// final JSAny? hostElement = ui_web.views.getHostElement(flutterViewId);
590/// // Use `package:web` with `hostElement`...
591/// }
592/// ```
593///
594/// **Important:** `FlutterView.viewId` and the `viewId` parameter passed to
595/// the `viewFactory` identify **different objects**:
596///
597/// * `flutterViewId` (from `View.of(context)`) represents the [FlutterView]
598/// where the web app is currently rendering.
599/// * `viewId` (passed to the `viewFactory` function) represents a unique ID
600/// for the `HtmlElementView` instance that is being attached to the app.
601///
602/// Read more about [FlutterView] on Flutter's API docs:
603///
604/// * [`View.of`](https://api.flutter.dev/flutter/widgets/View/of.html)
605/// * [`getHostElement`](https://main-api.flutter.dev/flutter/dart-ui_web/FlutterViewManagerProxy/getHostElement.html)
606///
607/// ## Pointer events
608///
609/// In order for the `HtmlElementView` contents to be interactive, they're allowed
610/// to handle `pointer-events`. This may result in Flutter missing some events
611/// because they've been handled by the `HtmlElementView`, and not seen by
612/// Flutter.
613///
614/// [`package:pointer_interceptor`](https://pub.dev/packages/pointer_interceptor)
615/// may help in some cases where Flutter content needs to be overlaid on top of
616/// an `HtmlElementView`. Alternatively, the `pointer-events: none` property can
617/// be set `onElementCreated`; but that will prevent **ALL** interactions with
618/// the underlying HTML content.
619///
620/// If the `HtmlElementView` is an `<iframe>` element, Flutter will not receive
621/// pointer events that land in the `<iframe>` (click/tap, drag, drop, etc.)
622/// In those cases, the `HtmlElementView` will seem like it's _swallowing_
623/// the events and not participating in Flutter's gesture detection.
624///
625/// ## `isVisible` parameter
626///
627/// Rendering custom HTML content (from `HtmlElementView`) in between `canvas`
628/// pixels means that the Flutter web engine needs to _split_ the canvas drawing
629/// into elements drawn _behind_ the HTML content, and those drawn _above_ it.
630///
631/// In the Flutter web engine, each of these _splits of the canvas to sandwich
632/// HTML content in between_ is referred to as an **overlay**.
633///
634/// Each _overlay_ present in a scene has implications both in memory and
635/// execution performance, and it is best to minimize their amount; browsers
636/// support a limited number of _overlays_ on a single scene at a given time.
637///
638/// `HtmlElementView` objects have an `isVisible` property that can be passed
639/// through `registerViewFactory`, or `fromTagName`. `isVisible` refers
640/// to whether the `HtmlElementView` will paint pixels on the screen or not.
641///
642/// Correctly defining this value helps the Flutter web rendering engine optimize
643/// the amount of _overlays_ it'll need to render a particular scene.
644///
645/// In general, `isVisible` should be left to its default value of `true`, but
646/// in some `HtmlElementView`s (like the `pointer_interceptor` or `Link` widget),
647/// it can be set to `false`, so the engine doesn't _waste_ an overlay to render
648/// Flutter content on top of views that don't paint any pixels.
649class HtmlElementView extends StatelessWidget {
650 /// Creates a platform view for Flutter web.
651 ///
652 /// `viewType` identifies the type of platform view to create.
653 const HtmlElementView({
654 super.key,
655 required this.viewType,
656 this.onPlatformViewCreated,
657 this.creationParams,
658 this.hitTestBehavior = PlatformViewHitTestBehavior.opaque,
659 });
660
661 /// Creates a platform view that creates a DOM element specified by [tagName].
662 ///
663 /// [isVisible] indicates whether the view is visible to the user or not.
664 /// Setting this to false allows the rendering pipeline to perform extra
665 /// optimizations knowing that the view will not result in any pixels painted
666 /// on the screen.
667 ///
668 /// [onElementCreated] is called when the DOM element is created. It can be
669 /// used by the app to customize the element by adding attributes and styles.
670 /// This method is called *before* the element is attached to the DOM.
671 factory HtmlElementView.fromTagName({
672 Key? key,
673 required String tagName,
674 bool isVisible = true,
675 ElementCreatedCallback? onElementCreated,
676 PlatformViewHitTestBehavior hitTestBehavior = PlatformViewHitTestBehavior.opaque,
677 }) => HtmlElementViewImpl.createFromTagName(
678 key: key,
679 tagName: tagName,
680 isVisible: isVisible,
681 onElementCreated: onElementCreated,
682 hitTestBehavior: hitTestBehavior,
683 );
684
685 /// The unique identifier for the HTML view type to be embedded by this widget.
686 ///
687 /// A PlatformViewFactory for this type must have been registered.
688 final String viewType;
689
690 /// Callback to invoke after the platform view has been created.
691 ///
692 /// This method is called *before* the platform view is attached to the DOM.
693 ///
694 /// May be null.
695 final PlatformViewCreatedCallback? onPlatformViewCreated;
696
697 /// Passed as the 2nd argument (i.e. `params`) of the registered view factory.
698 final Object? creationParams;
699
700 /// {@macro flutter.widgets.AndroidView.hitTestBehavior}
701 final PlatformViewHitTestBehavior hitTestBehavior;
702
703 @override
704 Widget build(BuildContext context) => buildImpl(context);
705}
706
707class _AndroidViewState extends State<AndroidView> {
708 int? _id;
709 late AndroidViewController _controller;
710 TextDirection? _layoutDirection;
711 bool _initialized = false;
712 FocusNode? _focusNode;
713
714 static final Set<Factory<OneSequenceGestureRecognizer>> _emptyRecognizersSet =
715 <Factory<OneSequenceGestureRecognizer>>{};
716
717 @override
718 Widget build(BuildContext context) {
719 return Focus(
720 focusNode: _focusNode,
721 onFocusChange: _onFocusChange,
722 child: _AndroidPlatformView(
723 controller: _controller,
724 hitTestBehavior: widget.hitTestBehavior,
725 gestureRecognizers: widget.gestureRecognizers ?? _emptyRecognizersSet,
726 clipBehavior: widget.clipBehavior,
727 ),
728 );
729 }
730
731 void _initializeOnce() {
732 if (_initialized) {
733 return;
734 }
735 _initialized = true;
736 _createNewAndroidView();
737 _focusNode = FocusNode(debugLabel: 'AndroidView(id: $_id)');
738 }
739
740 @override
741 void didChangeDependencies() {
742 super.didChangeDependencies();
743 final TextDirection newLayoutDirection = _findLayoutDirection();
744 final bool didChangeLayoutDirection = _layoutDirection != newLayoutDirection;
745 _layoutDirection = newLayoutDirection;
746
747 _initializeOnce();
748 if (didChangeLayoutDirection) {
749 // The native view will update asynchronously, in the meantime we don't want
750 // to block the framework. (so this is intentionally not awaiting).
751 _controller.setLayoutDirection(_layoutDirection!);
752 }
753 }
754
755 @override
756 void didUpdateWidget(AndroidView oldWidget) {
757 super.didUpdateWidget(oldWidget);
758
759 final TextDirection newLayoutDirection = _findLayoutDirection();
760 final bool didChangeLayoutDirection = _layoutDirection != newLayoutDirection;
761 _layoutDirection = newLayoutDirection;
762
763 if (widget.viewType != oldWidget.viewType) {
764 _controller.disposePostFrame();
765 _createNewAndroidView();
766 return;
767 }
768
769 if (didChangeLayoutDirection) {
770 _controller.setLayoutDirection(_layoutDirection!);
771 }
772 }
773
774 TextDirection _findLayoutDirection() {
775 assert(widget.layoutDirection != null || debugCheckHasDirectionality(context));
776 return widget.layoutDirection ?? Directionality.of(context);
777 }
778
779 @override
780 void dispose() {
781 _controller.dispose();
782 _focusNode?.dispose();
783 _focusNode = null;
784 super.dispose();
785 }
786
787 void _createNewAndroidView() {
788 _id = platformViewsRegistry.getNextPlatformViewId();
789 _controller = PlatformViewsService.initAndroidView(
790 id: _id!,
791 viewType: widget.viewType,
792 layoutDirection: _layoutDirection!,
793 creationParams: widget.creationParams,
794 creationParamsCodec: widget.creationParamsCodec,
795 onFocus: () {
796 _focusNode!.requestFocus();
797 },
798 );
799 if (widget.onPlatformViewCreated != null) {
800 _controller.addOnPlatformViewCreatedListener(widget.onPlatformViewCreated!);
801 }
802 }
803
804 void _onFocusChange(bool isFocused) {
805 if (!_controller.isCreated) {
806 return;
807 }
808 if (!isFocused) {
809 _controller.clearFocus().catchError((dynamic e) {
810 if (e is MissingPluginException) {
811 // We land the framework part of Android platform views keyboard
812 // support before the engine part. There will be a commit range where
813 // clearFocus isn't implemented in the engine. When that happens we
814 // just swallow the error here. Once the engine part is rolled to the
815 // framework I'll remove this.
816 // TODO(amirh): remove this once the engine's clearFocus is rolled.
817 return;
818 }
819 });
820 return;
821 }
822 SystemChannels.textInput
823 .invokeMethod<void>('TextInput.setPlatformViewClient', <String, dynamic>{
824 'platformViewId': _id,
825 })
826 .catchError((dynamic e) {
827 if (e is MissingPluginException) {
828 // We land the framework part of Android platform views keyboard
829 // support before the engine part. There will be a commit range where
830 // setPlatformViewClient isn't implemented in the engine. When that
831 // happens we just swallow the error here. Once the engine part is
832 // rolled to the framework I'll remove this.
833 // TODO(amirh): remove this once the engine's clearFocus is rolled.
834 return;
835 }
836 });
837 }
838}
839
840abstract class _DarwinViewState<
841 PlatformViewT extends _DarwinView,
842 ControllerT extends DarwinPlatformViewController,
843 RenderT extends RenderDarwinPlatformView<ControllerT>,
844 ViewT extends _DarwinPlatformView<ControllerT, RenderT>
845>
846 extends State<PlatformViewT> {
847 ControllerT? _controller;
848 TextDirection? _layoutDirection;
849 bool _initialized = false;
850
851 @visibleForTesting
852 FocusNode? focusNode;
853
854 static final Set<Factory<OneSequenceGestureRecognizer>> _emptyRecognizersSet =
855 <Factory<OneSequenceGestureRecognizer>>{};
856
857 @override
858 Widget build(BuildContext context) {
859 final ControllerT? controller = _controller;
860 if (controller == null) {
861 return const SizedBox.expand();
862 }
863 return Focus(
864 focusNode: focusNode,
865 onFocusChange: (bool isFocused) => _onFocusChange(isFocused, controller),
866 child: childPlatformView(),
867 );
868 }
869
870 ViewT childPlatformView();
871
872 void _initializeOnce() {
873 if (_initialized) {
874 return;
875 }
876 _initialized = true;
877 _createNewUiKitView();
878 }
879
880 @override
881 void didChangeDependencies() {
882 super.didChangeDependencies();
883 final TextDirection newLayoutDirection = _findLayoutDirection();
884 final bool didChangeLayoutDirection = _layoutDirection != newLayoutDirection;
885 _layoutDirection = newLayoutDirection;
886
887 _initializeOnce();
888 if (didChangeLayoutDirection) {
889 // The native view will update asynchronously, in the meantime we don't want
890 // to block the framework. (so this is intentionally not awaiting).
891 _controller?.setLayoutDirection(_layoutDirection!);
892 }
893 }
894
895 @override
896 void didUpdateWidget(PlatformViewT oldWidget) {
897 super.didUpdateWidget(oldWidget);
898
899 final TextDirection newLayoutDirection = _findLayoutDirection();
900 final bool didChangeLayoutDirection = _layoutDirection != newLayoutDirection;
901 _layoutDirection = newLayoutDirection;
902
903 if (widget.viewType != oldWidget.viewType) {
904 _controller?.dispose();
905 _controller = null;
906 focusNode?.dispose();
907 focusNode = null;
908 _createNewUiKitView();
909 return;
910 }
911
912 if (didChangeLayoutDirection) {
913 _controller?.setLayoutDirection(_layoutDirection!);
914 }
915 }
916
917 TextDirection _findLayoutDirection() {
918 assert(widget.layoutDirection != null || debugCheckHasDirectionality(context));
919 return widget.layoutDirection ?? Directionality.of(context);
920 }
921
922 @override
923 void dispose() {
924 _controller?.dispose();
925 _controller = null;
926 focusNode?.dispose();
927 focusNode = null;
928 super.dispose();
929 }
930
931 Future<void> _createNewUiKitView() async {
932 final int id = platformViewsRegistry.getNextPlatformViewId();
933 final ControllerT controller = await createNewViewController(id);
934 if (!mounted) {
935 controller.dispose();
936 return;
937 }
938 widget.onPlatformViewCreated?.call(id);
939 setState(() {
940 _controller = controller;
941 focusNode = FocusNode(debugLabel: 'UiKitView(id: $id)');
942 });
943 }
944
945 Future<ControllerT> createNewViewController(int id);
946
947 void _onFocusChange(bool isFocused, ControllerT controller) {
948 if (!isFocused) {
949 // Unlike Android, we do not need to send "clearFocus" channel message
950 // to the engine, because focusing on another view will automatically
951 // cancel the focus on the previously focused platform view.
952 return;
953 }
954 SystemChannels.textInput.invokeMethod<void>(
955 'TextInput.setPlatformViewClient',
956 <String, dynamic>{'platformViewId': controller.id},
957 );
958 }
959}
960
961class _UiKitViewState
962 extends _DarwinViewState<UiKitView, UiKitViewController, RenderUiKitView, _UiKitPlatformView> {
963 @override
964 Future<UiKitViewController> createNewViewController(int id) async {
965 return PlatformViewsService.initUiKitView(
966 id: id,
967 viewType: widget.viewType,
968 layoutDirection: _layoutDirection!,
969 creationParams: widget.creationParams,
970 creationParamsCodec: widget.creationParamsCodec,
971 onFocus: () {
972 focusNode?.requestFocus();
973 },
974 );
975 }
976
977 @override
978 _UiKitPlatformView childPlatformView() {
979 return _UiKitPlatformView(
980 controller: _controller!,
981 hitTestBehavior: widget.hitTestBehavior,
982 gestureRecognizers: widget.gestureRecognizers ?? _DarwinViewState._emptyRecognizersSet,
983 );
984 }
985}
986
987class _AppKitViewState
988 extends
989 _DarwinViewState<AppKitView, AppKitViewController, RenderAppKitView, _AppKitPlatformView> {
990 @override
991 Future<AppKitViewController> createNewViewController(int id) async {
992 return PlatformViewsService.initAppKitView(
993 id: id,
994 viewType: widget.viewType,
995 layoutDirection: _layoutDirection!,
996 creationParams: widget.creationParams,
997 creationParamsCodec: widget.creationParamsCodec,
998 onFocus: () {
999 focusNode?.requestFocus();
1000 },
1001 );
1002 }
1003
1004 @override
1005 _AppKitPlatformView childPlatformView() {
1006 return _AppKitPlatformView(
1007 controller: _controller!,
1008 hitTestBehavior: widget.hitTestBehavior,
1009 gestureRecognizers: widget.gestureRecognizers ?? _DarwinViewState._emptyRecognizersSet,
1010 );
1011 }
1012}
1013
1014class _AndroidPlatformView extends LeafRenderObjectWidget {
1015 const _AndroidPlatformView({
1016 required this.controller,
1017 required this.hitTestBehavior,
1018 required this.gestureRecognizers,
1019 this.clipBehavior = Clip.hardEdge,
1020 });
1021
1022 final AndroidViewController controller;
1023 final PlatformViewHitTestBehavior hitTestBehavior;
1024 final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers;
1025 final Clip clipBehavior;
1026
1027 @override
1028 RenderObject createRenderObject(BuildContext context) => RenderAndroidView(
1029 viewController: controller,
1030 hitTestBehavior: hitTestBehavior,
1031 gestureRecognizers: gestureRecognizers,
1032 clipBehavior: clipBehavior,
1033 );
1034
1035 @override
1036 void updateRenderObject(BuildContext context, RenderAndroidView renderObject) {
1037 renderObject.controller = controller;
1038 renderObject.hitTestBehavior = hitTestBehavior;
1039 renderObject.updateGestureRecognizers(gestureRecognizers);
1040 renderObject.clipBehavior = clipBehavior;
1041 }
1042}
1043
1044abstract class _DarwinPlatformView<
1045 TController extends DarwinPlatformViewController,
1046 TRender extends RenderDarwinPlatformView<TController>
1047>
1048 extends LeafRenderObjectWidget {
1049 const _DarwinPlatformView({
1050 required this.controller,
1051 required this.hitTestBehavior,
1052 required this.gestureRecognizers,
1053 });
1054
1055 final TController controller;
1056 final PlatformViewHitTestBehavior hitTestBehavior;
1057 final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers;
1058
1059 @override
1060 @mustCallSuper
1061 void updateRenderObject(BuildContext context, TRender renderObject) {
1062 renderObject
1063 ..viewController = controller
1064 ..hitTestBehavior = hitTestBehavior
1065 ..updateGestureRecognizers(gestureRecognizers);
1066 }
1067}
1068
1069class _UiKitPlatformView extends _DarwinPlatformView<UiKitViewController, RenderUiKitView> {
1070 const _UiKitPlatformView({
1071 required super.controller,
1072 required super.hitTestBehavior,
1073 required super.gestureRecognizers,
1074 });
1075
1076 @override
1077 RenderObject createRenderObject(BuildContext context) {
1078 return RenderUiKitView(
1079 viewController: controller,
1080 hitTestBehavior: hitTestBehavior,
1081 gestureRecognizers: gestureRecognizers,
1082 );
1083 }
1084}
1085
1086class _AppKitPlatformView extends _DarwinPlatformView<AppKitViewController, RenderAppKitView> {
1087 const _AppKitPlatformView({
1088 required super.controller,
1089 required super.hitTestBehavior,
1090 required super.gestureRecognizers,
1091 });
1092
1093 @override
1094 RenderObject createRenderObject(BuildContext context) {
1095 return RenderAppKitView(
1096 viewController: controller,
1097 hitTestBehavior: hitTestBehavior,
1098 gestureRecognizers: gestureRecognizers,
1099 );
1100 }
1101}
1102
1103/// The parameters used to create a [PlatformViewController].
1104///
1105/// See also:
1106///
1107/// * [CreatePlatformViewCallback] which uses this object to create a [PlatformViewController].
1108class PlatformViewCreationParams {
1109 const PlatformViewCreationParams._({
1110 required this.id,
1111 required this.viewType,
1112 required this.onPlatformViewCreated,
1113 required this.onFocusChanged,
1114 });
1115
1116 /// The unique identifier for the new platform view.
1117 ///
1118 /// [PlatformViewController.viewId] should match this id.
1119 final int id;
1120
1121 /// The unique identifier for the type of platform view to be embedded.
1122 ///
1123 /// This viewType is used to tell the platform which type of view to
1124 /// associate with the [id].
1125 final String viewType;
1126
1127 /// Callback invoked after the platform view has been created.
1128 final PlatformViewCreatedCallback onPlatformViewCreated;
1129
1130 /// Callback invoked when the platform view's focus is changed on the platform side.
1131 ///
1132 /// The value is true when the platform view gains focus and false when it loses focus.
1133 final ValueChanged<bool> onFocusChanged;
1134}
1135
1136/// A factory for a surface presenting a platform view as part of the widget hierarchy.
1137///
1138/// The returned widget should present the platform view associated with `controller`.
1139///
1140/// See also:
1141///
1142/// * [PlatformViewSurface], a common widget for presenting platform views.
1143typedef PlatformViewSurfaceFactory =
1144 Widget Function(BuildContext context, PlatformViewController controller);
1145
1146/// Constructs a [PlatformViewController].
1147///
1148/// The [PlatformViewController.viewId] field of the created controller must match the value of the
1149/// params [PlatformViewCreationParams.id] field.
1150///
1151/// See also:
1152///
1153/// * [PlatformViewLink], which links a platform view with the Flutter framework.
1154typedef CreatePlatformViewCallback =
1155 PlatformViewController Function(PlatformViewCreationParams params);
1156
1157/// Links a platform view with the Flutter framework.
1158///
1159/// Provides common functionality for embedding a platform view (e.g an android.view.View on Android)
1160/// with the Flutter framework.
1161///
1162/// {@macro flutter.widgets.AndroidView.lifetime}
1163///
1164/// To implement a new platform view widget, return this widget in the `build` method.
1165/// For example:
1166///
1167/// ```dart
1168/// class FooPlatformView extends StatelessWidget {
1169/// const FooPlatformView({super.key});
1170/// @override
1171/// Widget build(BuildContext context) {
1172/// return PlatformViewLink(
1173/// viewType: 'webview',
1174/// onCreatePlatformView: createFooWebView,
1175/// surfaceFactory: (BuildContext context, PlatformViewController controller) {
1176/// return PlatformViewSurface(
1177/// gestureRecognizers: gestureRecognizers,
1178/// controller: controller,
1179/// hitTestBehavior: PlatformViewHitTestBehavior.opaque,
1180/// );
1181/// },
1182/// );
1183/// }
1184/// }
1185/// ```
1186///
1187/// The `surfaceFactory` and the `onCreatePlatformView` are only called when the
1188/// state of this widget is initialized, or when the `viewType` changes.
1189class PlatformViewLink extends StatefulWidget {
1190 /// Construct a [PlatformViewLink] widget.
1191 ///
1192 /// See also:
1193 ///
1194 /// * [PlatformViewSurface] for details on the widget returned by `surfaceFactory`.
1195 /// * [PlatformViewCreationParams] for how each parameter can be used when implementing `createPlatformView`.
1196 const PlatformViewLink({
1197 super.key,
1198 required PlatformViewSurfaceFactory surfaceFactory,
1199 required CreatePlatformViewCallback onCreatePlatformView,
1200 required this.viewType,
1201 }) : _surfaceFactory = surfaceFactory,
1202 _onCreatePlatformView = onCreatePlatformView;
1203
1204 final PlatformViewSurfaceFactory _surfaceFactory;
1205 final CreatePlatformViewCallback _onCreatePlatformView;
1206
1207 /// The unique identifier for the view type to be embedded.
1208 ///
1209 /// Typically, this viewType has already been registered on the platform side.
1210 final String viewType;
1211
1212 @override
1213 State<StatefulWidget> createState() => _PlatformViewLinkState();
1214}
1215
1216class _PlatformViewLinkState extends State<PlatformViewLink> {
1217 int? _id;
1218 PlatformViewController? _controller;
1219 bool _platformViewCreated = false;
1220 Widget? _surface;
1221 FocusNode? _focusNode;
1222
1223 @override
1224 Widget build(BuildContext context) {
1225 final PlatformViewController? controller = _controller;
1226 if (controller == null) {
1227 return const SizedBox.expand();
1228 }
1229 if (!_platformViewCreated) {
1230 // Depending on the implementation, the first non-empty size can be used
1231 // to size the platform view.
1232 return _PlatformViewPlaceHolder(
1233 onLayout: (Size size, Offset position) {
1234 if (controller.awaitingCreation && !size.isEmpty) {
1235 controller.create(size: size, position: position);
1236 }
1237 },
1238 );
1239 }
1240 _surface ??= widget._surfaceFactory(context, controller);
1241 return Focus(
1242 focusNode: _focusNode,
1243 onFocusChange: _handleFrameworkFocusChanged,
1244 child: _surface!,
1245 );
1246 }
1247
1248 @override
1249 void initState() {
1250 _focusNode = FocusNode(debugLabel: 'PlatformView(id: $_id)');
1251 _initialize();
1252 super.initState();
1253 }
1254
1255 @override
1256 void didUpdateWidget(PlatformViewLink oldWidget) {
1257 super.didUpdateWidget(oldWidget);
1258
1259 if (widget.viewType != oldWidget.viewType) {
1260 _controller?.disposePostFrame();
1261 // The _surface has to be recreated as its controller is disposed.
1262 // Setting _surface to null will trigger its creation in build().
1263 _surface = null;
1264 _initialize();
1265 }
1266 }
1267
1268 void _initialize() {
1269 _id = platformViewsRegistry.getNextPlatformViewId();
1270 _controller = widget._onCreatePlatformView(
1271 PlatformViewCreationParams._(
1272 id: _id!,
1273 viewType: widget.viewType,
1274 onPlatformViewCreated: _onPlatformViewCreated,
1275 onFocusChanged: _handlePlatformFocusChanged,
1276 ),
1277 );
1278 }
1279
1280 void _onPlatformViewCreated(int id) {
1281 if (mounted) {
1282 setState(() {
1283 _platformViewCreated = true;
1284 });
1285 }
1286 }
1287
1288 void _handleFrameworkFocusChanged(bool isFocused) {
1289 if (!isFocused) {
1290 _controller?.clearFocus();
1291 }
1292 SystemChannels.textInput.invokeMethod<void>(
1293 'TextInput.setPlatformViewClient',
1294 <String, dynamic>{'platformViewId': _id},
1295 );
1296 }
1297
1298 void _handlePlatformFocusChanged(bool isFocused) {
1299 if (isFocused) {
1300 _focusNode!.requestFocus();
1301 }
1302 }
1303
1304 @override
1305 void dispose() {
1306 _controller?.dispose();
1307 _controller = null;
1308 _focusNode?.dispose();
1309 _focusNode = null;
1310 super.dispose();
1311 }
1312}
1313
1314/// Integrates a platform view with Flutter's compositor, touch, and semantics subsystems.
1315///
1316/// The compositor integration is done by adding a [PlatformViewLayer] to the layer tree. [PlatformViewSurface]
1317/// isn't supported on all platforms (e.g on Android platform views can be composited by using a [TextureLayer] or
1318/// [AndroidViewSurface]).
1319/// Custom Flutter embedders can support [PlatformViewLayer]s by implementing a SystemCompositor.
1320///
1321/// The widget fills all available space, the parent of this object must provide bounded layout
1322/// constraints.
1323///
1324/// If the associated platform view is not created the [PlatformViewSurface] does not paint any contents.
1325///
1326/// See also:
1327///
1328/// * [AndroidView] which embeds an Android platform view in the widget hierarchy using a [TextureLayer].
1329/// * [UiKitView] which embeds an iOS platform view in the widget hierarchy.
1330// TODO(amirh): Link to the embedder's system compositor documentation once available.
1331class PlatformViewSurface extends LeafRenderObjectWidget {
1332 /// Construct a [PlatformViewSurface].
1333 const PlatformViewSurface({
1334 super.key,
1335 required this.controller,
1336 required this.hitTestBehavior,
1337 required this.gestureRecognizers,
1338 });
1339
1340 /// The controller for the platform view integrated by this [PlatformViewSurface].
1341 ///
1342 /// [PlatformViewController] is used for dispatching touch events to the platform view.
1343 /// [PlatformViewController.viewId] identifies the platform view whose contents are painted by this widget.
1344 final PlatformViewController controller;
1345
1346 /// Which gestures should be forwarded to the PlatformView.
1347 ///
1348 /// {@macro flutter.widgets.AndroidView.gestureRecognizers.descHead}
1349 ///
1350 /// For example, with the following setup vertical drags will not be dispatched to the platform view
1351 /// as the vertical drag gesture is claimed by the parent [GestureDetector].
1352 ///
1353 /// ```dart
1354 /// GestureDetector(
1355 /// onVerticalDragStart: (DragStartDetails details) { },
1356 /// child: PlatformViewSurface(
1357 /// gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
1358 /// controller: _controller,
1359 /// hitTestBehavior: PlatformViewHitTestBehavior.opaque,
1360 /// ),
1361 /// )
1362 /// ```
1363 ///
1364 /// To get the [PlatformViewSurface] to claim the vertical drag gestures we can pass a vertical drag
1365 /// gesture recognizer factory in [gestureRecognizers] e.g:
1366 ///
1367 /// ```dart
1368 /// GestureDetector(
1369 /// onVerticalDragStart: (DragStartDetails details) { },
1370 /// child: SizedBox(
1371 /// width: 200.0,
1372 /// height: 100.0,
1373 /// child: PlatformViewSurface(
1374 /// gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{
1375 /// Factory<OneSequenceGestureRecognizer>(
1376 /// () => EagerGestureRecognizer(),
1377 /// ),
1378 /// },
1379 /// controller: _controller,
1380 /// hitTestBehavior: PlatformViewHitTestBehavior.opaque,
1381 /// ),
1382 /// ),
1383 /// )
1384 /// ```
1385 ///
1386 /// {@macro flutter.widgets.AndroidView.gestureRecognizers.descFoot}
1387 // We use OneSequenceGestureRecognizers as they support gesture arena teams.
1388 // TODO(amirh): get a list of GestureRecognizers here.
1389 // https://github.com/flutter/flutter/issues/20953
1390 final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers;
1391
1392 /// {@macro flutter.widgets.AndroidView.hitTestBehavior}
1393 final PlatformViewHitTestBehavior hitTestBehavior;
1394
1395 @override
1396 RenderObject createRenderObject(BuildContext context) {
1397 return PlatformViewRenderBox(
1398 controller: controller,
1399 gestureRecognizers: gestureRecognizers,
1400 hitTestBehavior: hitTestBehavior,
1401 );
1402 }
1403
1404 @override
1405 void updateRenderObject(BuildContext context, PlatformViewRenderBox renderObject) {
1406 renderObject
1407 ..controller = controller
1408 ..hitTestBehavior = hitTestBehavior
1409 ..updateGestureRecognizers(gestureRecognizers);
1410 }
1411}
1412
1413/// Integrates an Android view with Flutter's compositor, touch, and semantics subsystems.
1414///
1415/// The compositor integration is done by adding a [TextureLayer] to the layer tree.
1416///
1417/// The parent of this object must provide bounded layout constraints.
1418///
1419/// If the associated platform view is not created, the [AndroidViewSurface] does not paint any contents.
1420///
1421/// When possible, you may want to use [AndroidView] directly, since it requires less boilerplate code
1422/// than [AndroidViewSurface], and there's no difference in performance, or other trade-off(s).
1423///
1424/// See also:
1425///
1426/// * [AndroidView] which embeds an Android platform view in the widget hierarchy.
1427/// * [UiKitView] which embeds an iOS platform view in the widget hierarchy.
1428class AndroidViewSurface extends StatefulWidget {
1429 /// Construct an `AndroidPlatformViewSurface`.
1430 const AndroidViewSurface({
1431 super.key,
1432 required this.controller,
1433 required this.hitTestBehavior,
1434 required this.gestureRecognizers,
1435 });
1436
1437 /// The controller for the platform view integrated by this [AndroidViewSurface].
1438 ///
1439 /// See [PlatformViewSurface.controller] for details.
1440 final AndroidViewController controller;
1441
1442 /// Which gestures should be forwarded to the PlatformView.
1443 ///
1444 /// See [PlatformViewSurface.gestureRecognizers] for details.
1445 final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers;
1446
1447 /// {@macro flutter.widgets.AndroidView.hitTestBehavior}
1448 final PlatformViewHitTestBehavior hitTestBehavior;
1449
1450 @override
1451 State<StatefulWidget> createState() {
1452 return _AndroidViewSurfaceState();
1453 }
1454}
1455
1456class _AndroidViewSurfaceState extends State<AndroidViewSurface> {
1457 @override
1458 void initState() {
1459 super.initState();
1460 if (!widget.controller.isCreated) {
1461 // Schedule a rebuild once creation is complete and the final display
1462 // type is known.
1463 widget.controller.addOnPlatformViewCreatedListener(_onPlatformViewCreated);
1464 }
1465 }
1466
1467 @override
1468 void dispose() {
1469 widget.controller.removeOnPlatformViewCreatedListener(_onPlatformViewCreated);
1470 super.dispose();
1471 }
1472
1473 @override
1474 Widget build(BuildContext context) {
1475 if (widget.controller.requiresViewComposition) {
1476 return _PlatformLayerBasedAndroidViewSurface(
1477 controller: widget.controller,
1478 hitTestBehavior: widget.hitTestBehavior,
1479 gestureRecognizers: widget.gestureRecognizers,
1480 );
1481 } else {
1482 return _TextureBasedAndroidViewSurface(
1483 controller: widget.controller,
1484 hitTestBehavior: widget.hitTestBehavior,
1485 gestureRecognizers: widget.gestureRecognizers,
1486 );
1487 }
1488 }
1489
1490 void _onPlatformViewCreated(int _) {
1491 // Trigger a re-build based on the current controller state.
1492 setState(() {});
1493 }
1494}
1495
1496// Displays an Android platform view via GL texture.
1497class _TextureBasedAndroidViewSurface extends PlatformViewSurface {
1498 const _TextureBasedAndroidViewSurface({
1499 required AndroidViewController super.controller,
1500 required super.hitTestBehavior,
1501 required super.gestureRecognizers,
1502 });
1503
1504 @override
1505 RenderObject createRenderObject(BuildContext context) {
1506 final AndroidViewController viewController = controller as AndroidViewController;
1507 // Use GL texture based composition.
1508 // App should use GL texture unless they require to embed a SurfaceView.
1509 final RenderAndroidView renderBox = RenderAndroidView(
1510 viewController: viewController,
1511 gestureRecognizers: gestureRecognizers,
1512 hitTestBehavior: hitTestBehavior,
1513 );
1514 viewController.pointTransformer = (Offset position) => renderBox.globalToLocal(position);
1515 return renderBox;
1516 }
1517}
1518
1519class _PlatformLayerBasedAndroidViewSurface extends PlatformViewSurface {
1520 const _PlatformLayerBasedAndroidViewSurface({
1521 required AndroidViewController super.controller,
1522 required super.hitTestBehavior,
1523 required super.gestureRecognizers,
1524 });
1525
1526 @override
1527 RenderObject createRenderObject(BuildContext context) {
1528 final AndroidViewController viewController = controller as AndroidViewController;
1529 final PlatformViewRenderBox renderBox =
1530 super.createRenderObject(context) as PlatformViewRenderBox;
1531 viewController.pointTransformer = (Offset position) => renderBox.globalToLocal(position);
1532 return renderBox;
1533 }
1534}
1535
1536/// A callback used to notify the size of the platform view placeholder.
1537/// This size is the initial size of the platform view.
1538typedef _OnLayoutCallback = void Function(Size size, Offset position);
1539
1540/// A [RenderBox] that notifies its size to the owner after a layout.
1541class _PlatformViewPlaceholderBox extends RenderConstrainedBox {
1542 _PlatformViewPlaceholderBox({required this.onLayout})
1543 : super(
1544 additionalConstraints: const BoxConstraints.tightFor(
1545 width: double.infinity,
1546 height: double.infinity,
1547 ),
1548 );
1549
1550 _OnLayoutCallback onLayout;
1551
1552 @override
1553 void performLayout() {
1554 super.performLayout();
1555 // A call to `localToGlobal` requires waiting for a frame to render first.
1556 SchedulerBinding.instance.addPostFrameCallback((_) {
1557 onLayout(size, localToGlobal(Offset.zero));
1558 }, debugLabel: 'PlatformViewPlaceholderBox.onLayout');
1559 }
1560}
1561
1562/// When a platform view is in the widget hierarchy, this widget is used to capture
1563/// the size of the platform view after the first layout.
1564/// This placeholder is basically a [SizedBox.expand] with a [onLayout] callback to
1565/// notify the size of the render object to its parent.
1566class _PlatformViewPlaceHolder extends SingleChildRenderObjectWidget {
1567 const _PlatformViewPlaceHolder({required this.onLayout});
1568
1569 final _OnLayoutCallback onLayout;
1570
1571 @override
1572 _PlatformViewPlaceholderBox createRenderObject(BuildContext context) {
1573 return _PlatformViewPlaceholderBox(onLayout: onLayout);
1574 }
1575
1576 @override
1577 void updateRenderObject(BuildContext context, _PlatformViewPlaceholderBox renderObject) {
1578 renderObject.onLayout = onLayout;
1579 }
1580}
1581
1582extension on PlatformViewController {
1583 /// Disposes the controller in a post-frame callback, to allow other widgets to
1584 /// remove their listeners before the controller is disposed.
1585 void disposePostFrame() {
1586 SchedulerBinding.instance.addPostFrameCallback((_) {
1587 dispose();
1588 }, debugLabel: 'PlatformViewController.dispose');
1589 }
1590}
1591

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com