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'; |
6 | library; |
7 | |
8 | import 'package:flutter/foundation.dart'; |
9 | import 'package:flutter/gestures.dart'; |
10 | import 'package:flutter/rendering.dart'; |
11 | import 'package:flutter/scheduler.dart'; |
12 | import 'package:flutter/services.dart'; |
13 | |
14 | import '_html_element_view_io.dart' if (dart.library.js_util) '_html_element_view_web.dart'; |
15 | import 'basic.dart'; |
16 | import 'debug.dart'; |
17 | import 'focus_manager.dart'; |
18 | import 'focus_scope.dart'; |
19 | import 'framework.dart'; |
20 | |
21 | // Examples can assume: |
22 | // PlatformViewController createFooWebView(PlatformViewCreationParams params) { return (null as dynamic) as PlatformViewController; } |
23 | // Set |
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} |
70 | class 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. |
207 | abstract 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. |
313 | class 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. |
349 | class 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} |
383 | typedef 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. |
649 | class 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 | |
707 | class _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 | |
840 | abstract 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 | |
961 | class _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 | |
987 | class _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 | |
1014 | class _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 | |
1044 | abstract 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 | |
1069 | class _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 | |
1086 | class _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]. |
1108 | class 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. |
1143 | typedef 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. |
1154 | typedef 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. |
1189 | class 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 | |
1216 | class _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. |
1331 | class 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. |
1428 | class 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 | |
1456 | class _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. |
1497 | class _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 | |
1519 | class _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. |
1538 | typedef _OnLayoutCallback = void Function(Size size, Offset position); |
1539 | |
1540 | /// A [RenderBox] that notifies its size to the owner after a layout. |
1541 | class _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. |
1566 | class _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 | |
1582 | extension 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 |
Definitions
- AndroidView
- AndroidView
- createState
- _DarwinView
- _DarwinView
- UiKitView
- UiKitView
- createState
- AppKitView
- AppKitView
- createState
- HtmlElementView
- HtmlElementView
- fromTagName
- build
- _AndroidViewState
- build
- _initializeOnce
- didChangeDependencies
- didUpdateWidget
- _findLayoutDirection
- dispose
- _createNewAndroidView
- _onFocusChange
- _DarwinViewState
- build
- childPlatformView
- _initializeOnce
- didChangeDependencies
- didUpdateWidget
- _findLayoutDirection
- dispose
- _createNewUiKitView
- createNewViewController
- _onFocusChange
- _UiKitViewState
- createNewViewController
- childPlatformView
- _AppKitViewState
- createNewViewController
- childPlatformView
- _AndroidPlatformView
- _AndroidPlatformView
- createRenderObject
- updateRenderObject
- _DarwinPlatformView
- _DarwinPlatformView
- updateRenderObject
- _UiKitPlatformView
- _UiKitPlatformView
- createRenderObject
- _AppKitPlatformView
- _AppKitPlatformView
- createRenderObject
- PlatformViewCreationParams
- _
- PlatformViewLink
- PlatformViewLink
- createState
- _PlatformViewLinkState
- build
- initState
- didUpdateWidget
- _initialize
- _onPlatformViewCreated
- _handleFrameworkFocusChanged
- _handlePlatformFocusChanged
- dispose
- PlatformViewSurface
- PlatformViewSurface
- createRenderObject
- updateRenderObject
- AndroidViewSurface
- AndroidViewSurface
- createState
- _AndroidViewSurfaceState
- initState
- dispose
- build
- _onPlatformViewCreated
- _TextureBasedAndroidViewSurface
- _TextureBasedAndroidViewSurface
- createRenderObject
- _PlatformLayerBasedAndroidViewSurface
- _PlatformLayerBasedAndroidViewSurface
- createRenderObject
- _PlatformViewPlaceholderBox
- _PlatformViewPlaceholderBox
- performLayout
- _PlatformViewPlaceHolder
- _PlatformViewPlaceHolder
- createRenderObject
- updateRenderObject
Learn more about Flutter for embedded and desktop on industrialflutter.com