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 | import 'package:flutter/foundation.dart'; |
6 | import 'package:flutter/gestures.dart'; |
7 | import 'package:flutter/rendering.dart'; |
8 | import 'package:flutter/scheduler.dart'; |
9 | import 'package:flutter/services.dart'; |
10 | |
11 | import '_html_element_view_io.dart' if (dart.library.js_util) '_html_element_view_web.dart' ; |
12 | import 'basic.dart'; |
13 | import 'debug.dart'; |
14 | import 'focus_manager.dart'; |
15 | import 'focus_scope.dart'; |
16 | import 'framework.dart'; |
17 | |
18 | // Examples can assume: |
19 | // PlatformViewController createFooWebView(PlatformViewCreationParams params) { return (null as dynamic) as PlatformViewController; } |
20 | // Set> gestureRecognizers = >{}; |
21 | // late PlatformViewController _controller; |
22 | |
23 | /// Embeds an Android view in the Widget hierarchy. |
24 | /// |
25 | /// Requires Android API level 23 or greater. |
26 | /// |
27 | /// Embedding Android views is an expensive operation and should be avoided when a Flutter |
28 | /// equivalent is possible. |
29 | /// |
30 | /// The embedded Android view is painted just like any other Flutter widget and transformations |
31 | /// apply to it as well. |
32 | /// |
33 | /// {@template flutter.widgets.AndroidView.layout} |
34 | /// The widget fills all available space, the parent of this object must provide bounded layout |
35 | /// constraints. |
36 | /// {@endtemplate} |
37 | /// |
38 | /// {@template flutter.widgets.AndroidView.gestures} |
39 | /// The widget participates in Flutter's gesture arenas, and dispatches touch events to the |
40 | /// platform view iff it won the arena. Specific gestures that should be dispatched to the platform |
41 | /// view can be specified in the `gestureRecognizers` constructor parameter. If |
42 | /// the set of gesture recognizers is empty, a gesture will be dispatched to the platform |
43 | /// view iff it was not claimed by any other gesture recognizer. |
44 | /// {@endtemplate} |
45 | /// |
46 | /// The Android view object is created using a [PlatformViewFactory](/javadoc/io/flutter/plugin/platform/PlatformViewFactory.html). |
47 | /// 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-). |
48 | /// |
49 | /// Registration is typically done in the plugin's registerWith method, e.g: |
50 | /// |
51 | /// ```java |
52 | /// public static void registerWith(Registrar registrar) { |
53 | /// registrar.platformViewRegistry().registerViewFactory("webview", WebViewFactory(registrar.messenger())); |
54 | /// } |
55 | /// ``` |
56 | /// |
57 | /// {@template flutter.widgets.AndroidView.lifetime} |
58 | /// The platform view's lifetime is the same as the lifetime of the [State] object for this widget. |
59 | /// When the [State] is disposed the platform view (and auxiliary resources) are lazily |
60 | /// released (some resources are immediately released and some by platform garbage collector). |
61 | /// A stateful widget's state is disposed when the widget is removed from the tree or when it is |
62 | /// moved within the tree. If the stateful widget has a key and it's only moved relative to its siblings, |
63 | /// or it has a [GlobalKey] and it's moved within the tree, it will not be disposed. |
64 | /// {@endtemplate} |
65 | class AndroidView extends StatefulWidget { |
66 | /// Creates a widget that embeds an Android view. |
67 | /// |
68 | /// {@template flutter.widgets.AndroidView.constructorArgs} |
69 | /// If `creationParams` is not null then `creationParamsCodec` must not be null. |
70 | /// {@endtemplate} |
71 | const AndroidView({ |
72 | super.key, |
73 | required this.viewType, |
74 | this.onPlatformViewCreated, |
75 | this.hitTestBehavior = PlatformViewHitTestBehavior.opaque, |
76 | this.layoutDirection, |
77 | this.gestureRecognizers, |
78 | this.creationParams, |
79 | this.creationParamsCodec, |
80 | this.clipBehavior = Clip.hardEdge, |
81 | }) : assert(creationParams == null || creationParamsCodec != null); |
82 | |
83 | /// The unique identifier for Android view type to be embedded by this widget. |
84 | /// |
85 | /// A [PlatformViewFactory](/javadoc/io/flutter/plugin/platform/PlatformViewFactory.html) |
86 | /// for this type must have been registered. |
87 | /// |
88 | /// See also: |
89 | /// |
90 | /// * [AndroidView] for an example of registering a platform view factory. |
91 | final String viewType; |
92 | |
93 | /// {@template flutter.widgets.AndroidView.onPlatformViewCreated} |
94 | /// Callback to invoke after the platform view has been created. |
95 | /// |
96 | /// May be null. |
97 | /// {@endtemplate} |
98 | final PlatformViewCreatedCallback? onPlatformViewCreated; |
99 | |
100 | /// {@template flutter.widgets.AndroidView.hitTestBehavior} |
101 | /// How this widget should behave during hit testing. |
102 | /// |
103 | /// This defaults to [PlatformViewHitTestBehavior.opaque]. |
104 | /// {@endtemplate} |
105 | final PlatformViewHitTestBehavior hitTestBehavior; |
106 | |
107 | /// {@template flutter.widgets.AndroidView.layoutDirection} |
108 | /// The text direction to use for the embedded view. |
109 | /// |
110 | /// If this is null, the ambient [Directionality] is used instead. |
111 | /// {@endtemplate} |
112 | final TextDirection? layoutDirection; |
113 | |
114 | /// Which gestures should be forwarded to the Android view. |
115 | /// |
116 | /// {@template flutter.widgets.AndroidView.gestureRecognizers.descHead} |
117 | /// The gesture recognizers built by factories in this set participate in the gesture arena for |
118 | /// each pointer that was put down on the widget. If any of these recognizers win the |
119 | /// gesture arena, the entire pointer event sequence starting from the pointer down event |
120 | /// will be dispatched to the platform view. |
121 | /// |
122 | /// When null, an empty set of gesture recognizer factories is used, in which case a pointer event sequence |
123 | /// will only be dispatched to the platform view if no other member of the arena claimed it. |
124 | /// {@endtemplate} |
125 | /// |
126 | /// For example, with the following setup vertical drags will not be dispatched to the Android |
127 | /// view as the vertical drag gesture is claimed by the parent [GestureDetector]. |
128 | /// |
129 | /// ```dart |
130 | /// GestureDetector( |
131 | /// onVerticalDragStart: (DragStartDetails d) {}, |
132 | /// child: const AndroidView( |
133 | /// viewType: 'webview', |
134 | /// ), |
135 | /// ) |
136 | /// ``` |
137 | /// |
138 | /// To get the [AndroidView] to claim the vertical drag gestures we can pass a vertical drag |
139 | /// gesture recognizer factory in [gestureRecognizers] e.g: |
140 | /// |
141 | /// ```dart |
142 | /// GestureDetector( |
143 | /// onVerticalDragStart: (DragStartDetails details) {}, |
144 | /// child: SizedBox( |
145 | /// width: 200.0, |
146 | /// height: 100.0, |
147 | /// child: AndroidView( |
148 | /// viewType: 'webview', |
149 | /// gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{ |
150 | /// Factory<OneSequenceGestureRecognizer>( |
151 | /// () => EagerGestureRecognizer(), |
152 | /// ), |
153 | /// }, |
154 | /// ), |
155 | /// ), |
156 | /// ) |
157 | /// ``` |
158 | /// |
159 | /// {@template flutter.widgets.AndroidView.gestureRecognizers.descFoot} |
160 | /// A platform view can be configured to consume all pointers that were put |
161 | /// down in its bounds by passing a factory for an [EagerGestureRecognizer] in |
162 | /// [gestureRecognizers]. [EagerGestureRecognizer] is a special gesture |
163 | /// recognizer that immediately claims the gesture after a pointer down event. |
164 | /// |
165 | /// The [gestureRecognizers] property must not contain more than one factory |
166 | /// with the same [Factory.type]. |
167 | /// |
168 | /// Changing [gestureRecognizers] results in rejection of any active gesture |
169 | /// arenas (if the platform view is actively participating in an arena). |
170 | /// {@endtemplate} |
171 | // We use OneSequenceGestureRecognizers as they support gesture arena teams. |
172 | // TODO(amirh): get a list of GestureRecognizers here. |
173 | // https://github.com/flutter/flutter/issues/20953 |
174 | final Set<Factory<OneSequenceGestureRecognizer>>? gestureRecognizers; |
175 | |
176 | /// Passed as the args argument of [PlatformViewFactory#create](/javadoc/io/flutter/plugin/platform/PlatformViewFactory.html#create-android.content.Context-int-java.lang.Object-) |
177 | /// |
178 | /// This can be used by plugins to pass constructor parameters to the embedded Android view. |
179 | final dynamic creationParams; |
180 | |
181 | /// The codec used to encode `creationParams` before sending it to the |
182 | /// 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-). |
183 | /// |
184 | /// This is typically one of: [StandardMessageCodec], [JSONMessageCodec], [StringCodec], or [BinaryCodec]. |
185 | /// |
186 | /// This must not be null if [creationParams] is not null. |
187 | final MessageCodec<dynamic>? creationParamsCodec; |
188 | |
189 | /// {@macro flutter.material.Material.clipBehavior} |
190 | /// |
191 | /// Defaults to [Clip.hardEdge]. |
192 | final Clip clipBehavior; |
193 | |
194 | @override |
195 | State<AndroidView> createState() => _AndroidViewState(); |
196 | } |
197 | |
198 | /// Common superclass for iOS and macOS platform views. |
199 | /// |
200 | /// Platform views are used to embed native views in the widget hierarchy, with |
201 | /// support for transforms, clips, and opacity similar to any other Flutter widget. |
202 | abstract class _DarwinView extends StatefulWidget { |
203 | /// Creates a widget that embeds a platform view. |
204 | /// |
205 | /// {@macro flutter.widgets.AndroidView.constructorArgs} |
206 | const _DarwinView({ |
207 | super.key, |
208 | required this.viewType, |
209 | this.onPlatformViewCreated, |
210 | this.hitTestBehavior = PlatformViewHitTestBehavior.opaque, |
211 | this.layoutDirection, |
212 | this.creationParams, |
213 | this.creationParamsCodec, |
214 | this.gestureRecognizers, |
215 | }) : assert(creationParams == null || creationParamsCodec != null); |
216 | |
217 | // TODO(amirh): reference the iOS API doc once available. |
218 | /// The unique identifier for iOS view type to be embedded by this widget. |
219 | /// |
220 | /// A PlatformViewFactory for this type must have been registered. |
221 | final String viewType; |
222 | |
223 | /// {@macro flutter.widgets.AndroidView.onPlatformViewCreated} |
224 | final PlatformViewCreatedCallback? onPlatformViewCreated; |
225 | |
226 | /// {@macro flutter.widgets.AndroidView.hitTestBehavior} |
227 | final PlatformViewHitTestBehavior hitTestBehavior; |
228 | |
229 | /// {@macro flutter.widgets.AndroidView.layoutDirection} |
230 | final TextDirection? layoutDirection; |
231 | |
232 | /// Passed as the `arguments` argument of [-\[FlutterPlatformViewFactory createWithFrame:viewIdentifier:arguments:\]](/ios-embedder/protocol_flutter_platform_view_factory-p.html#a4e3c4390cd6ebd982390635e9bca4edc) |
233 | /// |
234 | /// This can be used by plugins to pass constructor parameters to the embedded iOS view. |
235 | final dynamic creationParams; |
236 | |
237 | /// The codec used to encode `creationParams` before sending it to the |
238 | /// platform side. It should match the codec returned by [-\[FlutterPlatformViewFactory createArgsCodec:\]](/ios-embedder/protocol_flutter_platform_view_factory-p.html#a32c3c067cb45a83dfa720c74a0d5c93c) |
239 | /// |
240 | /// This is typically one of: [StandardMessageCodec], [JSONMessageCodec], [StringCodec], or [BinaryCodec]. |
241 | /// |
242 | /// This must not be null if [creationParams] is not null. |
243 | final MessageCodec<dynamic>? creationParamsCodec; |
244 | |
245 | /// Which gestures should be forwarded to the UIKit view. |
246 | /// |
247 | /// {@macro flutter.widgets.AndroidView.gestureRecognizers.descHead} |
248 | /// |
249 | /// For example, with the following setup vertical drags will not be dispatched to the UIKit |
250 | /// view as the vertical drag gesture is claimed by the parent [GestureDetector]. |
251 | /// |
252 | /// ```dart |
253 | /// GestureDetector( |
254 | /// onVerticalDragStart: (DragStartDetails details) {}, |
255 | /// child: const UiKitView( |
256 | /// viewType: 'webview', |
257 | /// ), |
258 | /// ) |
259 | /// ``` |
260 | /// |
261 | /// To get the [UiKitView] to claim the vertical drag gestures we can pass a vertical drag |
262 | /// gesture recognizer factory in [gestureRecognizers] e.g: |
263 | /// |
264 | /// ```dart |
265 | /// GestureDetector( |
266 | /// onVerticalDragStart: (DragStartDetails details) {}, |
267 | /// child: SizedBox( |
268 | /// width: 200.0, |
269 | /// height: 100.0, |
270 | /// child: UiKitView( |
271 | /// viewType: 'webview', |
272 | /// gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{ |
273 | /// Factory<OneSequenceGestureRecognizer>( |
274 | /// () => EagerGestureRecognizer(), |
275 | /// ), |
276 | /// }, |
277 | /// ), |
278 | /// ), |
279 | /// ) |
280 | /// ``` |
281 | /// |
282 | /// {@macro flutter.widgets.AndroidView.gestureRecognizers.descFoot} |
283 | // We use OneSequenceGestureRecognizers as they support gesture arena teams. |
284 | // TODO(amirh): get a list of GestureRecognizers here. |
285 | // https://github.com/flutter/flutter/issues/20953 |
286 | final Set<Factory<OneSequenceGestureRecognizer>>? gestureRecognizers; |
287 | } |
288 | |
289 | // TODO(amirh): describe the embedding mechanism. |
290 | // TODO(ychris): remove the documentation for conic path not supported once https://github.com/flutter/flutter/issues/35062 is resolved. |
291 | /// Embeds an iOS view in the Widget hierarchy. |
292 | /// |
293 | /// Embedding iOS views is an expensive operation and should be avoided when a Flutter |
294 | /// equivalent is possible. |
295 | /// |
296 | /// {@macro flutter.widgets.AndroidView.layout} |
297 | /// |
298 | /// {@macro flutter.widgets.AndroidView.gestures} |
299 | /// |
300 | /// {@macro flutter.widgets.AndroidView.lifetime} |
301 | /// |
302 | /// Construction of UIViews is done asynchronously, before the UIView is ready this widget paints |
303 | /// nothing while maintaining the same layout constraints. |
304 | /// |
305 | /// Clipping operations on a UiKitView can result slow performance. |
306 | /// If a conic path clipping is applied to a UIKitView, |
307 | /// a quad path is used to approximate the clip due to limitation of Quartz. |
308 | class UiKitView extends _DarwinView { |
309 | /// Creates a widget that embeds an iOS view. |
310 | /// |
311 | /// {@macro flutter.widgets.AndroidView.constructorArgs} |
312 | const UiKitView({ |
313 | super.key, |
314 | required super.viewType, |
315 | super.onPlatformViewCreated, |
316 | super.hitTestBehavior = PlatformViewHitTestBehavior.opaque, |
317 | super.layoutDirection, |
318 | super.creationParams, |
319 | super.creationParamsCodec, |
320 | super.gestureRecognizers, |
321 | }) : assert(creationParams == null || creationParamsCodec != null); |
322 | |
323 | @override |
324 | State<UiKitView> createState() => _UiKitViewState(); |
325 | } |
326 | |
327 | /// Widget that contains a macOS AppKit view. |
328 | /// |
329 | /// Embedding macOS views is an expensive operation and should be avoided where |
330 | /// a Flutter equivalent is possible. |
331 | /// |
332 | /// The platform view's lifetime is the same as the lifetime of the [State] |
333 | /// object for this widget. When the [State] is disposed the platform view (and |
334 | /// auxiliary resources) are lazily released (some resources are immediately |
335 | /// released and some by platform garbage collector). A stateful widget's state |
336 | /// is disposed when the widget is removed from the tree or when it is moved |
337 | /// within the tree. If the stateful widget has a key and it's only moved |
338 | /// relative to its siblings, or it has a [GlobalKey] and it's moved within the |
339 | /// tree, it will not be disposed. |
340 | /// |
341 | /// Construction of AppKitViews is done asynchronously, before the underlying |
342 | /// NSView is ready this widget paints nothing while maintaining the same |
343 | /// layout constraints. |
344 | class AppKitView extends _DarwinView { |
345 | /// Creates a widget that embeds a macOS AppKit NSView. |
346 | const AppKitView({ |
347 | super.key, |
348 | required super.viewType, |
349 | super.onPlatformViewCreated, |
350 | super.hitTestBehavior = PlatformViewHitTestBehavior.opaque, |
351 | super.layoutDirection, |
352 | super.creationParams, |
353 | super.creationParamsCodec, |
354 | super.gestureRecognizers, |
355 | }); |
356 | |
357 | @override |
358 | State<AppKitView> createState() => _AppKitViewState(); |
359 | } |
360 | |
361 | /// Callback signature for when the platform view's DOM element was created. |
362 | /// |
363 | /// [element] is the DOM element that was created. |
364 | /// |
365 | /// Also see [HtmlElementView.fromTagName] that uses this callback |
366 | /// signature. |
367 | typedef ElementCreatedCallback = void Function(Object element); |
368 | |
369 | /// Embeds an HTML element in the Widget hierarchy in Flutter Web. |
370 | /// |
371 | /// *NOTE*: This only works in Flutter Web. To embed web content on other |
372 | /// platforms, consider using the `flutter_webview` plugin. |
373 | /// |
374 | /// Embedding HTML is an expensive operation and should be avoided when a |
375 | /// Flutter equivalent is possible. |
376 | /// |
377 | /// The embedded HTML is painted just like any other Flutter widget and |
378 | /// transformations apply to it as well. This widget should only be used in |
379 | /// Flutter Web. |
380 | /// |
381 | /// {@macro flutter.widgets.AndroidView.layout} |
382 | /// |
383 | /// Due to security restrictions with cross-origin `<iframe>` elements, Flutter |
384 | /// cannot dispatch pointer events to an HTML view. If an `<iframe>` is the |
385 | /// target of an event, the window containing the `<iframe>` is not notified |
386 | /// of the event. In particular, this means that any pointer events which land |
387 | /// on an `<iframe>` will not be seen by Flutter, and so the HTML view cannot |
388 | /// participate in gesture detection with other widgets. |
389 | /// |
390 | /// The way we enable accessibility on Flutter for web is to have a full-page |
391 | /// button which waits for a double tap. Placing this full-page button in front |
392 | /// of the scene would cause platform views not to receive pointer events. The |
393 | /// tradeoff is that by placing the scene in front of the semantics placeholder |
394 | /// will cause platform views to block pointer events from reaching the |
395 | /// placeholder. This means that in order to enable accessibility, you must |
396 | /// double tap the app *outside of a platform view*. As a consequence, a |
397 | /// full-screen platform view will make it impossible to enable accessibility. |
398 | /// Make sure that your HTML views are sized no larger than necessary, or you |
399 | /// may cause difficulty for users trying to enable accessibility. |
400 | /// |
401 | /// {@macro flutter.widgets.AndroidView.lifetime} |
402 | class HtmlElementView extends StatelessWidget { |
403 | /// Creates a platform view for Flutter Web. |
404 | /// |
405 | /// `viewType` identifies the type of platform view to create. |
406 | const HtmlElementView({ |
407 | super.key, |
408 | required this.viewType, |
409 | this.onPlatformViewCreated, |
410 | this.creationParams, |
411 | }); |
412 | |
413 | /// Creates a platform view that creates a DOM element specified by [tagName]. |
414 | /// |
415 | /// [isVisible] indicates whether the view is visible to the user or not. |
416 | /// Setting this to false allows the rendering pipeline to perform extra |
417 | /// optimizations knowing that the view will not result in any pixels painted |
418 | /// on the screen. |
419 | /// |
420 | /// [onElementCreated] is called when the DOM element is created. It can be |
421 | /// used by the app to customize the element by adding attributes and styles. |
422 | factory HtmlElementView.fromTagName({ |
423 | Key? key, |
424 | required String tagName, |
425 | bool isVisible = true, |
426 | ElementCreatedCallback? onElementCreated, |
427 | }) => |
428 | HtmlElementViewImpl.createFromTagName( |
429 | key: key, |
430 | tagName: tagName, |
431 | isVisible: isVisible, |
432 | onElementCreated: onElementCreated, |
433 | ); |
434 | |
435 | /// The unique identifier for the HTML view type to be embedded by this widget. |
436 | /// |
437 | /// A PlatformViewFactory for this type must have been registered. |
438 | final String viewType; |
439 | |
440 | /// Callback to invoke after the platform view has been created. |
441 | /// |
442 | /// May be null. |
443 | final PlatformViewCreatedCallback? onPlatformViewCreated; |
444 | |
445 | /// Passed as the 2nd argument (i.e. `params`) of the registered view factory. |
446 | final Object? creationParams; |
447 | |
448 | @override |
449 | Widget build(BuildContext context) => buildImpl(context); |
450 | } |
451 | |
452 | class _AndroidViewState extends State<AndroidView> { |
453 | int? _id; |
454 | late AndroidViewController _controller; |
455 | TextDirection? _layoutDirection; |
456 | bool _initialized = false; |
457 | FocusNode? _focusNode; |
458 | |
459 | static final Set<Factory<OneSequenceGestureRecognizer>> _emptyRecognizersSet = |
460 | <Factory<OneSequenceGestureRecognizer>>{}; |
461 | |
462 | @override |
463 | Widget build(BuildContext context) { |
464 | return Focus( |
465 | focusNode: _focusNode, |
466 | onFocusChange: _onFocusChange, |
467 | child: _AndroidPlatformView( |
468 | controller: _controller, |
469 | hitTestBehavior: widget.hitTestBehavior, |
470 | gestureRecognizers: widget.gestureRecognizers ?? _emptyRecognizersSet, |
471 | clipBehavior: widget.clipBehavior, |
472 | ), |
473 | ); |
474 | } |
475 | |
476 | void _initializeOnce() { |
477 | if (_initialized) { |
478 | return; |
479 | } |
480 | _initialized = true; |
481 | _createNewAndroidView(); |
482 | _focusNode = FocusNode(debugLabel: 'AndroidView(id: $_id)' ); |
483 | } |
484 | |
485 | @override |
486 | void didChangeDependencies() { |
487 | super.didChangeDependencies(); |
488 | final TextDirection newLayoutDirection = _findLayoutDirection(); |
489 | final bool didChangeLayoutDirection = _layoutDirection != newLayoutDirection; |
490 | _layoutDirection = newLayoutDirection; |
491 | |
492 | _initializeOnce(); |
493 | if (didChangeLayoutDirection) { |
494 | // The native view will update asynchronously, in the meantime we don't want |
495 | // to block the framework. (so this is intentionally not awaiting). |
496 | _controller.setLayoutDirection(_layoutDirection!); |
497 | } |
498 | } |
499 | |
500 | @override |
501 | void didUpdateWidget(AndroidView oldWidget) { |
502 | super.didUpdateWidget(oldWidget); |
503 | |
504 | final TextDirection newLayoutDirection = _findLayoutDirection(); |
505 | final bool didChangeLayoutDirection = _layoutDirection != newLayoutDirection; |
506 | _layoutDirection = newLayoutDirection; |
507 | |
508 | if (widget.viewType != oldWidget.viewType) { |
509 | _controller.disposePostFrame(); |
510 | _createNewAndroidView(); |
511 | return; |
512 | } |
513 | |
514 | if (didChangeLayoutDirection) { |
515 | _controller.setLayoutDirection(_layoutDirection!); |
516 | } |
517 | } |
518 | |
519 | TextDirection _findLayoutDirection() { |
520 | assert(widget.layoutDirection != null || debugCheckHasDirectionality(context)); |
521 | return widget.layoutDirection ?? Directionality.of(context); |
522 | } |
523 | |
524 | @override |
525 | void dispose() { |
526 | _controller.dispose(); |
527 | _focusNode?.dispose(); |
528 | _focusNode = null; |
529 | super.dispose(); |
530 | } |
531 | |
532 | void _createNewAndroidView() { |
533 | _id = platformViewsRegistry.getNextPlatformViewId(); |
534 | _controller = PlatformViewsService.initAndroidView( |
535 | id: _id!, |
536 | viewType: widget.viewType, |
537 | layoutDirection: _layoutDirection!, |
538 | creationParams: widget.creationParams, |
539 | creationParamsCodec: widget.creationParamsCodec, |
540 | onFocus: () { |
541 | _focusNode!.requestFocus(); |
542 | }, |
543 | ); |
544 | if (widget.onPlatformViewCreated != null) { |
545 | _controller.addOnPlatformViewCreatedListener(widget.onPlatformViewCreated!); |
546 | } |
547 | } |
548 | |
549 | void _onFocusChange(bool isFocused) { |
550 | if (!_controller.isCreated) { |
551 | return; |
552 | } |
553 | if (!isFocused) { |
554 | _controller.clearFocus().catchError((dynamic e) { |
555 | if (e is MissingPluginException) { |
556 | // We land the framework part of Android platform views keyboard |
557 | // support before the engine part. There will be a commit range where |
558 | // clearFocus isn't implemented in the engine. When that happens we |
559 | // just swallow the error here. Once the engine part is rolled to the |
560 | // framework I'll remove this. |
561 | // TODO(amirh): remove this once the engine's clearFocus is rolled. |
562 | return; |
563 | } |
564 | }); |
565 | return; |
566 | } |
567 | SystemChannels.textInput.invokeMethod<void>( |
568 | 'TextInput.setPlatformViewClient' , |
569 | <String, dynamic>{'platformViewId' : _id}, |
570 | ).catchError((dynamic e) { |
571 | if (e is MissingPluginException) { |
572 | // We land the framework part of Android platform views keyboard |
573 | // support before the engine part. There will be a commit range where |
574 | // setPlatformViewClient isn't implemented in the engine. When that |
575 | // happens we just swallow the error here. Once the engine part is |
576 | // rolled to the framework I'll remove this. |
577 | // TODO(amirh): remove this once the engine's clearFocus is rolled. |
578 | return; |
579 | } |
580 | }); |
581 | } |
582 | } |
583 | |
584 | abstract class _DarwinViewState<PlatformViewT extends _DarwinView, ControllerT extends DarwinPlatformViewController, RenderT extends RenderDarwinPlatformView<ControllerT>, ViewT extends _DarwinPlatformView<ControllerT, RenderT>> extends State<PlatformViewT> { |
585 | ControllerT? _controller; |
586 | TextDirection? _layoutDirection; |
587 | bool _initialized = false; |
588 | |
589 | @visibleForTesting |
590 | FocusNode? focusNode; |
591 | |
592 | static final Set<Factory<OneSequenceGestureRecognizer>> _emptyRecognizersSet = |
593 | <Factory<OneSequenceGestureRecognizer>>{}; |
594 | |
595 | @override |
596 | Widget build(BuildContext context) { |
597 | final ControllerT? controller = _controller; |
598 | if (controller == null) { |
599 | return const SizedBox.expand(); |
600 | } |
601 | return Focus( |
602 | focusNode: focusNode, |
603 | onFocusChange: (bool isFocused) => _onFocusChange(isFocused, controller), |
604 | child: childPlatformView() |
605 | ); |
606 | } |
607 | |
608 | ViewT childPlatformView(); |
609 | |
610 | void _initializeOnce() { |
611 | if (_initialized) { |
612 | return; |
613 | } |
614 | _initialized = true; |
615 | _createNewUiKitView(); |
616 | } |
617 | |
618 | @override |
619 | void didChangeDependencies() { |
620 | super.didChangeDependencies(); |
621 | final TextDirection newLayoutDirection = _findLayoutDirection(); |
622 | final bool didChangeLayoutDirection = _layoutDirection != newLayoutDirection; |
623 | _layoutDirection = newLayoutDirection; |
624 | |
625 | _initializeOnce(); |
626 | if (didChangeLayoutDirection) { |
627 | // The native view will update asynchronously, in the meantime we don't want |
628 | // to block the framework. (so this is intentionally not awaiting). |
629 | _controller?.setLayoutDirection(_layoutDirection!); |
630 | } |
631 | } |
632 | |
633 | @override |
634 | void didUpdateWidget(PlatformViewT oldWidget) { |
635 | super.didUpdateWidget(oldWidget); |
636 | |
637 | final TextDirection newLayoutDirection = _findLayoutDirection(); |
638 | final bool didChangeLayoutDirection = _layoutDirection != newLayoutDirection; |
639 | _layoutDirection = newLayoutDirection; |
640 | |
641 | if (widget.viewType != oldWidget.viewType) { |
642 | _controller?.dispose(); |
643 | _controller = null; |
644 | focusNode?.dispose(); |
645 | focusNode = null; |
646 | _createNewUiKitView(); |
647 | return; |
648 | } |
649 | |
650 | if (didChangeLayoutDirection) { |
651 | _controller?.setLayoutDirection(_layoutDirection!); |
652 | } |
653 | } |
654 | |
655 | TextDirection _findLayoutDirection() { |
656 | assert(widget.layoutDirection != null || debugCheckHasDirectionality(context)); |
657 | return widget.layoutDirection ?? Directionality.of(context); |
658 | } |
659 | |
660 | @override |
661 | void dispose() { |
662 | _controller?.dispose(); |
663 | _controller = null; |
664 | focusNode?.dispose(); |
665 | focusNode = null; |
666 | super.dispose(); |
667 | } |
668 | |
669 | Future<void> _createNewUiKitView() async { |
670 | final int id = platformViewsRegistry.getNextPlatformViewId(); |
671 | final ControllerT controller = await createNewViewController( |
672 | id |
673 | ); |
674 | if (!mounted) { |
675 | controller.dispose(); |
676 | return; |
677 | } |
678 | widget.onPlatformViewCreated?.call(id); |
679 | setState(() { |
680 | _controller = controller; |
681 | focusNode = FocusNode(debugLabel: 'UiKitView(id: $id)' ); |
682 | }); |
683 | } |
684 | |
685 | Future<ControllerT> createNewViewController(int id); |
686 | |
687 | void _onFocusChange(bool isFocused, ControllerT controller) { |
688 | if (!isFocused) { |
689 | // Unlike Android, we do not need to send "clearFocus" channel message |
690 | // to the engine, because focusing on another view will automatically |
691 | // cancel the focus on the previously focused platform view. |
692 | return; |
693 | } |
694 | SystemChannels.textInput.invokeMethod<void>( |
695 | 'TextInput.setPlatformViewClient' , |
696 | <String, dynamic>{'platformViewId' : controller.id}, |
697 | ); |
698 | } |
699 | } |
700 | |
701 | class _UiKitViewState extends _DarwinViewState<UiKitView, UiKitViewController, RenderUiKitView, _UiKitPlatformView> { |
702 | @override |
703 | Future<UiKitViewController> createNewViewController(int id) async { |
704 | return PlatformViewsService.initUiKitView( |
705 | id: id, |
706 | viewType: widget.viewType, |
707 | layoutDirection: _layoutDirection!, |
708 | creationParams: widget.creationParams, |
709 | creationParamsCodec: widget.creationParamsCodec, |
710 | onFocus: () { |
711 | focusNode?.requestFocus(); |
712 | } |
713 | ); |
714 | } |
715 | |
716 | @override |
717 | _UiKitPlatformView childPlatformView() { |
718 | return _UiKitPlatformView( |
719 | controller: _controller!, |
720 | hitTestBehavior: widget.hitTestBehavior, |
721 | gestureRecognizers: widget.gestureRecognizers ?? _DarwinViewState._emptyRecognizersSet, |
722 | ); |
723 | } |
724 | } |
725 | |
726 | class _AppKitViewState extends _DarwinViewState<AppKitView, AppKitViewController, RenderAppKitView, _AppKitPlatformView> { |
727 | @override |
728 | Future<AppKitViewController> createNewViewController(int id) async { |
729 | return PlatformViewsService.initAppKitView( |
730 | id: id, |
731 | viewType: widget.viewType, |
732 | layoutDirection: _layoutDirection!, |
733 | creationParams: widget.creationParams, |
734 | creationParamsCodec: widget.creationParamsCodec, |
735 | onFocus: () { |
736 | focusNode?.requestFocus(); |
737 | } |
738 | ); |
739 | } |
740 | |
741 | @override |
742 | _AppKitPlatformView childPlatformView() { |
743 | return _AppKitPlatformView( |
744 | controller: _controller!, |
745 | hitTestBehavior: widget.hitTestBehavior, |
746 | gestureRecognizers: widget.gestureRecognizers ?? _DarwinViewState._emptyRecognizersSet, |
747 | ); |
748 | } |
749 | } |
750 | |
751 | class _AndroidPlatformView extends LeafRenderObjectWidget { |
752 | const _AndroidPlatformView({ |
753 | required this.controller, |
754 | required this.hitTestBehavior, |
755 | required this.gestureRecognizers, |
756 | this.clipBehavior = Clip.hardEdge, |
757 | }); |
758 | |
759 | final AndroidViewController controller; |
760 | final PlatformViewHitTestBehavior hitTestBehavior; |
761 | final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers; |
762 | final Clip clipBehavior; |
763 | |
764 | @override |
765 | RenderObject createRenderObject(BuildContext context) => |
766 | RenderAndroidView( |
767 | viewController: controller, |
768 | hitTestBehavior: hitTestBehavior, |
769 | gestureRecognizers: gestureRecognizers, |
770 | clipBehavior: clipBehavior, |
771 | ); |
772 | |
773 | @override |
774 | void updateRenderObject(BuildContext context, RenderAndroidView renderObject) { |
775 | renderObject.controller = controller; |
776 | renderObject.hitTestBehavior = hitTestBehavior; |
777 | renderObject.updateGestureRecognizers(gestureRecognizers); |
778 | renderObject.clipBehavior = clipBehavior; |
779 | } |
780 | } |
781 | |
782 | abstract class _DarwinPlatformView<TController extends DarwinPlatformViewController, TRender extends RenderDarwinPlatformView<TController>> extends LeafRenderObjectWidget { |
783 | const _DarwinPlatformView({ |
784 | required this.controller, |
785 | required this.hitTestBehavior, |
786 | required this.gestureRecognizers, |
787 | }); |
788 | |
789 | final TController controller; |
790 | final PlatformViewHitTestBehavior hitTestBehavior; |
791 | final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers; |
792 | |
793 | @override |
794 | @mustCallSuper |
795 | void updateRenderObject(BuildContext context, TRender renderObject) { |
796 | renderObject |
797 | ..viewController = controller |
798 | ..hitTestBehavior = hitTestBehavior |
799 | ..updateGestureRecognizers(gestureRecognizers); |
800 | } |
801 | } |
802 | |
803 | class _UiKitPlatformView extends _DarwinPlatformView<UiKitViewController, RenderUiKitView> { |
804 | const _UiKitPlatformView({required super.controller, required super.hitTestBehavior, required super.gestureRecognizers}); |
805 | |
806 | @override |
807 | RenderObject createRenderObject(BuildContext context) { |
808 | return RenderUiKitView( |
809 | viewController: controller, |
810 | hitTestBehavior: hitTestBehavior, |
811 | gestureRecognizers: gestureRecognizers, |
812 | ); |
813 | } |
814 | } |
815 | |
816 | class _AppKitPlatformView extends _DarwinPlatformView<AppKitViewController, RenderAppKitView> { |
817 | const _AppKitPlatformView({required super.controller, required super.hitTestBehavior, required super.gestureRecognizers}); |
818 | |
819 | @override |
820 | RenderObject createRenderObject(BuildContext context) { |
821 | return RenderAppKitView( |
822 | viewController: controller, |
823 | hitTestBehavior: hitTestBehavior, |
824 | gestureRecognizers: gestureRecognizers, |
825 | ); |
826 | } |
827 | } |
828 | |
829 | /// The parameters used to create a [PlatformViewController]. |
830 | /// |
831 | /// See also: |
832 | /// |
833 | /// * [CreatePlatformViewCallback] which uses this object to create a [PlatformViewController]. |
834 | class PlatformViewCreationParams { |
835 | |
836 | const PlatformViewCreationParams._({ |
837 | required this.id, |
838 | required this.viewType, |
839 | required this.onPlatformViewCreated, |
840 | required this.onFocusChanged, |
841 | }); |
842 | |
843 | /// The unique identifier for the new platform view. |
844 | /// |
845 | /// [PlatformViewController.viewId] should match this id. |
846 | final int id; |
847 | |
848 | /// The unique identifier for the type of platform view to be embedded. |
849 | /// |
850 | /// This viewType is used to tell the platform which type of view to |
851 | /// associate with the [id]. |
852 | final String viewType; |
853 | |
854 | /// Callback invoked after the platform view has been created. |
855 | final PlatformViewCreatedCallback onPlatformViewCreated; |
856 | |
857 | /// Callback invoked when the platform view's focus is changed on the platform side. |
858 | /// |
859 | /// The value is true when the platform view gains focus and false when it loses focus. |
860 | final ValueChanged<bool> onFocusChanged; |
861 | } |
862 | |
863 | /// A factory for a surface presenting a platform view as part of the widget hierarchy. |
864 | /// |
865 | /// The returned widget should present the platform view associated with `controller`. |
866 | /// |
867 | /// See also: |
868 | /// |
869 | /// * [PlatformViewSurface], a common widget for presenting platform views. |
870 | typedef PlatformViewSurfaceFactory = Widget Function(BuildContext context, PlatformViewController controller); |
871 | |
872 | /// Constructs a [PlatformViewController]. |
873 | /// |
874 | /// The [PlatformViewController.viewId] field of the created controller must match the value of the |
875 | /// params [PlatformViewCreationParams.id] field. |
876 | /// |
877 | /// See also: |
878 | /// |
879 | /// * [PlatformViewLink], which links a platform view with the Flutter framework. |
880 | typedef CreatePlatformViewCallback = PlatformViewController Function(PlatformViewCreationParams params); |
881 | |
882 | /// Links a platform view with the Flutter framework. |
883 | /// |
884 | /// Provides common functionality for embedding a platform view (e.g an android.view.View on Android) |
885 | /// with the Flutter framework. |
886 | /// |
887 | /// {@macro flutter.widgets.AndroidView.lifetime} |
888 | /// |
889 | /// To implement a new platform view widget, return this widget in the `build` method. |
890 | /// For example: |
891 | /// |
892 | /// ```dart |
893 | /// class FooPlatformView extends StatelessWidget { |
894 | /// const FooPlatformView({super.key}); |
895 | /// @override |
896 | /// Widget build(BuildContext context) { |
897 | /// return PlatformViewLink( |
898 | /// viewType: 'webview', |
899 | /// onCreatePlatformView: createFooWebView, |
900 | /// surfaceFactory: (BuildContext context, PlatformViewController controller) { |
901 | /// return PlatformViewSurface( |
902 | /// gestureRecognizers: gestureRecognizers, |
903 | /// controller: controller, |
904 | /// hitTestBehavior: PlatformViewHitTestBehavior.opaque, |
905 | /// ); |
906 | /// }, |
907 | /// ); |
908 | /// } |
909 | /// } |
910 | /// ``` |
911 | /// |
912 | /// The `surfaceFactory` and the `onCreatePlatformView` are only called when the |
913 | /// state of this widget is initialized, or when the `viewType` changes. |
914 | class PlatformViewLink extends StatefulWidget { |
915 | /// Construct a [PlatformViewLink] widget. |
916 | /// |
917 | /// See also: |
918 | /// |
919 | /// * [PlatformViewSurface] for details on the widget returned by `surfaceFactory`. |
920 | /// * [PlatformViewCreationParams] for how each parameter can be used when implementing `createPlatformView`. |
921 | const PlatformViewLink({ |
922 | super.key, |
923 | required PlatformViewSurfaceFactory surfaceFactory, |
924 | required CreatePlatformViewCallback onCreatePlatformView, |
925 | required this.viewType, |
926 | }) : _surfaceFactory = surfaceFactory, |
927 | _onCreatePlatformView = onCreatePlatformView; |
928 | |
929 | |
930 | final PlatformViewSurfaceFactory _surfaceFactory; |
931 | final CreatePlatformViewCallback _onCreatePlatformView; |
932 | |
933 | /// The unique identifier for the view type to be embedded. |
934 | /// |
935 | /// Typically, this viewType has already been registered on the platform side. |
936 | final String viewType; |
937 | |
938 | @override |
939 | State<StatefulWidget> createState() => _PlatformViewLinkState(); |
940 | } |
941 | |
942 | class _PlatformViewLinkState extends State<PlatformViewLink> { |
943 | int? _id; |
944 | PlatformViewController? _controller; |
945 | bool _platformViewCreated = false; |
946 | Widget? _surface; |
947 | FocusNode? _focusNode; |
948 | |
949 | @override |
950 | Widget build(BuildContext context) { |
951 | final PlatformViewController? controller = _controller; |
952 | if (controller == null) { |
953 | return const SizedBox.expand(); |
954 | } |
955 | if (!_platformViewCreated) { |
956 | // Depending on the implementation, the first non-empty size can be used |
957 | // to size the platform view. |
958 | return _PlatformViewPlaceHolder(onLayout: (Size size, Offset position) { |
959 | if (controller.awaitingCreation && !size.isEmpty) { |
960 | controller.create(size: size, position: position); |
961 | } |
962 | }); |
963 | } |
964 | _surface ??= widget._surfaceFactory(context, controller); |
965 | return Focus( |
966 | focusNode: _focusNode, |
967 | onFocusChange: _handleFrameworkFocusChanged, |
968 | child: _surface!, |
969 | ); |
970 | } |
971 | |
972 | @override |
973 | void initState() { |
974 | _focusNode = FocusNode(debugLabel: 'PlatformView(id: $_id)' ); |
975 | _initialize(); |
976 | super.initState(); |
977 | } |
978 | |
979 | @override |
980 | void didUpdateWidget(PlatformViewLink oldWidget) { |
981 | super.didUpdateWidget(oldWidget); |
982 | |
983 | if (widget.viewType != oldWidget.viewType) { |
984 | _controller?.disposePostFrame(); |
985 | // The _surface has to be recreated as its controller is disposed. |
986 | // Setting _surface to null will trigger its creation in build(). |
987 | _surface = null; |
988 | _initialize(); |
989 | } |
990 | } |
991 | |
992 | void _initialize() { |
993 | _id = platformViewsRegistry.getNextPlatformViewId(); |
994 | _controller = widget._onCreatePlatformView( |
995 | PlatformViewCreationParams._( |
996 | id: _id!, |
997 | viewType: widget.viewType, |
998 | onPlatformViewCreated: _onPlatformViewCreated, |
999 | onFocusChanged: _handlePlatformFocusChanged, |
1000 | ), |
1001 | ); |
1002 | } |
1003 | |
1004 | void _onPlatformViewCreated(int id) { |
1005 | if (mounted) { |
1006 | setState(() { |
1007 | _platformViewCreated = true; |
1008 | }); |
1009 | } |
1010 | } |
1011 | |
1012 | void _handleFrameworkFocusChanged(bool isFocused) { |
1013 | if (!isFocused) { |
1014 | _controller?.clearFocus(); |
1015 | } |
1016 | SystemChannels.textInput.invokeMethod<void>( |
1017 | 'TextInput.setPlatformViewClient' , |
1018 | <String, dynamic>{'platformViewId' : _id}, |
1019 | ); |
1020 | } |
1021 | |
1022 | void _handlePlatformFocusChanged(bool isFocused) { |
1023 | if (isFocused) { |
1024 | _focusNode!.requestFocus(); |
1025 | } |
1026 | } |
1027 | |
1028 | @override |
1029 | void dispose() { |
1030 | _controller?.dispose(); |
1031 | _controller = null; |
1032 | _focusNode?.dispose(); |
1033 | _focusNode = null; |
1034 | super.dispose(); |
1035 | } |
1036 | } |
1037 | |
1038 | /// Integrates a platform view with Flutter's compositor, touch, and semantics subsystems. |
1039 | /// |
1040 | /// The compositor integration is done by adding a [PlatformViewLayer] to the layer tree. [PlatformViewSurface] |
1041 | /// isn't supported on all platforms (e.g on Android platform views can be composited by using a [TextureLayer] or |
1042 | /// [AndroidViewSurface]). |
1043 | /// Custom Flutter embedders can support [PlatformViewLayer]s by implementing a SystemCompositor. |
1044 | /// |
1045 | /// The widget fills all available space, the parent of this object must provide bounded layout |
1046 | /// constraints. |
1047 | /// |
1048 | /// If the associated platform view is not created the [PlatformViewSurface] does not paint any contents. |
1049 | /// |
1050 | /// See also: |
1051 | /// |
1052 | /// * [AndroidView] which embeds an Android platform view in the widget hierarchy using a [TextureLayer]. |
1053 | /// * [UiKitView] which embeds an iOS platform view in the widget hierarchy. |
1054 | // TODO(amirh): Link to the embedder's system compositor documentation once available. |
1055 | class PlatformViewSurface extends LeafRenderObjectWidget { |
1056 | |
1057 | /// Construct a [PlatformViewSurface]. |
1058 | const PlatformViewSurface({ |
1059 | super.key, |
1060 | required this.controller, |
1061 | required this.hitTestBehavior, |
1062 | required this.gestureRecognizers, |
1063 | }); |
1064 | |
1065 | /// The controller for the platform view integrated by this [PlatformViewSurface]. |
1066 | /// |
1067 | /// [PlatformViewController] is used for dispatching touch events to the platform view. |
1068 | /// [PlatformViewController.viewId] identifies the platform view whose contents are painted by this widget. |
1069 | final PlatformViewController controller; |
1070 | |
1071 | /// Which gestures should be forwarded to the PlatformView. |
1072 | /// |
1073 | /// {@macro flutter.widgets.AndroidView.gestureRecognizers.descHead} |
1074 | /// |
1075 | /// For example, with the following setup vertical drags will not be dispatched to the platform view |
1076 | /// as the vertical drag gesture is claimed by the parent [GestureDetector]. |
1077 | /// |
1078 | /// ```dart |
1079 | /// GestureDetector( |
1080 | /// onVerticalDragStart: (DragStartDetails details) { }, |
1081 | /// child: PlatformViewSurface( |
1082 | /// gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{}, |
1083 | /// controller: _controller, |
1084 | /// hitTestBehavior: PlatformViewHitTestBehavior.opaque, |
1085 | /// ), |
1086 | /// ) |
1087 | /// ``` |
1088 | /// |
1089 | /// To get the [PlatformViewSurface] to claim the vertical drag gestures we can pass a vertical drag |
1090 | /// gesture recognizer factory in [gestureRecognizers] e.g: |
1091 | /// |
1092 | /// ```dart |
1093 | /// GestureDetector( |
1094 | /// onVerticalDragStart: (DragStartDetails details) { }, |
1095 | /// child: SizedBox( |
1096 | /// width: 200.0, |
1097 | /// height: 100.0, |
1098 | /// child: PlatformViewSurface( |
1099 | /// gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{ |
1100 | /// Factory<OneSequenceGestureRecognizer>( |
1101 | /// () => EagerGestureRecognizer(), |
1102 | /// ), |
1103 | /// }, |
1104 | /// controller: _controller, |
1105 | /// hitTestBehavior: PlatformViewHitTestBehavior.opaque, |
1106 | /// ), |
1107 | /// ), |
1108 | /// ) |
1109 | /// ``` |
1110 | /// |
1111 | /// {@macro flutter.widgets.AndroidView.gestureRecognizers.descFoot} |
1112 | // We use OneSequenceGestureRecognizers as they support gesture arena teams. |
1113 | // TODO(amirh): get a list of GestureRecognizers here. |
1114 | // https://github.com/flutter/flutter/issues/20953 |
1115 | final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers; |
1116 | |
1117 | /// {@macro flutter.widgets.AndroidView.hitTestBehavior} |
1118 | final PlatformViewHitTestBehavior hitTestBehavior; |
1119 | |
1120 | @override |
1121 | RenderObject createRenderObject(BuildContext context) { |
1122 | return PlatformViewRenderBox(controller: controller, gestureRecognizers: gestureRecognizers, hitTestBehavior: hitTestBehavior); |
1123 | } |
1124 | |
1125 | @override |
1126 | void updateRenderObject(BuildContext context, PlatformViewRenderBox renderObject) { |
1127 | renderObject |
1128 | ..controller = controller |
1129 | ..hitTestBehavior = hitTestBehavior |
1130 | ..updateGestureRecognizers(gestureRecognizers); |
1131 | } |
1132 | } |
1133 | |
1134 | /// Integrates an Android view with Flutter's compositor, touch, and semantics subsystems. |
1135 | /// |
1136 | /// The compositor integration is done by adding a [TextureLayer] to the layer tree. |
1137 | /// |
1138 | /// The parent of this object must provide bounded layout constraints. |
1139 | /// |
1140 | /// If the associated platform view is not created, the [AndroidViewSurface] does not paint any contents. |
1141 | /// |
1142 | /// When possible, you may want to use [AndroidView] directly, since it requires less boilerplate code |
1143 | /// than [AndroidViewSurface], and there's no difference in performance, or other trade-off(s). |
1144 | /// |
1145 | /// See also: |
1146 | /// |
1147 | /// * [AndroidView] which embeds an Android platform view in the widget hierarchy. |
1148 | /// * [UiKitView] which embeds an iOS platform view in the widget hierarchy. |
1149 | class AndroidViewSurface extends StatefulWidget { |
1150 | /// Construct an `AndroidPlatformViewSurface`. |
1151 | const AndroidViewSurface({ |
1152 | super.key, |
1153 | required this.controller, |
1154 | required this.hitTestBehavior, |
1155 | required this.gestureRecognizers, |
1156 | }); |
1157 | |
1158 | /// The controller for the platform view integrated by this [AndroidViewSurface]. |
1159 | /// |
1160 | /// See [PlatformViewSurface.controller] for details. |
1161 | final AndroidViewController controller; |
1162 | |
1163 | /// Which gestures should be forwarded to the PlatformView. |
1164 | /// |
1165 | /// See [PlatformViewSurface.gestureRecognizers] for details. |
1166 | final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers; |
1167 | |
1168 | /// {@macro flutter.widgets.AndroidView.hitTestBehavior} |
1169 | final PlatformViewHitTestBehavior hitTestBehavior; |
1170 | |
1171 | @override |
1172 | State<StatefulWidget> createState() { |
1173 | return _AndroidViewSurfaceState(); |
1174 | } |
1175 | } |
1176 | |
1177 | class _AndroidViewSurfaceState extends State<AndroidViewSurface> { |
1178 | @override |
1179 | void initState() { |
1180 | super.initState(); |
1181 | if (!widget.controller.isCreated) { |
1182 | // Schedule a rebuild once creation is complete and the final display |
1183 | // type is known. |
1184 | widget.controller.addOnPlatformViewCreatedListener(_onPlatformViewCreated); |
1185 | } |
1186 | } |
1187 | |
1188 | @override |
1189 | void dispose() { |
1190 | widget.controller.removeOnPlatformViewCreatedListener(_onPlatformViewCreated); |
1191 | super.dispose(); |
1192 | } |
1193 | |
1194 | @override |
1195 | Widget build(BuildContext context) { |
1196 | if (widget.controller.requiresViewComposition) { |
1197 | return _PlatformLayerBasedAndroidViewSurface( |
1198 | controller: widget.controller, |
1199 | hitTestBehavior: widget.hitTestBehavior, |
1200 | gestureRecognizers: widget.gestureRecognizers, |
1201 | ); |
1202 | } else { |
1203 | return _TextureBasedAndroidViewSurface( |
1204 | controller: widget.controller, |
1205 | hitTestBehavior: widget.hitTestBehavior, |
1206 | gestureRecognizers: widget.gestureRecognizers, |
1207 | ); |
1208 | } |
1209 | } |
1210 | |
1211 | void _onPlatformViewCreated(int _) { |
1212 | // Trigger a re-build based on the current controller state. |
1213 | setState(() {}); |
1214 | } |
1215 | } |
1216 | |
1217 | // Displays an Android platform view via GL texture. |
1218 | class _TextureBasedAndroidViewSurface extends PlatformViewSurface { |
1219 | const _TextureBasedAndroidViewSurface({ |
1220 | required AndroidViewController super.controller, |
1221 | required super.hitTestBehavior, |
1222 | required super.gestureRecognizers, |
1223 | }); |
1224 | |
1225 | @override |
1226 | RenderObject createRenderObject(BuildContext context) { |
1227 | final AndroidViewController viewController = controller as AndroidViewController; |
1228 | // Use GL texture based composition. |
1229 | // App should use GL texture unless they require to embed a SurfaceView. |
1230 | final RenderAndroidView renderBox = RenderAndroidView( |
1231 | viewController: viewController, |
1232 | gestureRecognizers: gestureRecognizers, |
1233 | hitTestBehavior: hitTestBehavior, |
1234 | ); |
1235 | viewController.pointTransformer = |
1236 | (Offset position) => renderBox.globalToLocal(position); |
1237 | return renderBox; |
1238 | } |
1239 | } |
1240 | |
1241 | class _PlatformLayerBasedAndroidViewSurface extends PlatformViewSurface { |
1242 | const _PlatformLayerBasedAndroidViewSurface({ |
1243 | required AndroidViewController super.controller, |
1244 | required super.hitTestBehavior, |
1245 | required super.gestureRecognizers, |
1246 | }); |
1247 | |
1248 | @override |
1249 | RenderObject createRenderObject(BuildContext context) { |
1250 | final AndroidViewController viewController = controller as AndroidViewController; |
1251 | final PlatformViewRenderBox renderBox = |
1252 | super.createRenderObject(context) as PlatformViewRenderBox; |
1253 | viewController.pointTransformer = |
1254 | (Offset position) => renderBox.globalToLocal(position); |
1255 | return renderBox; |
1256 | } |
1257 | } |
1258 | |
1259 | /// A callback used to notify the size of the platform view placeholder. |
1260 | /// This size is the initial size of the platform view. |
1261 | typedef _OnLayoutCallback = void Function(Size size, Offset position); |
1262 | |
1263 | /// A [RenderBox] that notifies its size to the owner after a layout. |
1264 | class _PlatformViewPlaceholderBox extends RenderConstrainedBox { |
1265 | _PlatformViewPlaceholderBox({ |
1266 | required this.onLayout, |
1267 | }) : super(additionalConstraints: const BoxConstraints.tightFor( |
1268 | width: double.infinity, |
1269 | height: double.infinity, |
1270 | )); |
1271 | |
1272 | _OnLayoutCallback onLayout; |
1273 | |
1274 | @override |
1275 | void performLayout() { |
1276 | super.performLayout(); |
1277 | // A call to `localToGlobal` requires waiting for a frame to render first. |
1278 | SchedulerBinding.instance.addPostFrameCallback((_) { |
1279 | onLayout(size, localToGlobal(Offset.zero)); |
1280 | }, debugLabel: 'PlatformViewPlaceholderBox.onLayout' ); |
1281 | } |
1282 | } |
1283 | |
1284 | /// When a platform view is in the widget hierarchy, this widget is used to capture |
1285 | /// the size of the platform view after the first layout. |
1286 | /// This placeholder is basically a [SizedBox.expand] with a [onLayout] callback to |
1287 | /// notify the size of the render object to its parent. |
1288 | class _PlatformViewPlaceHolder extends SingleChildRenderObjectWidget { |
1289 | const _PlatformViewPlaceHolder({ |
1290 | required this.onLayout, |
1291 | }); |
1292 | |
1293 | final _OnLayoutCallback onLayout; |
1294 | |
1295 | @override |
1296 | _PlatformViewPlaceholderBox createRenderObject(BuildContext context) { |
1297 | return _PlatformViewPlaceholderBox(onLayout: onLayout); |
1298 | } |
1299 | |
1300 | @override |
1301 | void updateRenderObject(BuildContext context, _PlatformViewPlaceholderBox renderObject) { |
1302 | renderObject.onLayout = onLayout; |
1303 | } |
1304 | } |
1305 | |
1306 | extension on PlatformViewController { |
1307 | /// Disposes the controller in a post-frame callback, to allow other widgets to |
1308 | /// remove their listeners before the controller is disposed. |
1309 | void disposePostFrame() { |
1310 | SchedulerBinding.instance.addPostFrameCallback((_) { |
1311 | dispose(); |
1312 | }, debugLabel: 'PlatformViewController.dispose' ); |
1313 | } |
1314 | } |
1315 | |