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