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 'package:flutter/material.dart'; |
6 | /// |
7 | /// @docImport 'message_codecs.dart'; |
8 | library; |
9 | |
10 | import 'dart:async'; |
11 | import 'dart:ui'; |
12 | |
13 | import 'package:flutter/foundation.dart'; |
14 | import 'package:flutter/gestures.dart'; |
15 | |
16 | import 'message_codec.dart'; |
17 | import 'system_channels.dart'; |
18 | |
19 | export 'dart:ui' show Offset, Size, TextDirection, VoidCallback; |
20 | |
21 | export 'package:flutter/gestures.dart' show PointerEvent; |
22 | |
23 | export 'message_codec.dart' show MessageCodec; |
24 | |
25 | /// Converts a given point from the global coordinate system in logical pixels |
26 | /// to the local coordinate system for a box. |
27 | /// |
28 | /// Used by [AndroidViewController.pointTransformer]. |
29 | typedef PointTransformer = Offset Function(Offset position); |
30 | |
31 | /// The [PlatformViewsRegistry] responsible for generating unique identifiers for platform views. |
32 | final PlatformViewsRegistry platformViewsRegistry = PlatformViewsRegistry._instance(); |
33 | |
34 | /// A registry responsible for generating unique identifier for platform views. |
35 | /// |
36 | /// A Flutter application has a single [PlatformViewsRegistry] which can be accesses |
37 | /// through the [platformViewsRegistry] getter. |
38 | class PlatformViewsRegistry { |
39 | PlatformViewsRegistry._instance(); |
40 | |
41 | // Always non-negative. The id value -1 is used in the accessibility bridge |
42 | // to indicate the absence of a platform view. |
43 | int _nextPlatformViewId = 0; |
44 | |
45 | /// Allocates a unique identifier for a platform view. |
46 | /// |
47 | /// A platform view identifier can refer to a platform view that was never created, |
48 | /// a platform view that was disposed, or a platform view that is alive. |
49 | /// |
50 | /// Typically a platform view identifier is passed to a platform view widget |
51 | /// which creates the platform view and manages its lifecycle. |
52 | int getNextPlatformViewId() { |
53 | // On the Android side, the interface exposed to users uses 32-bit integers. |
54 | // See https://github.com/flutter/engine/pull/39476 for more details. |
55 | |
56 | // We can safely assume that a Flutter application will not require more |
57 | // than MAX_INT32 platform views during its lifetime. |
58 | const int MAX_INT32 = 0x7FFFFFFF; |
59 | assert(_nextPlatformViewId <= MAX_INT32); |
60 | return _nextPlatformViewId++; |
61 | } |
62 | } |
63 | |
64 | /// Callback signature for when a platform view was created. |
65 | /// |
66 | /// The `id` parameter is the platform view's unique identifier. |
67 | typedef PlatformViewCreatedCallback = void Function(int id); |
68 | |
69 | /// Provides access to the platform views service. |
70 | /// |
71 | /// This service allows creating and controlling platform-specific views. |
72 | class PlatformViewsService { |
73 | PlatformViewsService._() { |
74 | SystemChannels.platform_views.setMethodCallHandler(_onMethodCall); |
75 | } |
76 | |
77 | static final PlatformViewsService _instance = PlatformViewsService._(); |
78 | |
79 | Future<void> _onMethodCall(MethodCall call) { |
80 | switch (call.method) { |
81 | case 'viewFocused': |
82 | final int id = call.arguments as int; |
83 | if (_focusCallbacks.containsKey(id)) { |
84 | _focusCallbacks[id]!(); |
85 | } |
86 | default: |
87 | throw UnimplementedError( |
88 | "${call.method} was invoked but isn't implemented by PlatformViewsService", |
89 | ); |
90 | } |
91 | return Future<void>.value(); |
92 | } |
93 | |
94 | /// Maps platform view IDs to focus callbacks. |
95 | /// |
96 | /// The callbacks are invoked when the platform view asks to be focused. |
97 | final Map<int, VoidCallback> _focusCallbacks = <int, VoidCallback>{}; |
98 | |
99 | /// {@template flutter.services.PlatformViewsService.initAndroidView} |
100 | /// Creates a controller for a new Android view. |
101 | /// |
102 | /// The `id` argument is an unused unique identifier generated with |
103 | /// [platformViewsRegistry]. |
104 | /// |
105 | /// The `viewType` argument is the identifier of the Android view type to be |
106 | /// created, a factory for this view type must have been registered on the |
107 | /// platform side. Platform view factories are typically registered by plugin |
108 | /// code. Plugins can register a platform view factory with |
109 | /// [PlatformViewRegistry#registerViewFactory](/javadoc/io/flutter/plugin/platform/PlatformViewRegistry.html#registerViewFactory-java.lang.String-io.flutter.plugin.platform.PlatformViewFactory-). |
110 | /// |
111 | /// The `creationParams` argument will be passed as the args argument of |
112 | /// [PlatformViewFactory#create](/javadoc/io/flutter/plugin/platform/PlatformViewFactory.html#create-android.content.Context-int-java.lang.Object-) |
113 | /// |
114 | /// The `creationParamsCodec` argument is the codec used to encode |
115 | /// `creationParams` before sending it to the platform side. It should match |
116 | /// the codec passed to the constructor of |
117 | /// [PlatformViewFactory](/javadoc/io/flutter/plugin/platform/PlatformViewFactory.html#PlatformViewFactory-io.flutter.plugin.common.MessageCodec-). |
118 | /// This is typically one of: [StandardMessageCodec], [JSONMessageCodec], |
119 | /// [StringCodec], or [BinaryCodec]. |
120 | /// |
121 | /// The `onFocus` argument is a callback that will be invoked when the Android |
122 | /// View asks to get the input focus. |
123 | /// |
124 | /// The Android view will only be created after |
125 | /// [AndroidViewController.setSize] is called for the first time. |
126 | /// |
127 | /// If `creationParams` is non null then `creationParamsCodec` must not be |
128 | /// null. |
129 | /// {@endtemplate} |
130 | /// |
131 | /// This attempts to use the newest and most efficient platform view |
132 | /// implementation when possible. In cases where that is not supported, it |
133 | /// falls back to using Virtual Display. |
134 | static AndroidViewController initAndroidView({ |
135 | required int id, |
136 | required String viewType, |
137 | required TextDirection layoutDirection, |
138 | dynamic creationParams, |
139 | MessageCodec<dynamic>? creationParamsCodec, |
140 | VoidCallback? onFocus, |
141 | }) { |
142 | assert(creationParams == null || creationParamsCodec != null); |
143 | |
144 | final TextureAndroidViewController controller = TextureAndroidViewController._( |
145 | viewId: id, |
146 | viewType: viewType, |
147 | layoutDirection: layoutDirection, |
148 | creationParams: creationParams, |
149 | creationParamsCodec: creationParamsCodec, |
150 | ); |
151 | |
152 | _instance._focusCallbacks[id] = onFocus ?? () {}; |
153 | return controller; |
154 | } |
155 | |
156 | /// {@macro flutter.services.PlatformViewsService.initAndroidView} |
157 | /// |
158 | /// This attempts to use the newest and most efficient platform view |
159 | /// implementation when possible. In cases where that is not supported, it |
160 | /// falls back to using Hybrid Composition, which is the mode used by |
161 | /// [initExpensiveAndroidView]. |
162 | static SurfaceAndroidViewController initSurfaceAndroidView({ |
163 | required int id, |
164 | required String viewType, |
165 | required TextDirection layoutDirection, |
166 | dynamic creationParams, |
167 | MessageCodec<dynamic>? creationParamsCodec, |
168 | VoidCallback? onFocus, |
169 | }) { |
170 | assert(creationParams == null || creationParamsCodec != null); |
171 | |
172 | final SurfaceAndroidViewController controller = SurfaceAndroidViewController._( |
173 | viewId: id, |
174 | viewType: viewType, |
175 | layoutDirection: layoutDirection, |
176 | creationParams: creationParams, |
177 | creationParamsCodec: creationParamsCodec, |
178 | ); |
179 | _instance._focusCallbacks[id] = onFocus ?? () {}; |
180 | return controller; |
181 | } |
182 | |
183 | /// {@macro flutter.services.PlatformViewsService.initAndroidView} |
184 | /// |
185 | /// When this factory is used, the Android view and Flutter widgets are |
186 | /// composed at the Android view hierarchy level. |
187 | /// |
188 | /// Using this method has a performance cost on devices running Android 9 or |
189 | /// earlier, or on underpowered devices. In most situations, you should use |
190 | /// [initAndroidView] or [initSurfaceAndroidView] instead. |
191 | static ExpensiveAndroidViewController initExpensiveAndroidView({ |
192 | required int id, |
193 | required String viewType, |
194 | required TextDirection layoutDirection, |
195 | dynamic creationParams, |
196 | MessageCodec<dynamic>? creationParamsCodec, |
197 | VoidCallback? onFocus, |
198 | }) { |
199 | final ExpensiveAndroidViewController controller = ExpensiveAndroidViewController._( |
200 | viewId: id, |
201 | viewType: viewType, |
202 | layoutDirection: layoutDirection, |
203 | creationParams: creationParams, |
204 | creationParamsCodec: creationParamsCodec, |
205 | ); |
206 | |
207 | _instance._focusCallbacks[id] = onFocus ?? () {}; |
208 | return controller; |
209 | } |
210 | |
211 | /// {@macro flutter.services.PlatformViewsService.initAndroidView} |
212 | /// |
213 | /// When this factory is used, the Android view and Flutter widgets are |
214 | /// composed at the Android view hierarchy level. |
215 | /// |
216 | /// This functionality is only supported on Android devices running Vulkan on |
217 | /// API 34 or newer. |
218 | static HybridAndroidViewController initHybridAndroidView({ |
219 | required int id, |
220 | required String viewType, |
221 | required TextDirection layoutDirection, |
222 | dynamic creationParams, |
223 | MessageCodec<dynamic>? creationParamsCodec, |
224 | VoidCallback? onFocus, |
225 | }) { |
226 | final HybridAndroidViewController controller = HybridAndroidViewController._( |
227 | viewId: id, |
228 | viewType: viewType, |
229 | layoutDirection: layoutDirection, |
230 | creationParams: creationParams, |
231 | creationParamsCodec: creationParamsCodec, |
232 | ); |
233 | |
234 | _instance._focusCallbacks[id] = onFocus ?? () {}; |
235 | return controller; |
236 | } |
237 | |
238 | /// Factory method to create a `UiKitView`. |
239 | /// |
240 | /// The `id` parameter is an unused unique identifier generated with |
241 | /// [platformViewsRegistry]. |
242 | /// |
243 | /// The `viewType` parameter is the identifier of the iOS view type to be |
244 | /// created, a factory for this view type must have been registered on the |
245 | /// platform side. Platform view factories are typically registered by plugin |
246 | /// code. |
247 | /// |
248 | /// The `onFocus` parameter is a callback that will be invoked when the UIKit |
249 | /// view asks to get the input focus. If `creationParams` is non null then |
250 | /// `creationParamsCodec` must not be null. |
251 | /// |
252 | /// See: https://docs.flutter.dev/platform-integration/ios/platform-views |
253 | static Future<UiKitViewController> initUiKitView({ |
254 | required int id, |
255 | required String viewType, |
256 | required TextDirection layoutDirection, |
257 | dynamic creationParams, |
258 | MessageCodec<dynamic>? creationParamsCodec, |
259 | VoidCallback? onFocus, |
260 | }) async { |
261 | assert(creationParams == null || creationParamsCodec != null); |
262 | |
263 | // TODO(amirh): pass layoutDirection once the system channel supports it. |
264 | // https://github.com/flutter/flutter/issues/133682 |
265 | final Map<String, dynamic> args = <String, dynamic>{'id': id, 'viewType': viewType}; |
266 | if (creationParams != null) { |
267 | final ByteData paramsByteData = creationParamsCodec!.encodeMessage(creationParams)!; |
268 | args['params'] = Uint8List.view(paramsByteData.buffer, 0, paramsByteData.lengthInBytes); |
269 | } |
270 | await SystemChannels.platform_views.invokeMethod<void>('create', args); |
271 | if (onFocus != null) { |
272 | _instance._focusCallbacks[id] = onFocus; |
273 | } |
274 | return UiKitViewController._(id, layoutDirection); |
275 | } |
276 | |
277 | // TODO(cbracken): Write and link website docs. https://github.com/flutter/website/issues/9424. |
278 | // |
279 | /// Factory method to create an `AppKitView`. |
280 | /// |
281 | /// The `id` parameter is an unused unique identifier generated with |
282 | /// [platformViewsRegistry]. |
283 | /// |
284 | /// The `viewType` parameter is the identifier of the iOS view type to be |
285 | /// created, a factory for this view type must have been registered on the |
286 | /// platform side. Platform view factories are typically registered by plugin |
287 | /// code. |
288 | /// |
289 | /// The `onFocus` parameter is a callback that will be invoked when the UIKit |
290 | /// view asks to get the input focus. If `creationParams` is non null then |
291 | /// `creationParamsCodec` must not be null. |
292 | static Future<AppKitViewController> initAppKitView({ |
293 | required int id, |
294 | required String viewType, |
295 | required TextDirection layoutDirection, |
296 | dynamic creationParams, |
297 | MessageCodec<dynamic>? creationParamsCodec, |
298 | VoidCallback? onFocus, |
299 | }) async { |
300 | assert(creationParams == null || creationParamsCodec != null); |
301 | |
302 | // TODO(amirh): pass layoutDirection once the system channel supports it. |
303 | // https://github.com/flutter/flutter/issues/133682 |
304 | final Map<String, dynamic> args = <String, dynamic>{'id': id, 'viewType': viewType}; |
305 | if (creationParams != null) { |
306 | final ByteData paramsByteData = creationParamsCodec!.encodeMessage(creationParams)!; |
307 | args['params'] = Uint8List.view(paramsByteData.buffer, 0, paramsByteData.lengthInBytes); |
308 | } |
309 | await SystemChannels.platform_views.invokeMethod<void>('create', args); |
310 | if (onFocus != null) { |
311 | _instance._focusCallbacks[id] = onFocus; |
312 | } |
313 | return AppKitViewController._(id, layoutDirection); |
314 | } |
315 | } |
316 | |
317 | /// Properties of an Android pointer. |
318 | /// |
319 | /// A Dart version of Android's [MotionEvent.PointerProperties](https://developer.android.com/reference/android/view/MotionEvent.PointerProperties). |
320 | class AndroidPointerProperties { |
321 | /// Creates an [AndroidPointerProperties] object. |
322 | const AndroidPointerProperties({required this.id, required this.toolType}); |
323 | |
324 | /// See Android's [MotionEvent.PointerProperties#id](https://developer.android.com/reference/android/view/MotionEvent.PointerProperties.html#id). |
325 | final int id; |
326 | |
327 | /// The type of tool used to make contact such as a finger or stylus, if known. |
328 | /// See Android's [MotionEvent.PointerProperties#toolType](https://developer.android.com/reference/android/view/MotionEvent.PointerProperties.html#toolType). |
329 | final int toolType; |
330 | |
331 | /// Value for `toolType` when the tool type is unknown. |
332 | static const int kToolTypeUnknown = 0; |
333 | |
334 | /// Value for `toolType` when the tool type is a finger. |
335 | static const int kToolTypeFinger = 1; |
336 | |
337 | /// Value for `toolType` when the tool type is a stylus. |
338 | static const int kToolTypeStylus = 2; |
339 | |
340 | /// Value for `toolType` when the tool type is a mouse. |
341 | static const int kToolTypeMouse = 3; |
342 | |
343 | /// Value for `toolType` when the tool type is an eraser. |
344 | static const int kToolTypeEraser = 4; |
345 | |
346 | List<int> _asList() => <int>[id, toolType]; |
347 | |
348 | @override |
349 | String toString() { |
350 | return '${objectRuntimeType(this, 'AndroidPointerProperties')} (id:$id , toolType:$toolType )'; |
351 | } |
352 | } |
353 | |
354 | /// Position information for an Android pointer. |
355 | /// |
356 | /// A Dart version of Android's [MotionEvent.PointerCoords](https://developer.android.com/reference/android/view/MotionEvent.PointerCoords). |
357 | class AndroidPointerCoords { |
358 | /// Creates an AndroidPointerCoords. |
359 | const AndroidPointerCoords({ |
360 | required this.orientation, |
361 | required this.pressure, |
362 | required this.size, |
363 | required this.toolMajor, |
364 | required this.toolMinor, |
365 | required this.touchMajor, |
366 | required this.touchMinor, |
367 | required this.x, |
368 | required this.y, |
369 | }); |
370 | |
371 | /// The orientation of the touch area and tool area in radians clockwise from vertical. |
372 | /// |
373 | /// See Android's [MotionEvent.PointerCoords#orientation](https://developer.android.com/reference/android/view/MotionEvent.PointerCoords.html#orientation). |
374 | final double orientation; |
375 | |
376 | /// A normalized value that describes the pressure applied to the device by a finger or other tool. |
377 | /// |
378 | /// See Android's [MotionEvent.PointerCoords#pressure](https://developer.android.com/reference/android/view/MotionEvent.PointerCoords.html#pressure). |
379 | final double pressure; |
380 | |
381 | /// A normalized value that describes the approximate size of the pointer touch area in relation to the maximum detectable size of the device. |
382 | /// |
383 | /// See Android's [MotionEvent.PointerCoords#size](https://developer.android.com/reference/android/view/MotionEvent.PointerCoords.html#size). |
384 | final double size; |
385 | |
386 | /// See Android's [MotionEvent.PointerCoords#toolMajor](https://developer.android.com/reference/android/view/MotionEvent.PointerCoords.html#toolMajor). |
387 | final double toolMajor; |
388 | |
389 | /// See Android's [MotionEvent.PointerCoords#toolMinor](https://developer.android.com/reference/android/view/MotionEvent.PointerCoords.html#toolMinor). |
390 | final double toolMinor; |
391 | |
392 | /// See Android's [MotionEvent.PointerCoords#touchMajor](https://developer.android.com/reference/android/view/MotionEvent.PointerCoords.html#touchMajor). |
393 | final double touchMajor; |
394 | |
395 | /// See Android's [MotionEvent.PointerCoords#touchMinor](https://developer.android.com/reference/android/view/MotionEvent.PointerCoords.html#touchMinor). |
396 | final double touchMinor; |
397 | |
398 | /// The X component of the pointer movement. |
399 | /// |
400 | /// See Android's [MotionEvent.PointerCoords#x](https://developer.android.com/reference/android/view/MotionEvent.PointerCoords.html#x). |
401 | final double x; |
402 | |
403 | /// The Y component of the pointer movement. |
404 | /// |
405 | /// See Android's [MotionEvent.PointerCoords#y](https://developer.android.com/reference/android/view/MotionEvent.PointerCoords.html#y). |
406 | final double y; |
407 | |
408 | List<double> _asList() { |
409 | return <double>[ |
410 | orientation, |
411 | pressure, |
412 | size, |
413 | toolMajor, |
414 | toolMinor, |
415 | touchMajor, |
416 | touchMinor, |
417 | x, |
418 | y, |
419 | ]; |
420 | } |
421 | |
422 | @override |
423 | String toString() { |
424 | return '${objectRuntimeType(this, 'AndroidPointerCoords')} (orientation:$orientation , pressure:$pressure , size:$size , toolMajor:$toolMajor , toolMinor:$toolMinor , touchMajor:$touchMajor , touchMinor:$touchMinor , x:$x , y:$y )'; |
425 | } |
426 | } |
427 | |
428 | /// A Dart version of Android's [MotionEvent](https://developer.android.com/reference/android/view/MotionEvent). |
429 | /// |
430 | /// This is used by [AndroidViewController] to describe pointer events that are forwarded to a platform view |
431 | /// when Flutter receives an event that it determines is to be handled by that platform view rather than by |
432 | /// another Flutter widget. |
433 | /// |
434 | /// See also: |
435 | /// |
436 | /// * [AndroidViewController.sendMotionEvent], which can be used to send an [AndroidMotionEvent] explicitly. |
437 | class AndroidMotionEvent { |
438 | /// Creates an AndroidMotionEvent. |
439 | AndroidMotionEvent({ |
440 | required this.downTime, |
441 | required this.eventTime, |
442 | required this.action, |
443 | required this.pointerCount, |
444 | required this.pointerProperties, |
445 | required this.pointerCoords, |
446 | required this.metaState, |
447 | required this.buttonState, |
448 | required this.xPrecision, |
449 | required this.yPrecision, |
450 | required this.deviceId, |
451 | required this.edgeFlags, |
452 | required this.source, |
453 | required this.flags, |
454 | required this.motionEventId, |
455 | }) : assert(pointerProperties.length == pointerCount), |
456 | assert(pointerCoords.length == pointerCount); |
457 | |
458 | /// The time (in ms) when the user originally pressed down to start a stream of position events, |
459 | /// relative to an arbitrary timeline. |
460 | /// |
461 | /// See Android's [MotionEvent#getDownTime](https://developer.android.com/reference/android/view/MotionEvent.html#getDownTime()). |
462 | final int downTime; |
463 | |
464 | /// The time this event occurred, relative to an arbitrary timeline. |
465 | /// |
466 | /// See Android's [MotionEvent#getEventTime](https://developer.android.com/reference/android/view/MotionEvent.html#getEventTime()). |
467 | final int eventTime; |
468 | |
469 | /// A value representing the kind of action being performed. |
470 | /// |
471 | /// See Android's [MotionEvent#getAction](https://developer.android.com/reference/android/view/MotionEvent.html#getAction()). |
472 | final int action; |
473 | |
474 | /// The number of pointers that are part of this event. |
475 | /// This must be equivalent to the length of `pointerProperties` and `pointerCoords`. |
476 | /// |
477 | /// See Android's [MotionEvent#getPointerCount](https://developer.android.com/reference/android/view/MotionEvent.html#getPointerCount()). |
478 | final int pointerCount; |
479 | |
480 | /// List of [AndroidPointerProperties] for each pointer that is part of this event. |
481 | final List<AndroidPointerProperties> pointerProperties; |
482 | |
483 | /// List of [AndroidPointerCoords] for each pointer that is part of this event. |
484 | final List<AndroidPointerCoords> pointerCoords; |
485 | |
486 | /// The state of any meta / modifier keys that were in effect when the event was generated. |
487 | /// |
488 | /// See Android's [MotionEvent#getMetaState](https://developer.android.com/reference/android/view/MotionEvent.html#getMetaState()). |
489 | final int metaState; |
490 | |
491 | /// The state of all buttons that are pressed such as a mouse or stylus button. |
492 | /// |
493 | /// See Android's [MotionEvent#getButtonState](https://developer.android.com/reference/android/view/MotionEvent.html#getButtonState()). |
494 | final int buttonState; |
495 | |
496 | /// The precision of the X coordinates being reported, in physical pixels. |
497 | /// |
498 | /// See Android's [MotionEvent#getXPrecision](https://developer.android.com/reference/android/view/MotionEvent.html#getXPrecision()). |
499 | final double xPrecision; |
500 | |
501 | /// The precision of the Y coordinates being reported, in physical pixels. |
502 | /// |
503 | /// See Android's [MotionEvent#getYPrecision](https://developer.android.com/reference/android/view/MotionEvent.html#getYPrecision()). |
504 | final double yPrecision; |
505 | |
506 | /// See Android's [MotionEvent#getDeviceId](https://developer.android.com/reference/android/view/MotionEvent.html#getDeviceId()). |
507 | final int deviceId; |
508 | |
509 | /// A bit field indicating which edges, if any, were touched by this MotionEvent. |
510 | /// |
511 | /// See Android's [MotionEvent#getEdgeFlags](https://developer.android.com/reference/android/view/MotionEvent.html#getEdgeFlags()). |
512 | final int edgeFlags; |
513 | |
514 | /// The source of this event (e.g a touchpad or stylus). |
515 | /// |
516 | /// See Android's [MotionEvent#getSource](https://developer.android.com/reference/android/view/MotionEvent.html#getSource()). |
517 | final int source; |
518 | |
519 | /// See Android's [MotionEvent#getFlags](https://developer.android.com/reference/android/view/MotionEvent.html#getFlags()). |
520 | final int flags; |
521 | |
522 | /// Used to identify this [MotionEvent](https://developer.android.com/reference/android/view/MotionEvent.html) uniquely in the Flutter Engine. |
523 | final int motionEventId; |
524 | |
525 | List<dynamic> _asList(int viewId) { |
526 | return <dynamic>[ |
527 | viewId, |
528 | downTime, |
529 | eventTime, |
530 | action, |
531 | pointerCount, |
532 | pointerProperties.map<List<int>>((AndroidPointerProperties p) => p._asList()).toList(), |
533 | pointerCoords.map<List<double>>((AndroidPointerCoords p) => p._asList()).toList(), |
534 | metaState, |
535 | buttonState, |
536 | xPrecision, |
537 | yPrecision, |
538 | deviceId, |
539 | edgeFlags, |
540 | source, |
541 | flags, |
542 | motionEventId, |
543 | ]; |
544 | } |
545 | |
546 | @override |
547 | String toString() { |
548 | return 'AndroidPointerEvent(downTime:$downTime , eventTime:$eventTime , action:$action , pointerCount:$pointerCount , pointerProperties:$pointerProperties , pointerCoords:$pointerCoords , metaState:$metaState , buttonState:$buttonState , xPrecision:$xPrecision , yPrecision:$yPrecision , deviceId:$deviceId , edgeFlags:$edgeFlags , source:$source , flags:$flags , motionEventId:$motionEventId )'; |
549 | } |
550 | } |
551 | |
552 | enum _AndroidViewState { waitingForSize, creating, created, disposed } |
553 | |
554 | // Helper for converting PointerEvents into AndroidMotionEvents. |
555 | class _AndroidMotionEventConverter { |
556 | _AndroidMotionEventConverter(); |
557 | |
558 | final Map<int, AndroidPointerCoords> pointerPositions = <int, AndroidPointerCoords>{}; |
559 | final Map<int, AndroidPointerProperties> pointerProperties = <int, AndroidPointerProperties>{}; |
560 | final Set<int> usedAndroidPointerIds = <int>{}; |
561 | |
562 | late PointTransformer pointTransformer; |
563 | |
564 | int? downTimeMillis; |
565 | |
566 | void handlePointerDownEvent(PointerDownEvent event) { |
567 | if (pointerProperties.isEmpty) { |
568 | downTimeMillis = event.timeStamp.inMilliseconds; |
569 | } |
570 | int androidPointerId = 0; |
571 | while (usedAndroidPointerIds.contains(androidPointerId)) { |
572 | androidPointerId++; |
573 | } |
574 | usedAndroidPointerIds.add(androidPointerId); |
575 | pointerProperties[event.pointer] = propertiesFor(event, androidPointerId); |
576 | } |
577 | |
578 | void updatePointerPositions(PointerEvent event) { |
579 | final Offset position = pointTransformer(event.position); |
580 | pointerPositions[event.pointer] = AndroidPointerCoords( |
581 | orientation: event.orientation, |
582 | pressure: event.pressure, |
583 | size: event.size, |
584 | toolMajor: event.radiusMajor, |
585 | toolMinor: event.radiusMinor, |
586 | touchMajor: event.radiusMajor, |
587 | touchMinor: event.radiusMinor, |
588 | x: position.dx, |
589 | y: position.dy, |
590 | ); |
591 | } |
592 | |
593 | void _remove(int pointer) { |
594 | pointerPositions.remove(pointer); |
595 | usedAndroidPointerIds.remove(pointerProperties[pointer]!.id); |
596 | pointerProperties.remove(pointer); |
597 | if (pointerProperties.isEmpty) { |
598 | downTimeMillis = null; |
599 | } |
600 | } |
601 | |
602 | void handlePointerUpEvent(PointerUpEvent event) { |
603 | _remove(event.pointer); |
604 | } |
605 | |
606 | void handlePointerCancelEvent(PointerCancelEvent event) { |
607 | // The pointer cancel event is handled like pointer up. Normally, |
608 | // the difference is that pointer cancel doesn't perform any action, |
609 | // but in this case neither up or cancel perform any action. |
610 | _remove(event.pointer); |
611 | } |
612 | |
613 | AndroidMotionEvent? toAndroidMotionEvent(PointerEvent event) { |
614 | final List<int> pointers = pointerPositions.keys.toList(); |
615 | final int pointerIdx = pointers.indexOf(event.pointer); |
616 | final int numPointers = pointers.length; |
617 | |
618 | // This value must match the value in engine's FlutterView.java. |
619 | // This flag indicates whether the original Android pointer events were batched together. |
620 | const int kPointerDataFlagBatched = 1; |
621 | |
622 | // Android MotionEvent objects can batch information on multiple pointers. |
623 | // Flutter breaks these such batched events into multiple PointerEvent objects. |
624 | // When there are multiple active pointers we accumulate the information for all pointers |
625 | // as we get PointerEvents, and only send it to the embedded Android view when |
626 | // we see the last pointer. This way we achieve the same batching as Android. |
627 | if (event.platformData == kPointerDataFlagBatched || |
628 | (isSinglePointerAction(event) && pointerIdx < numPointers - 1)) { |
629 | return null; |
630 | } |
631 | |
632 | final int? action = switch (event) { |
633 | PointerDownEvent() when numPointers == 1 => AndroidViewController.kActionDown, |
634 | PointerUpEvent() when numPointers == 1 => AndroidViewController.kActionUp, |
635 | PointerDownEvent() => AndroidViewController.pointerAction( |
636 | pointerIdx, |
637 | AndroidViewController.kActionPointerDown, |
638 | ), |
639 | PointerUpEvent() => AndroidViewController.pointerAction( |
640 | pointerIdx, |
641 | AndroidViewController.kActionPointerUp, |
642 | ), |
643 | PointerMoveEvent() => AndroidViewController.kActionMove, |
644 | PointerCancelEvent() => AndroidViewController.kActionCancel, |
645 | _ => null, |
646 | }; |
647 | if (action == null) { |
648 | return null; |
649 | } |
650 | |
651 | return AndroidMotionEvent( |
652 | downTime: downTimeMillis!, |
653 | eventTime: event.timeStamp.inMilliseconds, |
654 | action: action, |
655 | pointerCount: pointerPositions.length, |
656 | pointerProperties: |
657 | pointers.map<AndroidPointerProperties>((int i) => pointerProperties[i]!).toList(), |
658 | pointerCoords: pointers.map<AndroidPointerCoords>((int i) => pointerPositions[i]!).toList(), |
659 | metaState: 0, |
660 | buttonState: 0, |
661 | xPrecision: 1.0, |
662 | yPrecision: 1.0, |
663 | deviceId: 0, |
664 | edgeFlags: 0, |
665 | source: _AndroidMotionEventConverter.sourceFor(event), |
666 | flags: 0, |
667 | motionEventId: event.embedderId, |
668 | ); |
669 | } |
670 | |
671 | static int sourceFor(PointerEvent event) { |
672 | return switch (event.kind) { |
673 | PointerDeviceKind.touch => AndroidViewController.kInputDeviceSourceTouchScreen, |
674 | PointerDeviceKind.trackpad => AndroidViewController.kInputDeviceSourceTouchPad, |
675 | PointerDeviceKind.mouse => AndroidViewController.kInputDeviceSourceMouse, |
676 | PointerDeviceKind.stylus => AndroidViewController.kInputDeviceSourceStylus, |
677 | PointerDeviceKind.invertedStylus => AndroidViewController.kInputDeviceSourceStylus, |
678 | PointerDeviceKind.unknown => AndroidViewController.kInputDeviceSourceUnknown, |
679 | }; |
680 | } |
681 | |
682 | AndroidPointerProperties propertiesFor(PointerEvent event, int pointerId) { |
683 | return AndroidPointerProperties( |
684 | id: pointerId, |
685 | toolType: switch (event.kind) { |
686 | PointerDeviceKind.touch => AndroidPointerProperties.kToolTypeFinger, |
687 | PointerDeviceKind.trackpad => AndroidPointerProperties.kToolTypeFinger, |
688 | PointerDeviceKind.mouse => AndroidPointerProperties.kToolTypeMouse, |
689 | PointerDeviceKind.stylus => AndroidPointerProperties.kToolTypeStylus, |
690 | PointerDeviceKind.invertedStylus => AndroidPointerProperties.kToolTypeEraser, |
691 | PointerDeviceKind.unknown => AndroidPointerProperties.kToolTypeUnknown, |
692 | }, |
693 | ); |
694 | } |
695 | |
696 | bool isSinglePointerAction(PointerEvent event) => |
697 | event is! PointerDownEvent && event is! PointerUpEvent; |
698 | } |
699 | |
700 | class _CreationParams { |
701 | const _CreationParams(this.data, this.codec); |
702 | final dynamic data; |
703 | final MessageCodec<dynamic> codec; |
704 | } |
705 | |
706 | /// Controls an Android view that is composed using a GL texture. |
707 | /// |
708 | /// Typically created with [PlatformViewsService.initAndroidView]. |
709 | // TODO(bparrishMines): Remove abstract methods that are not required by all subclasses. |
710 | abstract class AndroidViewController extends PlatformViewController { |
711 | AndroidViewController._({ |
712 | required this.viewId, |
713 | required String viewType, |
714 | required TextDirection layoutDirection, |
715 | dynamic creationParams, |
716 | MessageCodec<dynamic>? creationParamsCodec, |
717 | }) : assert(creationParams == null || creationParamsCodec != null), |
718 | _viewType = viewType, |
719 | _layoutDirection = layoutDirection, |
720 | _creationParams = |
721 | creationParams == null ? null : _CreationParams(creationParams, creationParamsCodec!); |
722 | |
723 | /// Action code for when a primary pointer touched the screen. |
724 | /// |
725 | /// Android's [MotionEvent.ACTION_DOWN](https://developer.android.com/reference/android/view/MotionEvent#ACTION_DOWN) |
726 | static const int kActionDown = 0; |
727 | |
728 | /// Action code for when a primary pointer stopped touching the screen. |
729 | /// |
730 | /// Android's [MotionEvent.ACTION_UP](https://developer.android.com/reference/android/view/MotionEvent#ACTION_UP) |
731 | static const int kActionUp = 1; |
732 | |
733 | /// Action code for when the event only includes information about pointer movement. |
734 | /// |
735 | /// Android's [MotionEvent.ACTION_MOVE](https://developer.android.com/reference/android/view/MotionEvent#ACTION_MOVE) |
736 | static const int kActionMove = 2; |
737 | |
738 | /// Action code for when a motion event has been canceled. |
739 | /// |
740 | /// Android's [MotionEvent.ACTION_CANCEL](https://developer.android.com/reference/android/view/MotionEvent#ACTION_CANCEL) |
741 | static const int kActionCancel = 3; |
742 | |
743 | /// Action code for when a secondary pointer touched the screen. |
744 | /// |
745 | /// Android's [MotionEvent.ACTION_POINTER_DOWN](https://developer.android.com/reference/android/view/MotionEvent#ACTION_POINTER_DOWN) |
746 | static const int kActionPointerDown = 5; |
747 | |
748 | /// Action code for when a secondary pointer stopped touching the screen. |
749 | /// |
750 | /// Android's [MotionEvent.ACTION_POINTER_UP](https://developer.android.com/reference/android/view/MotionEvent#ACTION_POINTER_UP) |
751 | static const int kActionPointerUp = 6; |
752 | |
753 | /// Android's [View.LAYOUT_DIRECTION_LTR](https://developer.android.com/reference/android/view/View.html#LAYOUT_DIRECTION_LTR) value. |
754 | static const int kAndroidLayoutDirectionLtr = 0; |
755 | |
756 | /// Android's [View.LAYOUT_DIRECTION_RTL](https://developer.android.com/reference/android/view/View.html#LAYOUT_DIRECTION_RTL) value. |
757 | static const int kAndroidLayoutDirectionRtl = 1; |
758 | |
759 | /// Android's [InputDevice.SOURCE_UNKNOWN](https://developer.android.com/reference/android/view/InputDevice#SOURCE_UNKNOWN) |
760 | static const int kInputDeviceSourceUnknown = 0; |
761 | |
762 | /// Android's [InputDevice.SOURCE_TOUCHSCREEN](https://developer.android.com/reference/android/view/InputDevice#SOURCE_TOUCHSCREEN) |
763 | static const int kInputDeviceSourceTouchScreen = 4098; |
764 | |
765 | /// Android's [InputDevice.SOURCE_MOUSE](https://developer.android.com/reference/android/view/InputDevice#SOURCE_MOUSE) |
766 | static const int kInputDeviceSourceMouse = 8194; |
767 | |
768 | /// Android's [InputDevice.SOURCE_STYLUS](https://developer.android.com/reference/android/view/InputDevice#SOURCE_STYLUS) |
769 | static const int kInputDeviceSourceStylus = 16386; |
770 | |
771 | /// Android's [InputDevice.SOURCE_TOUCHPAD](https://developer.android.com/reference/android/view/InputDevice#SOURCE_TOUCHPAD) |
772 | static const int kInputDeviceSourceTouchPad = 1048584; |
773 | |
774 | /// The unique identifier of the Android view controlled by this controller. |
775 | @override |
776 | final int viewId; |
777 | |
778 | final String _viewType; |
779 | |
780 | // Helps convert PointerEvents to AndroidMotionEvents. |
781 | final _AndroidMotionEventConverter _motionEventConverter = _AndroidMotionEventConverter(); |
782 | |
783 | TextDirection _layoutDirection; |
784 | |
785 | _AndroidViewState _state = _AndroidViewState.waitingForSize; |
786 | |
787 | final _CreationParams? _creationParams; |
788 | |
789 | final List<PlatformViewCreatedCallback> _platformViewCreatedCallbacks = |
790 | <PlatformViewCreatedCallback>[]; |
791 | |
792 | static int _getAndroidDirection(TextDirection direction) { |
793 | return switch (direction) { |
794 | TextDirection.ltr => kAndroidLayoutDirectionLtr, |
795 | TextDirection.rtl => kAndroidLayoutDirectionRtl, |
796 | }; |
797 | } |
798 | |
799 | /// Creates a masked Android MotionEvent action value for an indexed pointer. |
800 | static int pointerAction(int pointerId, int action) { |
801 | return ((pointerId << 8) & 0xff00) | (action & 0xff); |
802 | } |
803 | |
804 | /// Sends the message to dispose the platform view. |
805 | Future<void> _sendDisposeMessage(); |
806 | |
807 | /// True if [_sendCreateMessage] can only be called with a non-null size. |
808 | bool get _createRequiresSize; |
809 | |
810 | /// Sends the message to create the platform view with an initial [size]. |
811 | /// |
812 | /// If [_createRequiresSize] is true, `size` is non-nullable, and the call |
813 | /// should instead be deferred until the size is available. |
814 | Future<void> _sendCreateMessage({required covariant Size? size, Offset? position}); |
815 | |
816 | /// Sends the message to resize the platform view to [size]. |
817 | Future<Size> _sendResizeMessage(Size size); |
818 | |
819 | @override |
820 | bool get awaitingCreation => _state == _AndroidViewState.waitingForSize; |
821 | |
822 | @override |
823 | Future<void> create({Size? size, Offset? position}) async { |
824 | assert(_state != _AndroidViewState.disposed, 'trying to create a disposed Android view'); |
825 | assert( |
826 | _state == _AndroidViewState.waitingForSize, |
827 | 'Android view is already sized. View id:$viewId ', |
828 | ); |
829 | |
830 | if (_createRequiresSize && size == null) { |
831 | // Wait for a setSize call. |
832 | return; |
833 | } |
834 | |
835 | _state = _AndroidViewState.creating; |
836 | await _sendCreateMessage(size: size, position: position); |
837 | _state = _AndroidViewState.created; |
838 | |
839 | for (final PlatformViewCreatedCallback callback in _platformViewCreatedCallbacks) { |
840 | callback(viewId); |
841 | } |
842 | } |
843 | |
844 | /// Sizes the Android View. |
845 | /// |
846 | /// [size] is the view's new size in logical pixel. It must be greater than |
847 | /// zero. |
848 | /// |
849 | /// The first time a size is set triggers the creation of the Android view. |
850 | /// |
851 | /// Returns the buffer size in logical pixel that backs the texture where the platform |
852 | /// view pixels are written to. |
853 | /// |
854 | /// The buffer size may or may not be the same as [size]. |
855 | /// |
856 | /// As a result, consumers are expected to clip the texture using [size], while using |
857 | /// the return value to size the texture. |
858 | Future<Size> setSize(Size size) async { |
859 | assert(_state != _AndroidViewState.disposed, 'Android view is disposed. View id:$viewId '); |
860 | if (_state == _AndroidViewState.waitingForSize) { |
861 | // Either `create` hasn't been called, or it couldn't run due to missing |
862 | // size information, so create the view now. |
863 | await create(size: size); |
864 | return size; |
865 | } else { |
866 | return _sendResizeMessage(size); |
867 | } |
868 | } |
869 | |
870 | /// Sets the offset of the platform view. |
871 | /// |
872 | /// [off] is the view's new offset in logical pixel. |
873 | /// |
874 | /// On Android, this allows the Android native view to draw the a11y highlights in the same |
875 | /// location on the screen as the platform view widget in the Flutter framework. |
876 | Future<void> setOffset(Offset off); |
877 | |
878 | /// Returns the texture entry id that the Android view is rendering into. |
879 | /// |
880 | /// Returns null if the Android view has not been successfully created, if it has been |
881 | /// disposed, or if the implementation does not use textures. |
882 | int? get textureId; |
883 | |
884 | /// True if the view requires native view composition rather than using a |
885 | /// texture to render. |
886 | /// |
887 | /// This value may change during [create], but will not change after that |
888 | /// call's future has completed. |
889 | bool get requiresViewComposition => false; |
890 | |
891 | /// Sends an Android [MotionEvent](https://developer.android.com/reference/android/view/MotionEvent) |
892 | /// to the view. |
893 | /// |
894 | /// The Android MotionEvent object is created with [MotionEvent.obtain](https://developer.android.com/reference/android/view/MotionEvent.html#obtain(long,%20long,%20int,%20float,%20float,%20float,%20float,%20int,%20float,%20float,%20int,%20int)). |
895 | /// See documentation of [MotionEvent.obtain](https://developer.android.com/reference/android/view/MotionEvent.html#obtain(long,%20long,%20int,%20float,%20float,%20float,%20float,%20int,%20float,%20float,%20int,%20int)) |
896 | /// for description of the parameters. |
897 | /// |
898 | /// See [AndroidViewController.dispatchPointerEvent] for sending a |
899 | /// [PointerEvent]. |
900 | Future<void> sendMotionEvent(AndroidMotionEvent event) async { |
901 | await SystemChannels.platform_views.invokeMethod<dynamic>('touch', event._asList(viewId)); |
902 | } |
903 | |
904 | /// Converts a given point from the global coordinate system in logical pixels |
905 | /// to the local coordinate system for this box. |
906 | /// |
907 | /// This is required to convert a [PointerEvent] to an [AndroidMotionEvent]. |
908 | /// It is typically provided by using [RenderBox.globalToLocal]. |
909 | PointTransformer get pointTransformer => _motionEventConverter.pointTransformer; |
910 | set pointTransformer(PointTransformer transformer) { |
911 | _motionEventConverter.pointTransformer = transformer; |
912 | } |
913 | |
914 | /// Whether the platform view has already been created. |
915 | bool get isCreated => _state == _AndroidViewState.created; |
916 | |
917 | /// Adds a callback that will get invoke after the platform view has been |
918 | /// created. |
919 | void addOnPlatformViewCreatedListener(PlatformViewCreatedCallback listener) { |
920 | assert(_state != _AndroidViewState.disposed); |
921 | _platformViewCreatedCallbacks.add(listener); |
922 | } |
923 | |
924 | /// Removes a callback added with [addOnPlatformViewCreatedListener]. |
925 | void removeOnPlatformViewCreatedListener(PlatformViewCreatedCallback listener) { |
926 | assert(_state != _AndroidViewState.disposed); |
927 | _platformViewCreatedCallbacks.remove(listener); |
928 | } |
929 | |
930 | /// The created callbacks that are invoked after the platform view has been |
931 | /// created. |
932 | @visibleForTesting |
933 | List<PlatformViewCreatedCallback> get createdCallbacks => _platformViewCreatedCallbacks; |
934 | |
935 | /// Sets the layout direction for the Android view. |
936 | Future<void> setLayoutDirection(TextDirection layoutDirection) async { |
937 | assert( |
938 | _state != _AndroidViewState.disposed, |
939 | 'trying to set a layout direction for a disposed Android view. View id:$viewId ', |
940 | ); |
941 | |
942 | if (layoutDirection == _layoutDirection) { |
943 | return; |
944 | } |
945 | |
946 | _layoutDirection = layoutDirection; |
947 | |
948 | // If the view was not yet created we just update _layoutDirection and return, as the new |
949 | // direction will be used in _create. |
950 | if (_state == _AndroidViewState.waitingForSize) { |
951 | return; |
952 | } |
953 | |
954 | await SystemChannels.platform_views.invokeMethod<void>('setDirection', <String, dynamic>{ |
955 | 'id': viewId, |
956 | 'direction': _getAndroidDirection(layoutDirection), |
957 | }); |
958 | } |
959 | |
960 | /// Converts the [PointerEvent] and sends an Android [MotionEvent](https://developer.android.com/reference/android/view/MotionEvent) |
961 | /// to the view. |
962 | /// |
963 | /// This method can only be used if a [PointTransformer] is provided to |
964 | /// [AndroidViewController.pointTransformer]. Otherwise, an [AssertionError] |
965 | /// is thrown. See [AndroidViewController.sendMotionEvent] for sending a |
966 | /// `MotionEvent` without a [PointTransformer]. |
967 | /// |
968 | /// The Android MotionEvent object is created with [MotionEvent.obtain](https://developer.android.com/reference/android/view/MotionEvent.html#obtain(long,%20long,%20int,%20float,%20float,%20float,%20float,%20int,%20float,%20float,%20int,%20int)). |
969 | /// See documentation of [MotionEvent.obtain](https://developer.android.com/reference/android/view/MotionEvent.html#obtain(long,%20long,%20int,%20float,%20float,%20float,%20float,%20int,%20float,%20float,%20int,%20int)) |
970 | /// for description of the parameters. |
971 | @override |
972 | Future<void> dispatchPointerEvent(PointerEvent event) async { |
973 | if (event is PointerHoverEvent) { |
974 | return; |
975 | } |
976 | |
977 | if (event is PointerDownEvent) { |
978 | _motionEventConverter.handlePointerDownEvent(event); |
979 | } |
980 | |
981 | _motionEventConverter.updatePointerPositions(event); |
982 | |
983 | final AndroidMotionEvent? androidEvent = _motionEventConverter.toAndroidMotionEvent(event); |
984 | |
985 | if (event is PointerUpEvent) { |
986 | _motionEventConverter.handlePointerUpEvent(event); |
987 | } else if (event is PointerCancelEvent) { |
988 | _motionEventConverter.handlePointerCancelEvent(event); |
989 | } |
990 | |
991 | if (androidEvent != null) { |
992 | await sendMotionEvent(androidEvent); |
993 | } |
994 | } |
995 | |
996 | /// Clears the focus from the Android View if it is focused. |
997 | @override |
998 | Future<void> clearFocus() { |
999 | if (_state != _AndroidViewState.created) { |
1000 | return Future<void>.value(); |
1001 | } |
1002 | return SystemChannels.platform_views.invokeMethod<void>('clearFocus', viewId); |
1003 | } |
1004 | |
1005 | /// Disposes the Android view. |
1006 | /// |
1007 | /// The [AndroidViewController] object is unusable after calling this. |
1008 | /// The identifier of the platform view cannot be reused after the view is |
1009 | /// disposed. |
1010 | @override |
1011 | Future<void> dispose() async { |
1012 | final _AndroidViewState state = _state; |
1013 | _state = _AndroidViewState.disposed; |
1014 | _platformViewCreatedCallbacks.clear(); |
1015 | PlatformViewsService._instance._focusCallbacks.remove(viewId); |
1016 | if (state == _AndroidViewState.creating || state == _AndroidViewState.created) { |
1017 | await _sendDisposeMessage(); |
1018 | } |
1019 | } |
1020 | } |
1021 | |
1022 | /// Controls an Android view that is composed using a GL texture. |
1023 | /// This controller is created from the [PlatformViewsService.initSurfaceAndroidView] factory, |
1024 | /// and is defined for backward compatibility. |
1025 | class SurfaceAndroidViewController extends AndroidViewController { |
1026 | SurfaceAndroidViewController._({ |
1027 | required super.viewId, |
1028 | required super.viewType, |
1029 | required super.layoutDirection, |
1030 | super.creationParams, |
1031 | super.creationParamsCodec, |
1032 | }) : super._(); |
1033 | |
1034 | // By default, assume the implementation will be texture-based. |
1035 | _AndroidViewControllerInternals _internals = _TextureAndroidViewControllerInternals(); |
1036 | |
1037 | @override |
1038 | bool get _createRequiresSize => true; |
1039 | |
1040 | @override |
1041 | Future<bool> _sendCreateMessage({required Size size, Offset? position}) async { |
1042 | assert( |
1043 | !size.isEmpty, |
1044 | 'trying to create$TextureAndroidViewController without setting a valid size.', |
1045 | ); |
1046 | |
1047 | final dynamic response = await _AndroidViewControllerInternals.sendCreateMessage( |
1048 | viewId: viewId, |
1049 | viewType: _viewType, |
1050 | hybrid: false, |
1051 | hybridFallback: true, |
1052 | layoutDirection: _layoutDirection, |
1053 | creationParams: _creationParams, |
1054 | size: size, |
1055 | position: position, |
1056 | ); |
1057 | if (response is int) { |
1058 | (_internals as _TextureAndroidViewControllerInternals).textureId = response; |
1059 | } else { |
1060 | // A null response indicates fallback to Hybrid Composition, so swap out |
1061 | // the implementation. |
1062 | _internals = _HybridAndroidViewControllerInternals(); |
1063 | } |
1064 | return true; |
1065 | } |
1066 | |
1067 | @override |
1068 | int? get textureId { |
1069 | return _internals.textureId; |
1070 | } |
1071 | |
1072 | @override |
1073 | bool get requiresViewComposition { |
1074 | return _internals.requiresViewComposition; |
1075 | } |
1076 | |
1077 | @override |
1078 | Future<void> _sendDisposeMessage() { |
1079 | return _internals.sendDisposeMessage(viewId: viewId); |
1080 | } |
1081 | |
1082 | @override |
1083 | Future<Size> _sendResizeMessage(Size size) { |
1084 | return _internals.setSize(size, viewId: viewId, viewState: _state); |
1085 | } |
1086 | |
1087 | @override |
1088 | Future<void> setOffset(Offset off) { |
1089 | return _internals.setOffset(off, viewId: viewId, viewState: _state); |
1090 | } |
1091 | } |
1092 | |
1093 | /// Controls an Android view that is composed using the Android view hierarchy. |
1094 | /// This controller is created from the [PlatformViewsService.initExpensiveAndroidView] factory. |
1095 | class ExpensiveAndroidViewController extends AndroidViewController { |
1096 | ExpensiveAndroidViewController._({ |
1097 | required super.viewId, |
1098 | required super.viewType, |
1099 | required super.layoutDirection, |
1100 | super.creationParams, |
1101 | super.creationParamsCodec, |
1102 | }) : super._(); |
1103 | |
1104 | final _AndroidViewControllerInternals _internals = _HybridAndroidViewControllerInternals(); |
1105 | |
1106 | @override |
1107 | bool get _createRequiresSize => false; |
1108 | |
1109 | @override |
1110 | Future<void> _sendCreateMessage({required Size? size, Offset? position}) async { |
1111 | await _AndroidViewControllerInternals.sendCreateMessage( |
1112 | viewId: viewId, |
1113 | viewType: _viewType, |
1114 | hybrid: true, |
1115 | layoutDirection: _layoutDirection, |
1116 | creationParams: _creationParams, |
1117 | position: position, |
1118 | ); |
1119 | } |
1120 | |
1121 | @override |
1122 | int? get textureId { |
1123 | return _internals.textureId; |
1124 | } |
1125 | |
1126 | @override |
1127 | bool get requiresViewComposition { |
1128 | return _internals.requiresViewComposition; |
1129 | } |
1130 | |
1131 | @override |
1132 | Future<void> _sendDisposeMessage() { |
1133 | return _internals.sendDisposeMessage(viewId: viewId); |
1134 | } |
1135 | |
1136 | @override |
1137 | Future<Size> _sendResizeMessage(Size size) { |
1138 | return _internals.setSize(size, viewId: viewId, viewState: _state); |
1139 | } |
1140 | |
1141 | @override |
1142 | Future<void> setOffset(Offset off) { |
1143 | return _internals.setOffset(off, viewId: viewId, viewState: _state); |
1144 | } |
1145 | } |
1146 | |
1147 | /// Controls an Android view that is composed using the Android view hierarchy. |
1148 | /// This controller is created from the [PlatformViewsService.initExpensiveAndroidView] factory. |
1149 | class HybridAndroidViewController extends AndroidViewController { |
1150 | HybridAndroidViewController._({ |
1151 | required super.viewId, |
1152 | required super.viewType, |
1153 | required super.layoutDirection, |
1154 | super.creationParams, |
1155 | super.creationParamsCodec, |
1156 | }) : super._(); |
1157 | |
1158 | final _AndroidViewControllerInternals _internals = _Hybrid2AndroidViewControllerInternals(); |
1159 | |
1160 | /// Perform a runtime check to determine if HCPP mode is supported on the |
1161 | /// current device. |
1162 | static Future<bool> checkIfSupported() => |
1163 | _Hybrid2AndroidViewControllerInternals.checkIfSurfaceControlEnabled(); |
1164 | |
1165 | @override |
1166 | bool get _createRequiresSize => false; |
1167 | |
1168 | @override |
1169 | Future<void> _sendCreateMessage({required Size? size, Offset? position}) async { |
1170 | await _AndroidViewControllerInternals.sendCreateMessage( |
1171 | viewId: viewId, |
1172 | viewType: _viewType, |
1173 | hybrid: true, |
1174 | layoutDirection: _layoutDirection, |
1175 | creationParams: _creationParams, |
1176 | position: position, |
1177 | useNewController: true, |
1178 | ); |
1179 | } |
1180 | |
1181 | @override |
1182 | int? get textureId { |
1183 | return _internals.textureId; |
1184 | } |
1185 | |
1186 | @override |
1187 | bool get requiresViewComposition { |
1188 | return _internals.requiresViewComposition; |
1189 | } |
1190 | |
1191 | @override |
1192 | Future<void> _sendDisposeMessage() { |
1193 | return _internals.sendDisposeMessage(viewId: viewId); |
1194 | } |
1195 | |
1196 | @override |
1197 | Future<Size> _sendResizeMessage(Size size) { |
1198 | return _internals.setSize(size, viewId: viewId, viewState: _state); |
1199 | } |
1200 | |
1201 | @override |
1202 | Future<void> setOffset(Offset off) { |
1203 | return _internals.setOffset(off, viewId: viewId, viewState: _state); |
1204 | } |
1205 | |
1206 | @override |
1207 | Future<void> sendMotionEvent(AndroidMotionEvent event) async { |
1208 | await SystemChannels.platform_views_2.invokeMethod<dynamic>('touch', event._asList(viewId)); |
1209 | } |
1210 | } |
1211 | |
1212 | /// Controls an Android view that is rendered as a texture. |
1213 | /// This is typically used by [AndroidView] to display a View in the Android view hierarchy. |
1214 | /// |
1215 | /// The platform view is created by calling [create] with an initial size. |
1216 | /// |
1217 | /// The controller is typically created with [PlatformViewsService.initAndroidView]. |
1218 | class TextureAndroidViewController extends AndroidViewController { |
1219 | TextureAndroidViewController._({ |
1220 | required super.viewId, |
1221 | required super.viewType, |
1222 | required super.layoutDirection, |
1223 | super.creationParams, |
1224 | super.creationParamsCodec, |
1225 | }) : super._(); |
1226 | |
1227 | final _TextureAndroidViewControllerInternals _internals = |
1228 | _TextureAndroidViewControllerInternals(); |
1229 | |
1230 | @override |
1231 | bool get _createRequiresSize => true; |
1232 | |
1233 | @override |
1234 | Future<void> _sendCreateMessage({required Size size, Offset? position}) async { |
1235 | assert( |
1236 | !size.isEmpty, |
1237 | 'trying to create$TextureAndroidViewController without setting a valid size.', |
1238 | ); |
1239 | |
1240 | _internals.textureId = |
1241 | await _AndroidViewControllerInternals.sendCreateMessage( |
1242 | viewId: viewId, |
1243 | viewType: _viewType, |
1244 | hybrid: false, |
1245 | layoutDirection: _layoutDirection, |
1246 | creationParams: _creationParams, |
1247 | size: size, |
1248 | position: position, |
1249 | ) |
1250 | as int; |
1251 | } |
1252 | |
1253 | @override |
1254 | int? get textureId { |
1255 | return _internals.textureId; |
1256 | } |
1257 | |
1258 | @override |
1259 | bool get requiresViewComposition { |
1260 | return _internals.requiresViewComposition; |
1261 | } |
1262 | |
1263 | @override |
1264 | Future<void> _sendDisposeMessage() { |
1265 | return _internals.sendDisposeMessage(viewId: viewId); |
1266 | } |
1267 | |
1268 | @override |
1269 | Future<Size> _sendResizeMessage(Size size) { |
1270 | return _internals.setSize(size, viewId: viewId, viewState: _state); |
1271 | } |
1272 | |
1273 | @override |
1274 | Future<void> setOffset(Offset off) { |
1275 | return _internals.setOffset(off, viewId: viewId, viewState: _state); |
1276 | } |
1277 | } |
1278 | |
1279 | // The base class for an implementation of AndroidViewController. |
1280 | // |
1281 | // Subclasses should correspond to different rendering modes for platform |
1282 | // views, and match different mode logic on the engine side. |
1283 | abstract class _AndroidViewControllerInternals { |
1284 | // Sends a create message with the given parameters, and returns the result |
1285 | // if any. |
1286 | // |
1287 | // This uses a dynamic return because depending on the mode that is selected |
1288 | // on the native side, the return type is different. Callers should cast |
1289 | // depending on the possible return types for their arguments. |
1290 | static Future<dynamic> sendCreateMessage({ |
1291 | required int viewId, |
1292 | required String viewType, |
1293 | required TextDirection layoutDirection, |
1294 | required bool hybrid, |
1295 | bool hybridFallback = false, |
1296 | bool useNewController = false, |
1297 | _CreationParams? creationParams, |
1298 | Size? size, |
1299 | Offset? position, |
1300 | }) { |
1301 | final Map<String, dynamic> args = <String, dynamic>{ |
1302 | 'id': viewId, |
1303 | 'viewType': viewType, |
1304 | 'direction': AndroidViewController._getAndroidDirection(layoutDirection), |
1305 | if (hybrid) 'hybrid': hybrid, |
1306 | if (size != null) 'width': size.width, |
1307 | if (size != null) 'height': size.height, |
1308 | if (hybridFallback) 'hybridFallback': hybridFallback, |
1309 | if (position != null) 'left': position.dx, |
1310 | if (position != null) 'top': position.dy, |
1311 | }; |
1312 | if (creationParams != null) { |
1313 | final ByteData paramsByteData = creationParams.codec.encodeMessage(creationParams.data)!; |
1314 | args['params'] = Uint8List.view(paramsByteData.buffer, 0, paramsByteData.lengthInBytes); |
1315 | } |
1316 | if (useNewController) { |
1317 | return SystemChannels.platform_views_2.invokeMethod<dynamic>('create', args); |
1318 | } |
1319 | return SystemChannels.platform_views.invokeMethod<dynamic>('create', args); |
1320 | } |
1321 | |
1322 | int? get textureId; |
1323 | |
1324 | bool get requiresViewComposition; |
1325 | |
1326 | Future<Size> setSize(Size size, {required int viewId, required _AndroidViewState viewState}); |
1327 | |
1328 | Future<void> setOffset( |
1329 | Offset offset, { |
1330 | required int viewId, |
1331 | required _AndroidViewState viewState, |
1332 | }); |
1333 | |
1334 | Future<void> sendDisposeMessage({required int viewId}); |
1335 | } |
1336 | |
1337 | // An AndroidViewController implementation for views whose contents are |
1338 | // displayed via a texture rather than directly in a native view. |
1339 | // |
1340 | // This is used for both Virtual Display and Texture Layer Hybrid Composition. |
1341 | class _TextureAndroidViewControllerInternals extends _AndroidViewControllerInternals { |
1342 | _TextureAndroidViewControllerInternals(); |
1343 | |
1344 | /// The current offset of the platform view. |
1345 | Offset _offset = Offset.zero; |
1346 | |
1347 | @override |
1348 | int? textureId; |
1349 | |
1350 | @override |
1351 | bool get requiresViewComposition => false; |
1352 | |
1353 | @override |
1354 | Future<Size> setSize( |
1355 | Size size, { |
1356 | required int viewId, |
1357 | required _AndroidViewState viewState, |
1358 | }) async { |
1359 | assert( |
1360 | viewState != _AndroidViewState.waitingForSize, |
1361 | 'Android view must have an initial size. View id:$viewId ', |
1362 | ); |
1363 | assert(!size.isEmpty); |
1364 | |
1365 | final Map<Object?, Object?>? meta = await SystemChannels.platform_views |
1366 | .invokeMapMethod<Object?, Object?>('resize', <String, dynamic>{ |
1367 | 'id': viewId, |
1368 | 'width': size.width, |
1369 | 'height': size.height, |
1370 | }); |
1371 | assert(meta != null); |
1372 | assert(meta!.containsKey('width')); |
1373 | assert(meta!.containsKey('height')); |
1374 | return Size(meta!['width']! as double, meta[ 'height']! as double); |
1375 | } |
1376 | |
1377 | @override |
1378 | Future<void> setOffset( |
1379 | Offset offset, { |
1380 | required int viewId, |
1381 | required _AndroidViewState viewState, |
1382 | }) async { |
1383 | if (offset == _offset) { |
1384 | return; |
1385 | } |
1386 | |
1387 | // Don't set the offset unless the Android view has been created. |
1388 | // The implementation of this method channel throws if the Android view for this viewId |
1389 | // isn't addressable. |
1390 | if (viewState != _AndroidViewState.created) { |
1391 | return; |
1392 | } |
1393 | |
1394 | _offset = offset; |
1395 | |
1396 | await SystemChannels.platform_views.invokeMethod<void>('offset', <String, dynamic>{ |
1397 | 'id': viewId, |
1398 | 'top': offset.dy, |
1399 | 'left': offset.dx, |
1400 | }); |
1401 | } |
1402 | |
1403 | @override |
1404 | Future<void> sendDisposeMessage({required int viewId}) { |
1405 | return SystemChannels.platform_views.invokeMethod<void>('dispose', <String, dynamic>{ |
1406 | 'id': viewId, |
1407 | 'hybrid': false, |
1408 | }); |
1409 | } |
1410 | } |
1411 | |
1412 | // An AndroidViewController implementation for views whose contents are |
1413 | // displayed directly in a native view. |
1414 | // |
1415 | // This is used for Hybrid Composition. |
1416 | class _HybridAndroidViewControllerInternals extends _AndroidViewControllerInternals { |
1417 | @override |
1418 | int get textureId { |
1419 | throw UnimplementedError('Not supported for hybrid composition.'); |
1420 | } |
1421 | |
1422 | @override |
1423 | bool get requiresViewComposition => true; |
1424 | |
1425 | @override |
1426 | Future<Size> setSize(Size size, {required int viewId, required _AndroidViewState viewState}) { |
1427 | throw UnimplementedError('Not supported for hybrid composition.'); |
1428 | } |
1429 | |
1430 | @override |
1431 | Future<void> setOffset( |
1432 | Offset offset, { |
1433 | required int viewId, |
1434 | required _AndroidViewState viewState, |
1435 | }) { |
1436 | throw UnimplementedError('Not supported for hybrid composition.'); |
1437 | } |
1438 | |
1439 | @override |
1440 | Future<void> sendDisposeMessage({required int viewId}) { |
1441 | return SystemChannels.platform_views.invokeMethod<void>('dispose', <String, dynamic>{ |
1442 | 'id': viewId, |
1443 | 'hybrid': true, |
1444 | }); |
1445 | } |
1446 | } |
1447 | |
1448 | // The HCPP platform view controller. |
1449 | // |
1450 | // This is only supported via an opt in on Impeller Android. |
1451 | class _Hybrid2AndroidViewControllerInternals extends _AndroidViewControllerInternals { |
1452 | // Determine if HCPP can be used. |
1453 | static Future<bool> checkIfSurfaceControlEnabled() async { |
1454 | return (await SystemChannels.platform_views_2.invokeMethod<bool>( |
1455 | 'isSurfaceControlEnabled', |
1456 | <String, Object?>{}, |
1457 | ))!; |
1458 | } |
1459 | |
1460 | @override |
1461 | int get textureId { |
1462 | throw UnimplementedError('Not supported for hybrid composition.'); |
1463 | } |
1464 | |
1465 | @override |
1466 | bool get requiresViewComposition => true; |
1467 | |
1468 | @override |
1469 | Future<Size> setSize(Size size, {required int viewId, required _AndroidViewState viewState}) { |
1470 | throw UnimplementedError('Not supported for hybrid composition.'); |
1471 | } |
1472 | |
1473 | @override |
1474 | Future<void> setOffset( |
1475 | Offset offset, { |
1476 | required int viewId, |
1477 | required _AndroidViewState viewState, |
1478 | }) { |
1479 | throw UnimplementedError('Not supported for hybrid composition.'); |
1480 | } |
1481 | |
1482 | @override |
1483 | Future<void> sendDisposeMessage({required int viewId}) { |
1484 | return SystemChannels.platform_views_2.invokeMethod<void>('dispose', <String, dynamic>{ |
1485 | 'id': viewId, |
1486 | 'hybrid': true, |
1487 | }); |
1488 | } |
1489 | } |
1490 | |
1491 | /// Base class for iOS and macOS view controllers. |
1492 | /// |
1493 | /// View controllers are used to create and interact with the UIView or NSView |
1494 | /// underlying a platform view. |
1495 | abstract class DarwinPlatformViewController { |
1496 | /// Public default for subclasses to override. |
1497 | DarwinPlatformViewController(this.id, TextDirection layoutDirection) |
1498 | : _layoutDirection = layoutDirection; |
1499 | |
1500 | /// The unique identifier of the iOS view controlled by this controller. |
1501 | /// |
1502 | /// This identifier is typically generated by |
1503 | /// [PlatformViewsRegistry.getNextPlatformViewId]. |
1504 | final int id; |
1505 | |
1506 | bool _debugDisposed = false; |
1507 | |
1508 | TextDirection _layoutDirection; |
1509 | |
1510 | /// Sets the layout direction for the iOS UIView. |
1511 | Future<void> setLayoutDirection(TextDirection layoutDirection) async { |
1512 | assert( |
1513 | !_debugDisposed, |
1514 | 'trying to set a layout direction for a disposed iOS UIView. View id:$id ', |
1515 | ); |
1516 | |
1517 | if (layoutDirection == _layoutDirection) { |
1518 | return; |
1519 | } |
1520 | |
1521 | _layoutDirection = layoutDirection; |
1522 | |
1523 | // TODO(amirh): invoke the iOS platform views channel direction method once available. |
1524 | } |
1525 | |
1526 | /// Accept an active gesture. |
1527 | /// |
1528 | /// When a touch sequence is happening on the embedded UIView all touch events are delayed. |
1529 | /// Calling this method releases the delayed events to the embedded UIView and makes it consume |
1530 | /// any following touch events for the pointers involved in the active gesture. |
1531 | Future<void> acceptGesture() { |
1532 | final Map<String, dynamic> args = <String, dynamic>{'id': id}; |
1533 | return SystemChannels.platform_views.invokeMethod('acceptGesture', args); |
1534 | } |
1535 | |
1536 | /// Rejects an active gesture. |
1537 | /// |
1538 | /// When a touch sequence is happening on the embedded UIView all touch events are delayed. |
1539 | /// Calling this method drops the buffered touch events and prevents any future touch events for |
1540 | /// the pointers that are part of the active touch sequence from arriving to the embedded view. |
1541 | Future<void> rejectGesture() { |
1542 | final Map<String, dynamic> args = <String, dynamic>{'id': id}; |
1543 | return SystemChannels.platform_views.invokeMethod('rejectGesture', args); |
1544 | } |
1545 | |
1546 | /// Disposes the view. |
1547 | /// |
1548 | /// The [UiKitViewController] object is unusable after calling this. |
1549 | /// The `id` of the platform view cannot be reused after the view is |
1550 | /// disposed. |
1551 | Future<void> dispose() async { |
1552 | _debugDisposed = true; |
1553 | await SystemChannels.platform_views.invokeMethod<void>('dispose', id); |
1554 | PlatformViewsService._instance._focusCallbacks.remove(id); |
1555 | } |
1556 | } |
1557 | |
1558 | /// Controller for an iOS platform view. |
1559 | /// |
1560 | /// View controllers create and interact with the underlying UIView. |
1561 | /// |
1562 | /// Typically created with [PlatformViewsService.initUiKitView]. |
1563 | class UiKitViewController extends DarwinPlatformViewController { |
1564 | UiKitViewController._(super.id, super.layoutDirection); |
1565 | } |
1566 | |
1567 | /// Controller for a macOS platform view. |
1568 | class AppKitViewController extends DarwinPlatformViewController { |
1569 | AppKitViewController._(super.id, super.layoutDirection); |
1570 | } |
1571 | |
1572 | /// An interface for controlling a single platform view. |
1573 | /// |
1574 | /// Used by [PlatformViewSurface] to interface with the platform view it embeds. |
1575 | abstract class PlatformViewController { |
1576 | /// The viewId associated with this controller. |
1577 | /// |
1578 | /// The viewId should always be unique and non-negative. |
1579 | /// |
1580 | /// See also: |
1581 | /// |
1582 | /// * [PlatformViewsRegistry], which is a helper for managing platform view IDs. |
1583 | int get viewId; |
1584 | |
1585 | /// True if [create] has not been successfully called the platform view. |
1586 | /// |
1587 | /// This can indicate either that [create] was never called, or that [create] |
1588 | /// was deferred for implementation-specific reasons. |
1589 | /// |
1590 | /// A `false` return value does not necessarily indicate that the [Future] |
1591 | /// returned by [create] has completed, only that creation has been started. |
1592 | bool get awaitingCreation => false; |
1593 | |
1594 | /// Dispatches the `event` to the platform view. |
1595 | Future<void> dispatchPointerEvent(PointerEvent event); |
1596 | |
1597 | /// Creates the platform view with the initial [size]. |
1598 | /// |
1599 | /// [size] is the view's initial size in logical pixel. |
1600 | /// [size] can be omitted if the concrete implementation doesn't require an initial size |
1601 | /// to create the platform view. |
1602 | /// |
1603 | /// [position] is the view's initial position in logical pixels. |
1604 | /// [position] can be omitted if the concrete implementation doesn't require |
1605 | /// an initial position. |
1606 | Future<void> create({Size? size, Offset? position}) async {} |
1607 | |
1608 | /// Disposes the platform view. |
1609 | /// |
1610 | /// The [PlatformViewController] is unusable after calling dispose. |
1611 | Future<void> dispose(); |
1612 | |
1613 | /// Clears the view's focus on the platform side. |
1614 | Future<void> clearFocus(); |
1615 | } |
1616 |
Definitions
- platformViewsRegistry
- PlatformViewsRegistry
- _instance
- getNextPlatformViewId
- PlatformViewsService
- _
- _onMethodCall
- initAndroidView
- initSurfaceAndroidView
- initExpensiveAndroidView
- initHybridAndroidView
- initUiKitView
- initAppKitView
- AndroidPointerProperties
- AndroidPointerProperties
- _asList
- toString
- AndroidPointerCoords
- AndroidPointerCoords
- _asList
- toString
- AndroidMotionEvent
- AndroidMotionEvent
- _asList
- toString
- _AndroidViewState
- _AndroidMotionEventConverter
- _AndroidMotionEventConverter
- handlePointerDownEvent
- updatePointerPositions
- _remove
- handlePointerUpEvent
- handlePointerCancelEvent
- toAndroidMotionEvent
- sourceFor
- propertiesFor
- isSinglePointerAction
- _CreationParams
- _CreationParams
- AndroidViewController
- _
- _getAndroidDirection
- pointerAction
- _sendDisposeMessage
- _createRequiresSize
- _sendCreateMessage
- _sendResizeMessage
- awaitingCreation
- create
- setSize
- setOffset
- textureId
- requiresViewComposition
- sendMotionEvent
- pointTransformer
- pointTransformer
- isCreated
- addOnPlatformViewCreatedListener
- removeOnPlatformViewCreatedListener
- createdCallbacks
- setLayoutDirection
- dispatchPointerEvent
- clearFocus
- dispose
- SurfaceAndroidViewController
- _
- _createRequiresSize
- _sendCreateMessage
- textureId
- requiresViewComposition
- _sendDisposeMessage
- _sendResizeMessage
- setOffset
- ExpensiveAndroidViewController
- _
- _createRequiresSize
- _sendCreateMessage
- textureId
- requiresViewComposition
- _sendDisposeMessage
- _sendResizeMessage
- setOffset
- HybridAndroidViewController
- _
- checkIfSupported
- _createRequiresSize
- _sendCreateMessage
- textureId
- requiresViewComposition
- _sendDisposeMessage
- _sendResizeMessage
- setOffset
- sendMotionEvent
- TextureAndroidViewController
- _
- _createRequiresSize
- _sendCreateMessage
- textureId
- requiresViewComposition
- _sendDisposeMessage
- _sendResizeMessage
- setOffset
- _AndroidViewControllerInternals
- sendCreateMessage
- textureId
- requiresViewComposition
- setSize
- setOffset
- sendDisposeMessage
- _TextureAndroidViewControllerInternals
- _TextureAndroidViewControllerInternals
- requiresViewComposition
- setSize
- setOffset
- sendDisposeMessage
- _HybridAndroidViewControllerInternals
- textureId
- requiresViewComposition
- setSize
- setOffset
- sendDisposeMessage
- _Hybrid2AndroidViewControllerInternals
- checkIfSurfaceControlEnabled
- textureId
- requiresViewComposition
- setSize
- setOffset
- sendDisposeMessage
- DarwinPlatformViewController
- DarwinPlatformViewController
- setLayoutDirection
- acceptGesture
- rejectGesture
- dispose
- UiKitViewController
- _
- AppKitViewController
- _
- PlatformViewController
- viewId
- awaitingCreation
- dispatchPointerEvent
- create
- dispose
Learn more about Flutter for embedded and desktop on industrialflutter.com