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/widgets.dart'; |
6 | library; |
7 | |
8 | import 'package:flutter/foundation.dart'; |
9 | import 'package:flutter/gestures.dart'; |
10 | import 'package:flutter/scheduler.dart'; |
11 | import 'package:flutter/semantics.dart'; |
12 | import 'package:flutter/services.dart'; |
13 | |
14 | import 'box.dart'; |
15 | import 'layer.dart'; |
16 | import 'object.dart'; |
17 | |
18 | /// How an embedded platform view behave during hit tests. |
19 | enum PlatformViewHitTestBehavior { |
20 | /// Opaque targets can be hit by hit tests, causing them to both receive |
21 | /// events within their bounds and prevent targets visually behind them from |
22 | /// also receiving events. |
23 | opaque, |
24 | |
25 | /// Translucent targets both receive events within their bounds and permit |
26 | /// targets visually behind them to also receive events. |
27 | translucent, |
28 | |
29 | /// Transparent targets don't receive events within their bounds and permit |
30 | /// targets visually behind them to receive events. |
31 | transparent, |
32 | } |
33 | |
34 | enum _PlatformViewState { uninitialized, resizing, ready } |
35 | |
36 | bool _factoryTypesSetEquals<T>(Set<Factory<T>>? a, Set<Factory<T>>? b) { |
37 | if (a == b) { |
38 | return true; |
39 | } |
40 | if (a == null || b == null) { |
41 | return false; |
42 | } |
43 | return setEquals(_factoriesTypeSet(a), _factoriesTypeSet(b)); |
44 | } |
45 | |
46 | Set<Type> _factoriesTypeSet<T>(Set<Factory<T>> factories) { |
47 | return factories.map<Type>((Factory<T> factory) => factory.type).toSet(); |
48 | } |
49 | |
50 | /// A render object for an Android view. |
51 | /// |
52 | /// Requires Android API level 23 or greater. |
53 | /// |
54 | /// [RenderAndroidView] is responsible for sizing, displaying and passing touch events to an |
55 | /// Android [View](https://developer.android.com/reference/android/view/View). |
56 | /// |
57 | /// {@template flutter.rendering.RenderAndroidView.layout} |
58 | /// The render object's layout behavior is to fill all available space, the parent of this object must |
59 | /// provide bounded layout constraints. |
60 | /// {@endtemplate} |
61 | /// |
62 | /// {@template flutter.rendering.RenderAndroidView.gestures} |
63 | /// The render object participates in Flutter's gesture arenas, and dispatches touch events to the |
64 | /// platform view iff it won the arena. Specific gestures that should be dispatched to the platform |
65 | /// view can be specified with factories in the `gestureRecognizers` constructor parameter or |
66 | /// by calling `updateGestureRecognizers`. If the set of gesture recognizers is empty, the gesture |
67 | /// will be dispatched to the platform view iff it was not claimed by any other gesture recognizer. |
68 | /// {@endtemplate} |
69 | /// |
70 | /// See also: |
71 | /// |
72 | /// * [AndroidView] which is a widget that is used to show an Android view. |
73 | /// * [PlatformViewsService] which is a service for controlling platform views. |
74 | class RenderAndroidView extends PlatformViewRenderBox { |
75 | /// Creates a render object for an Android view. |
76 | RenderAndroidView({ |
77 | required AndroidViewController viewController, |
78 | required PlatformViewHitTestBehavior hitTestBehavior, |
79 | required Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers, |
80 | Clip clipBehavior = Clip.hardEdge, |
81 | }) : _viewController = viewController, |
82 | _clipBehavior = clipBehavior, |
83 | super( |
84 | controller: viewController, |
85 | hitTestBehavior: hitTestBehavior, |
86 | gestureRecognizers: gestureRecognizers, |
87 | ) { |
88 | _viewController.pointTransformer = (Offset offset) => globalToLocal(offset); |
89 | updateGestureRecognizers(gestureRecognizers); |
90 | _viewController.addOnPlatformViewCreatedListener(_onPlatformViewCreated); |
91 | this.hitTestBehavior = hitTestBehavior; |
92 | _setOffset(); |
93 | } |
94 | |
95 | _PlatformViewState _state = _PlatformViewState.uninitialized; |
96 | |
97 | Size? _currentTextureSize; |
98 | |
99 | bool _isDisposed = false; |
100 | |
101 | /// The Android view controller for the Android view associated with this render object. |
102 | @override |
103 | AndroidViewController get controller => _viewController; |
104 | |
105 | AndroidViewController _viewController; |
106 | |
107 | /// Sets a new Android view controller. |
108 | @override |
109 | set controller(AndroidViewController controller) { |
110 | assert(!_isDisposed); |
111 | if (_viewController == controller) { |
112 | return; |
113 | } |
114 | _viewController.removeOnPlatformViewCreatedListener(_onPlatformViewCreated); |
115 | super.controller = controller; |
116 | _viewController = controller; |
117 | _viewController.pointTransformer = (Offset offset) => globalToLocal(offset); |
118 | _sizePlatformView(); |
119 | if (_viewController.isCreated) { |
120 | markNeedsSemanticsUpdate(); |
121 | } |
122 | _viewController.addOnPlatformViewCreatedListener(_onPlatformViewCreated); |
123 | } |
124 | |
125 | /// {@macro flutter.material.Material.clipBehavior} |
126 | /// |
127 | /// Defaults to [Clip.hardEdge]. |
128 | Clip get clipBehavior => _clipBehavior; |
129 | Clip _clipBehavior = Clip.hardEdge; |
130 | set clipBehavior(Clip value) { |
131 | if (value != _clipBehavior) { |
132 | _clipBehavior = value; |
133 | markNeedsPaint(); |
134 | markNeedsSemanticsUpdate(); |
135 | } |
136 | } |
137 | |
138 | void _onPlatformViewCreated(int id) { |
139 | assert(!_isDisposed); |
140 | markNeedsSemanticsUpdate(); |
141 | } |
142 | |
143 | @override |
144 | bool get sizedByParent => true; |
145 | |
146 | @override |
147 | bool get alwaysNeedsCompositing => true; |
148 | |
149 | @override |
150 | bool get isRepaintBoundary => true; |
151 | |
152 | @override |
153 | @protected |
154 | Size computeDryLayout(covariant BoxConstraints constraints) { |
155 | return constraints.biggest; |
156 | } |
157 | |
158 | @override |
159 | void performResize() { |
160 | super.performResize(); |
161 | _sizePlatformView(); |
162 | } |
163 | |
164 | Future<void> _sizePlatformView() async { |
165 | // Android virtual displays cannot have a zero size. |
166 | // Trying to size it to 0 crashes the app, which was happening when starting the app |
167 | // with a locked screen (see: https://github.com/flutter/flutter/issues/20456). |
168 | if (_state == _PlatformViewState.resizing || size.isEmpty) { |
169 | return; |
170 | } |
171 | |
172 | _state = _PlatformViewState.resizing; |
173 | markNeedsPaint(); |
174 | |
175 | Size targetSize; |
176 | do { |
177 | targetSize = size; |
178 | _currentTextureSize = await _viewController.setSize(targetSize); |
179 | if (_isDisposed) { |
180 | return; |
181 | } |
182 | // We've resized the platform view to targetSize, but it is possible that |
183 | // while we were resizing the render object's size was changed again. |
184 | // In that case we will resize the platform view again. |
185 | } while (size != targetSize); |
186 | |
187 | _state = _PlatformViewState.ready; |
188 | markNeedsPaint(); |
189 | } |
190 | |
191 | // Sets the offset of the underlying platform view on the platform side. |
192 | // |
193 | // This allows the Android native view to draw the a11y highlights in the same |
194 | // location on the screen as the platform view widget in the Flutter framework. |
195 | // |
196 | // It also allows platform code to obtain the correct position of the Android |
197 | // native view on the screen. |
198 | void _setOffset() { |
199 | SchedulerBinding.instance.addPostFrameCallback((_) async { |
200 | if (!_isDisposed) { |
201 | if (attached) { |
202 | await _viewController.setOffset(localToGlobal(Offset.zero)); |
203 | } |
204 | // Schedule a new post frame callback. |
205 | _setOffset(); |
206 | } |
207 | }, debugLabel: 'RenderAndroidView.setOffset'); |
208 | } |
209 | |
210 | @override |
211 | void paint(PaintingContext context, Offset offset) { |
212 | if (_viewController.textureId == null || _currentTextureSize == null) { |
213 | return; |
214 | } |
215 | |
216 | // As resizing the Android view happens asynchronously we don't know exactly when is a |
217 | // texture frame with the new size is ready for consumption. |
218 | // TextureLayer is unaware of the texture frame's size and always maps it to the |
219 | // specified rect. If the rect we provide has a different size from the current texture frame's |
220 | // size the texture frame will be scaled. |
221 | // To prevent unwanted scaling artifacts while resizing, clip the texture. |
222 | // This guarantees that the size of the texture frame we're painting is always |
223 | // _currentAndroidTextureSize. |
224 | final bool isTextureLargerThanWidget = |
225 | _currentTextureSize!.width > size.width || _currentTextureSize!.height > size.height; |
226 | if (isTextureLargerThanWidget && clipBehavior != Clip.none) { |
227 | _clipRectLayer.layer = context.pushClipRect( |
228 | true, |
229 | offset, |
230 | offset & size, |
231 | _paintTexture, |
232 | clipBehavior: clipBehavior, |
233 | oldLayer: _clipRectLayer.layer, |
234 | ); |
235 | return; |
236 | } |
237 | _clipRectLayer.layer = null; |
238 | _paintTexture(context, offset); |
239 | } |
240 | |
241 | final LayerHandle<ClipRectLayer> _clipRectLayer = LayerHandle<ClipRectLayer>(); |
242 | |
243 | @override |
244 | void dispose() { |
245 | _isDisposed = true; |
246 | _clipRectLayer.layer = null; |
247 | _viewController.removeOnPlatformViewCreatedListener(_onPlatformViewCreated); |
248 | super.dispose(); |
249 | } |
250 | |
251 | void _paintTexture(PaintingContext context, Offset offset) { |
252 | if (_currentTextureSize == null) { |
253 | return; |
254 | } |
255 | |
256 | context.addLayer( |
257 | TextureLayer(rect: offset & _currentTextureSize!, textureId: _viewController.textureId!), |
258 | ); |
259 | } |
260 | |
261 | @override |
262 | void describeSemanticsConfiguration(SemanticsConfiguration config) { |
263 | // Don't call the super implementation since `platformViewId` should |
264 | // be set only when the platform view is created, but the concept of |
265 | // a "created" platform view belongs to this subclass. |
266 | config.isSemanticBoundary = true; |
267 | |
268 | if (_viewController.isCreated) { |
269 | config.platformViewId = _viewController.viewId; |
270 | } |
271 | } |
272 | } |
273 | |
274 | /// Common render-layer functionality for iOS and macOS platform views. |
275 | /// |
276 | /// Provides the basic rendering logic for iOS and macOS platformviews. |
277 | /// Subclasses shall override handleEvent in order to execute custom event logic. |
278 | /// T represents the class of the view controller for the corresponding widget. |
279 | abstract class RenderDarwinPlatformView<T extends DarwinPlatformViewController> extends RenderBox { |
280 | /// Creates a render object for a platform view. |
281 | RenderDarwinPlatformView({ |
282 | required T viewController, |
283 | required this.hitTestBehavior, |
284 | required Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers, |
285 | }) : _viewController = viewController { |
286 | updateGestureRecognizers(gestureRecognizers); |
287 | } |
288 | |
289 | /// The unique identifier of the platform view controlled by this controller. |
290 | T get viewController => _viewController; |
291 | T _viewController; |
292 | set viewController(T value) { |
293 | if (_viewController == value) { |
294 | return; |
295 | } |
296 | final bool needsSemanticsUpdate = _viewController.id != value.id; |
297 | _viewController = value; |
298 | markNeedsPaint(); |
299 | if (needsSemanticsUpdate) { |
300 | markNeedsSemanticsUpdate(); |
301 | } |
302 | } |
303 | |
304 | /// How to behave during hit testing. |
305 | // The implicit setter is enough here as changing this value will just affect |
306 | // any newly arriving events there's nothing we need to invalidate. |
307 | PlatformViewHitTestBehavior hitTestBehavior; |
308 | |
309 | @override |
310 | bool get sizedByParent => true; |
311 | |
312 | @override |
313 | bool get alwaysNeedsCompositing => true; |
314 | |
315 | @override |
316 | bool get isRepaintBoundary => true; |
317 | |
318 | PointerEvent? _lastPointerDownEvent; |
319 | |
320 | _UiKitViewGestureRecognizer? _gestureRecognizer; |
321 | |
322 | @override |
323 | @protected |
324 | Size computeDryLayout(covariant BoxConstraints constraints) { |
325 | return constraints.biggest; |
326 | } |
327 | |
328 | @override |
329 | void paint(PaintingContext context, Offset offset) { |
330 | context.addLayer(PlatformViewLayer(rect: offset & size, viewId: _viewController.id)); |
331 | } |
332 | |
333 | @override |
334 | bool hitTest(BoxHitTestResult result, {Offset? position}) { |
335 | if (hitTestBehavior == PlatformViewHitTestBehavior.transparent || !size.contains(position!)) { |
336 | return false; |
337 | } |
338 | result.add(BoxHitTestEntry(this, position)); |
339 | return hitTestBehavior == PlatformViewHitTestBehavior.opaque; |
340 | } |
341 | |
342 | @override |
343 | bool hitTestSelf(Offset position) => hitTestBehavior != PlatformViewHitTestBehavior.transparent; |
344 | |
345 | // This is registered as a global PointerRoute while the render object is attached. |
346 | void _handleGlobalPointerEvent(PointerEvent event) { |
347 | if (event is! PointerDownEvent) { |
348 | return; |
349 | } |
350 | if (!(Offset.zero & size).contains(globalToLocal(event.position))) { |
351 | return; |
352 | } |
353 | if ((event.original ?? event) != _lastPointerDownEvent) { |
354 | // The pointer event is in the bounds of this render box, but we didn't get it in handleEvent. |
355 | // This means that the pointer event was absorbed by a different render object. |
356 | // Since on the platform side the FlutterTouchIntercepting view is seeing all events that are |
357 | // within its bounds we need to tell it to reject the current touch sequence. |
358 | _viewController.rejectGesture(); |
359 | } |
360 | _lastPointerDownEvent = null; |
361 | } |
362 | |
363 | @override |
364 | void describeSemanticsConfiguration(SemanticsConfiguration config) { |
365 | super.describeSemanticsConfiguration(config); |
366 | config.isSemanticBoundary = true; |
367 | config.platformViewId = _viewController.id; |
368 | } |
369 | |
370 | @override |
371 | void attach(PipelineOwner owner) { |
372 | super.attach(owner); |
373 | GestureBinding.instance.pointerRouter.addGlobalRoute(_handleGlobalPointerEvent); |
374 | } |
375 | |
376 | @override |
377 | void detach() { |
378 | GestureBinding.instance.pointerRouter.removeGlobalRoute(_handleGlobalPointerEvent); |
379 | super.detach(); |
380 | } |
381 | |
382 | /// {@macro flutter.rendering.PlatformViewRenderBox.updateGestureRecognizers} |
383 | void updateGestureRecognizers(Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers); |
384 | } |
385 | |
386 | /// A render object for an iOS UIKit UIView. |
387 | /// |
388 | /// [RenderUiKitView] is responsible for sizing and displaying an iOS |
389 | /// [UIView](https://developer.apple.com/documentation/uikit/uiview). |
390 | /// |
391 | /// UIViews are added as subviews of the FlutterView and are composited by Quartz. |
392 | /// |
393 | /// The viewController is typically generated by [PlatformViewsRegistry.getNextPlatformViewId], the UIView |
394 | /// must have been created by calling [PlatformViewsService.initUiKitView]. |
395 | /// |
396 | /// {@macro flutter.rendering.RenderAndroidView.layout} |
397 | /// |
398 | /// {@macro flutter.rendering.RenderAndroidView.gestures} |
399 | /// |
400 | /// See also: |
401 | /// |
402 | /// * [UiKitView], which is a widget that is used to show a UIView. |
403 | /// * [PlatformViewsService], which is a service for controlling platform views. |
404 | class RenderUiKitView extends RenderDarwinPlatformView<UiKitViewController> { |
405 | /// Creates a render object for an iOS UIView. |
406 | RenderUiKitView({ |
407 | required super.viewController, |
408 | required super.hitTestBehavior, |
409 | required super.gestureRecognizers, |
410 | }); |
411 | |
412 | /// {@macro flutter.rendering.PlatformViewRenderBox.updateGestureRecognizers} |
413 | @override |
414 | void updateGestureRecognizers(Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers) { |
415 | assert( |
416 | _factoriesTypeSet(gestureRecognizers).length == gestureRecognizers.length, |
417 | 'There were multiple gesture recognizer factories for the same type, there must only be a single ' |
418 | 'gesture recognizer factory for each gesture recognizer type.', |
419 | ); |
420 | if (_factoryTypesSetEquals( |
421 | gestureRecognizers, |
422 | _gestureRecognizer?.gestureRecognizerFactories, |
423 | )) { |
424 | return; |
425 | } |
426 | _gestureRecognizer?.dispose(); |
427 | _gestureRecognizer = _UiKitViewGestureRecognizer(viewController, gestureRecognizers); |
428 | } |
429 | |
430 | @override |
431 | void handleEvent(PointerEvent event, HitTestEntry entry) { |
432 | if (event is! PointerDownEvent) { |
433 | return; |
434 | } |
435 | _gestureRecognizer!.addPointer(event); |
436 | _lastPointerDownEvent = event.original ?? event; |
437 | } |
438 | |
439 | @override |
440 | void detach() { |
441 | _gestureRecognizer!.reset(); |
442 | super.detach(); |
443 | } |
444 | |
445 | @override |
446 | void dispose() { |
447 | _gestureRecognizer?.dispose(); |
448 | super.dispose(); |
449 | } |
450 | } |
451 | |
452 | /// A render object for a macOS platform view. |
453 | class RenderAppKitView extends RenderDarwinPlatformView<AppKitViewController> { |
454 | /// Creates a render object for a macOS AppKitView. |
455 | RenderAppKitView({ |
456 | required super.viewController, |
457 | required super.hitTestBehavior, |
458 | required super.gestureRecognizers, |
459 | }); |
460 | |
461 | // TODO(schectman): Add gesture functionality to macOS platform view when implemented. |
462 | // https://github.com/flutter/flutter/issues/128519 |
463 | // This method will need to behave the same as the same-named method for RenderUiKitView, |
464 | // but use a _AppKitViewGestureRecognizer or equivalent, whose constructor shall accept an |
465 | // AppKitViewController. |
466 | @override |
467 | void updateGestureRecognizers(Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers) {} |
468 | } |
469 | |
470 | // This recognizer constructs gesture recognizers from a set of gesture recognizer factories |
471 | // it was give, adds all of them to a gesture arena team with the _UiKitViewGestureRecognizer |
472 | // as the team captain. |
473 | // When the team wins a gesture the recognizer notifies the engine that it should release |
474 | // the touch sequence to the embedded UIView. |
475 | class _UiKitViewGestureRecognizer extends OneSequenceGestureRecognizer { |
476 | _UiKitViewGestureRecognizer(this.controller, this.gestureRecognizerFactories) { |
477 | team = GestureArenaTeam()..captain = this; |
478 | _gestureRecognizers = |
479 | gestureRecognizerFactories.map((Factory<OneSequenceGestureRecognizer> recognizerFactory) { |
480 | final OneSequenceGestureRecognizer gestureRecognizer = recognizerFactory.constructor(); |
481 | gestureRecognizer.team = team; |
482 | // The below gesture recognizers requires at least one non-empty callback to |
483 | // compete in the gesture arena. |
484 | // https://github.com/flutter/flutter/issues/35394#issuecomment-562285087 |
485 | if (gestureRecognizer is LongPressGestureRecognizer) { |
486 | gestureRecognizer.onLongPress ??= () {}; |
487 | } else if (gestureRecognizer is DragGestureRecognizer) { |
488 | gestureRecognizer.onDown ??= (_) {}; |
489 | } else if (gestureRecognizer is TapGestureRecognizer) { |
490 | gestureRecognizer.onTapDown ??= (_) {}; |
491 | } |
492 | return gestureRecognizer; |
493 | }).toSet(); |
494 | } |
495 | |
496 | // We use OneSequenceGestureRecognizers as they support gesture arena teams. |
497 | // TODO(amirh): get a list of GestureRecognizers here. |
498 | // https://github.com/flutter/flutter/issues/20953 |
499 | final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizerFactories; |
500 | late Set<OneSequenceGestureRecognizer> _gestureRecognizers; |
501 | |
502 | final UiKitViewController controller; |
503 | |
504 | @override |
505 | void addAllowedPointer(PointerDownEvent event) { |
506 | super.addAllowedPointer(event); |
507 | for (final OneSequenceGestureRecognizer recognizer in _gestureRecognizers) { |
508 | recognizer.addPointer(event); |
509 | } |
510 | } |
511 | |
512 | @override |
513 | String get debugDescription => 'UIKit view'; |
514 | |
515 | @override |
516 | void didStopTrackingLastPointer(int pointer) {} |
517 | |
518 | @override |
519 | void handleEvent(PointerEvent event) { |
520 | stopTrackingIfPointerNoLongerDown(event); |
521 | } |
522 | |
523 | @override |
524 | void acceptGesture(int pointer) { |
525 | controller.acceptGesture(); |
526 | } |
527 | |
528 | @override |
529 | void rejectGesture(int pointer) { |
530 | controller.rejectGesture(); |
531 | } |
532 | |
533 | void reset() { |
534 | resolve(GestureDisposition.rejected); |
535 | } |
536 | } |
537 | |
538 | typedef _HandlePointerEvent = Future<void> Function(PointerEvent event); |
539 | |
540 | // This recognizer constructs gesture recognizers from a set of gesture recognizer factories |
541 | // it was give, adds all of them to a gesture arena team with the _PlatformViewGestureRecognizer |
542 | // as the team captain. |
543 | // As long as the gesture arena is unresolved, the recognizer caches all pointer events. |
544 | // When the team wins, the recognizer sends all the cached pointer events to `_handlePointerEvent`, and |
545 | // sets itself to a "forwarding mode" where it will forward any new pointer event to `_handlePointerEvent`. |
546 | class _PlatformViewGestureRecognizer extends OneSequenceGestureRecognizer { |
547 | _PlatformViewGestureRecognizer( |
548 | _HandlePointerEvent handlePointerEvent, |
549 | this.gestureRecognizerFactories, |
550 | ) { |
551 | team = GestureArenaTeam()..captain = this; |
552 | _gestureRecognizers = |
553 | gestureRecognizerFactories.map((Factory<OneSequenceGestureRecognizer> recognizerFactory) { |
554 | final OneSequenceGestureRecognizer gestureRecognizer = recognizerFactory.constructor(); |
555 | gestureRecognizer.team = team; |
556 | // The below gesture recognizers requires at least one non-empty callback to |
557 | // compete in the gesture arena. |
558 | // https://github.com/flutter/flutter/issues/35394#issuecomment-562285087 |
559 | if (gestureRecognizer is LongPressGestureRecognizer) { |
560 | gestureRecognizer.onLongPress ??= () {}; |
561 | } else if (gestureRecognizer is DragGestureRecognizer) { |
562 | gestureRecognizer.onDown ??= (_) {}; |
563 | } else if (gestureRecognizer is TapGestureRecognizer) { |
564 | gestureRecognizer.onTapDown ??= (_) {}; |
565 | } |
566 | return gestureRecognizer; |
567 | }).toSet(); |
568 | _handlePointerEvent = handlePointerEvent; |
569 | } |
570 | |
571 | late _HandlePointerEvent _handlePointerEvent; |
572 | |
573 | // Maps a pointer to a list of its cached pointer events. |
574 | // Before the arena for a pointer is resolved all events are cached here, if we win the arena |
575 | // the cached events are dispatched to `_handlePointerEvent`, if we lose the arena we clear the cache for |
576 | // the pointer. |
577 | final Map<int, List<PointerEvent>> cachedEvents = <int, List<PointerEvent>>{}; |
578 | |
579 | // Pointer for which we have already won the arena, events for pointers in this set are |
580 | // immediately dispatched to `_handlePointerEvent`. |
581 | final Set<int> forwardedPointers = <int>{}; |
582 | |
583 | // We use OneSequenceGestureRecognizers as they support gesture arena teams. |
584 | // TODO(amirh): get a list of GestureRecognizers here. |
585 | // https://github.com/flutter/flutter/issues/20953 |
586 | final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizerFactories; |
587 | late Set<OneSequenceGestureRecognizer> _gestureRecognizers; |
588 | |
589 | @override |
590 | void addAllowedPointer(PointerDownEvent event) { |
591 | super.addAllowedPointer(event); |
592 | for (final OneSequenceGestureRecognizer recognizer in _gestureRecognizers) { |
593 | recognizer.addPointer(event); |
594 | } |
595 | } |
596 | |
597 | @override |
598 | String get debugDescription => 'Platform view'; |
599 | |
600 | @override |
601 | void didStopTrackingLastPointer(int pointer) {} |
602 | |
603 | @override |
604 | void handleEvent(PointerEvent event) { |
605 | if (!forwardedPointers.contains(event.pointer)) { |
606 | _cacheEvent(event); |
607 | } else { |
608 | _handlePointerEvent(event); |
609 | } |
610 | stopTrackingIfPointerNoLongerDown(event); |
611 | } |
612 | |
613 | @override |
614 | void acceptGesture(int pointer) { |
615 | _flushPointerCache(pointer); |
616 | forwardedPointers.add(pointer); |
617 | } |
618 | |
619 | @override |
620 | void rejectGesture(int pointer) { |
621 | stopTrackingPointer(pointer); |
622 | cachedEvents.remove(pointer); |
623 | } |
624 | |
625 | void _cacheEvent(PointerEvent event) { |
626 | if (!cachedEvents.containsKey(event.pointer)) { |
627 | cachedEvents[event.pointer] = <PointerEvent>[]; |
628 | } |
629 | cachedEvents[event.pointer]!.add(event); |
630 | } |
631 | |
632 | void _flushPointerCache(int pointer) { |
633 | cachedEvents.remove(pointer)?.forEach(_handlePointerEvent); |
634 | } |
635 | |
636 | @override |
637 | void stopTrackingPointer(int pointer) { |
638 | super.stopTrackingPointer(pointer); |
639 | forwardedPointers.remove(pointer); |
640 | } |
641 | |
642 | void reset() { |
643 | forwardedPointers.forEach(super.stopTrackingPointer); |
644 | forwardedPointers.clear(); |
645 | cachedEvents.keys.forEach(super.stopTrackingPointer); |
646 | cachedEvents.clear(); |
647 | resolve(GestureDisposition.rejected); |
648 | } |
649 | } |
650 | |
651 | /// A render object for embedding a platform view. |
652 | /// |
653 | /// [PlatformViewRenderBox] presents a platform view by adding a [PlatformViewLayer] layer, |
654 | /// integrates it with the gesture arenas system and adds relevant semantic nodes to the semantics tree. |
655 | class PlatformViewRenderBox extends RenderBox with _PlatformViewGestureMixin { |
656 | /// Creating a render object for a [PlatformViewSurface]. |
657 | PlatformViewRenderBox({ |
658 | required PlatformViewController controller, |
659 | required PlatformViewHitTestBehavior hitTestBehavior, |
660 | required Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers, |
661 | }) : assert(controller.viewId > -1), |
662 | _controller = controller { |
663 | this.hitTestBehavior = hitTestBehavior; |
664 | updateGestureRecognizers(gestureRecognizers); |
665 | } |
666 | |
667 | /// The controller for this render object. |
668 | PlatformViewController get controller => _controller; |
669 | PlatformViewController _controller; |
670 | |
671 | /// Setting this value to a new value will result in a repaint. |
672 | set controller(covariant PlatformViewController controller) { |
673 | assert(controller.viewId > -1); |
674 | |
675 | if (_controller == controller) { |
676 | return; |
677 | } |
678 | final bool needsSemanticsUpdate = _controller.viewId != controller.viewId; |
679 | _controller = controller; |
680 | markNeedsPaint(); |
681 | if (needsSemanticsUpdate) { |
682 | markNeedsSemanticsUpdate(); |
683 | } |
684 | } |
685 | |
686 | /// {@template flutter.rendering.PlatformViewRenderBox.updateGestureRecognizers} |
687 | /// Updates which gestures should be forwarded to the platform view. |
688 | /// |
689 | /// Gesture recognizers created by factories in this set participate in the gesture arena for each |
690 | /// pointer that was put down on the render box. If any of the recognizers on this list wins the |
691 | /// gesture arena, the entire pointer event sequence starting from the pointer down event |
692 | /// will be dispatched to the Android view. |
693 | /// |
694 | /// The `gestureRecognizers` property must not contain more than one factory with the same [Factory.type]. |
695 | /// |
696 | /// Setting a new set of gesture recognizer factories with the same [Factory.type]s as the current |
697 | /// set has no effect, because the factories' constructors would have already been called with the previous set. |
698 | /// {@endtemplate} |
699 | /// |
700 | /// Any active gesture arena the `PlatformView` participates in is rejected when the |
701 | /// set of gesture recognizers is changed. |
702 | void updateGestureRecognizers(Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers) { |
703 | _updateGestureRecognizersWithCallBack(gestureRecognizers, _controller.dispatchPointerEvent); |
704 | } |
705 | |
706 | @override |
707 | bool get sizedByParent => true; |
708 | |
709 | @override |
710 | bool get alwaysNeedsCompositing => true; |
711 | |
712 | @override |
713 | bool get isRepaintBoundary => true; |
714 | |
715 | @override |
716 | @protected |
717 | Size computeDryLayout(covariant BoxConstraints constraints) { |
718 | return constraints.biggest; |
719 | } |
720 | |
721 | @override |
722 | void paint(PaintingContext context, Offset offset) { |
723 | context.addLayer(PlatformViewLayer(rect: offset & size, viewId: _controller.viewId)); |
724 | } |
725 | |
726 | @override |
727 | void describeSemanticsConfiguration(SemanticsConfiguration config) { |
728 | super.describeSemanticsConfiguration(config); |
729 | config.isSemanticBoundary = true; |
730 | config.platformViewId = _controller.viewId; |
731 | } |
732 | } |
733 | |
734 | /// The Mixin handling the pointer events and gestures of a platform view render box. |
735 | mixin _PlatformViewGestureMixin on RenderBox implements MouseTrackerAnnotation { |
736 | /// How to behave during hit testing. |
737 | // Changing _hitTestBehavior might affect which objects are considered hovered over. |
738 | set hitTestBehavior(PlatformViewHitTestBehavior value) { |
739 | if (value != _hitTestBehavior) { |
740 | _hitTestBehavior = value; |
741 | if (owner != null) { |
742 | markNeedsPaint(); |
743 | } |
744 | } |
745 | } |
746 | |
747 | PlatformViewHitTestBehavior? _hitTestBehavior; |
748 | |
749 | _HandlePointerEvent? _handlePointerEvent; |
750 | |
751 | /// {@macro flutter.rendering.PlatformViewRenderBox.updateGestureRecognizers} |
752 | /// |
753 | /// Any active gesture arena the `PlatformView` participates in is rejected when the |
754 | /// set of gesture recognizers is changed. |
755 | void _updateGestureRecognizersWithCallBack( |
756 | Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers, |
757 | _HandlePointerEvent handlePointerEvent, |
758 | ) { |
759 | assert( |
760 | _factoriesTypeSet(gestureRecognizers).length == gestureRecognizers.length, |
761 | 'There were multiple gesture recognizer factories for the same type, there must only be a single ' |
762 | 'gesture recognizer factory for each gesture recognizer type.', |
763 | ); |
764 | if (_factoryTypesSetEquals( |
765 | gestureRecognizers, |
766 | _gestureRecognizer?.gestureRecognizerFactories, |
767 | )) { |
768 | return; |
769 | } |
770 | _gestureRecognizer?.dispose(); |
771 | _gestureRecognizer = _PlatformViewGestureRecognizer(handlePointerEvent, gestureRecognizers); |
772 | _handlePointerEvent = handlePointerEvent; |
773 | } |
774 | |
775 | _PlatformViewGestureRecognizer? _gestureRecognizer; |
776 | |
777 | @override |
778 | bool hitTest(BoxHitTestResult result, {required Offset position}) { |
779 | if (_hitTestBehavior == PlatformViewHitTestBehavior.transparent || !size.contains(position)) { |
780 | return false; |
781 | } |
782 | result.add(BoxHitTestEntry(this, position)); |
783 | return _hitTestBehavior == PlatformViewHitTestBehavior.opaque; |
784 | } |
785 | |
786 | @override |
787 | bool hitTestSelf(Offset position) => _hitTestBehavior != PlatformViewHitTestBehavior.transparent; |
788 | |
789 | @override |
790 | PointerEnterEventListener? get onEnter => null; |
791 | |
792 | @override |
793 | PointerExitEventListener? get onExit => null; |
794 | |
795 | @override |
796 | MouseCursor get cursor => MouseCursor.uncontrolled; |
797 | |
798 | @override |
799 | bool get validForMouseTracker => true; |
800 | |
801 | @override |
802 | void handleEvent(PointerEvent event, HitTestEntry entry) { |
803 | if (event is PointerDownEvent) { |
804 | _gestureRecognizer!.addPointer(event); |
805 | } |
806 | if (event is PointerHoverEvent) { |
807 | _handlePointerEvent?.call(event); |
808 | } |
809 | } |
810 | |
811 | @override |
812 | void detach() { |
813 | _gestureRecognizer!.reset(); |
814 | super.detach(); |
815 | } |
816 | |
817 | @override |
818 | void dispose() { |
819 | _gestureRecognizer?.dispose(); |
820 | super.dispose(); |
821 | } |
822 | } |
823 |
Definitions
- PlatformViewHitTestBehavior
- _PlatformViewState
- _factoryTypesSetEquals
- _factoriesTypeSet
- RenderAndroidView
- RenderAndroidView
- controller
- controller
- clipBehavior
- clipBehavior
- _onPlatformViewCreated
- sizedByParent
- alwaysNeedsCompositing
- isRepaintBoundary
- computeDryLayout
- performResize
- _sizePlatformView
- _setOffset
- paint
- dispose
- _paintTexture
- describeSemanticsConfiguration
- RenderDarwinPlatformView
- RenderDarwinPlatformView
- viewController
- viewController
- sizedByParent
- alwaysNeedsCompositing
- isRepaintBoundary
- computeDryLayout
- paint
- hitTest
- hitTestSelf
- _handleGlobalPointerEvent
- describeSemanticsConfiguration
- attach
- detach
- updateGestureRecognizers
- RenderUiKitView
- RenderUiKitView
- updateGestureRecognizers
- handleEvent
- detach
- dispose
- RenderAppKitView
- RenderAppKitView
- updateGestureRecognizers
- _UiKitViewGestureRecognizer
- _UiKitViewGestureRecognizer
- addAllowedPointer
- debugDescription
- didStopTrackingLastPointer
- handleEvent
- acceptGesture
- rejectGesture
- reset
- _PlatformViewGestureRecognizer
- _PlatformViewGestureRecognizer
- addAllowedPointer
- debugDescription
- didStopTrackingLastPointer
- handleEvent
- acceptGesture
- rejectGesture
- _cacheEvent
- _flushPointerCache
- stopTrackingPointer
- reset
- PlatformViewRenderBox
- PlatformViewRenderBox
- controller
- controller
- updateGestureRecognizers
- sizedByParent
- alwaysNeedsCompositing
- isRepaintBoundary
- computeDryLayout
- paint
- describeSemanticsConfiguration
- _PlatformViewGestureMixin
- hitTestBehavior
- _updateGestureRecognizersWithCallBack
- hitTest
- hitTestSelf
- onEnter
- onExit
- cursor
- validForMouseTracker
- handleEvent
- detach
Learn more about Flutter for embedded and desktop on industrialflutter.com