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';
6library;
7
8import 'package:flutter/foundation.dart';
9import 'package:flutter/gestures.dart';
10import 'package:flutter/scheduler.dart';
11import 'package:flutter/semantics.dart';
12import 'package:flutter/services.dart';
13
14import 'box.dart';
15import 'layer.dart';
16import 'object.dart';
17
18/// How an embedded platform view behave during hit tests.
19enum 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
34enum _PlatformViewState { uninitialized, resizing, ready }
35
36bool _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
46Set<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.
74class 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.
279abstract 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.
404class 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.
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(this.controller, this.gestureRecognizerFactories) {
477 team = GestureArenaTeam()..captain = this;
478 _gestureRecognizers = gestureRecognizerFactories.map((
479 Factory<OneSequenceGestureRecognizer> recognizerFactory,
480 ) {
481 final OneSequenceGestureRecognizer gestureRecognizer = recognizerFactory.constructor();
482 gestureRecognizer.team = team;
483 // The below gesture recognizers requires at least one non-empty callback to
484 // compete in the gesture arena.
485 // https://github.com/flutter/flutter/issues/35394#issuecomment-562285087
486 if (gestureRecognizer is LongPressGestureRecognizer) {
487 gestureRecognizer.onLongPress ??= () {};
488 } else if (gestureRecognizer is DragGestureRecognizer) {
489 gestureRecognizer.onDown ??= (_) {};
490 } else if (gestureRecognizer is TapGestureRecognizer) {
491 gestureRecognizer.onTapDown ??= (_) {};
492 }
493 return gestureRecognizer;
494 }).toSet();
495 }
496
497 // We use OneSequenceGestureRecognizers as they support gesture arena teams.
498 // TODO(amirh): get a list of GestureRecognizers here.
499 // https://github.com/flutter/flutter/issues/20953
500 final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizerFactories;
501 late Set<OneSequenceGestureRecognizer> _gestureRecognizers;
502
503 final UiKitViewController controller;
504
505 @override
506 void addAllowedPointer(PointerDownEvent event) {
507 super.addAllowedPointer(event);
508 for (final OneSequenceGestureRecognizer recognizer in _gestureRecognizers) {
509 recognizer.addPointer(event);
510 }
511 }
512
513 @override
514 String get debugDescription => 'UIKit view';
515
516 @override
517 void didStopTrackingLastPointer(int pointer) {}
518
519 @override
520 void handleEvent(PointerEvent event) {
521 stopTrackingIfPointerNoLongerDown(event);
522 }
523
524 @override
525 void acceptGesture(int pointer) {
526 controller.acceptGesture();
527 }
528
529 @override
530 void rejectGesture(int pointer) {
531 controller.rejectGesture();
532 }
533
534 void reset() {
535 resolve(GestureDisposition.rejected);
536 }
537}
538
539typedef _HandlePointerEvent = Future<void> Function(PointerEvent event);
540
541// This recognizer constructs gesture recognizers from a set of gesture recognizer factories
542// it was give, adds all of them to a gesture arena team with the _PlatformViewGestureRecognizer
543// as the team captain.
544// As long as the gesture arena is unresolved, the recognizer caches all pointer events.
545// When the team wins, the recognizer sends all the cached pointer events to `_handlePointerEvent`, and
546// sets itself to a "forwarding mode" where it will forward any new pointer event to `_handlePointerEvent`.
547class _PlatformViewGestureRecognizer extends OneSequenceGestureRecognizer {
548 _PlatformViewGestureRecognizer(
549 _HandlePointerEvent handlePointerEvent,
550 this.gestureRecognizerFactories,
551 ) {
552 team = GestureArenaTeam()..captain = this;
553 _gestureRecognizers = gestureRecognizerFactories.map((
554 Factory<OneSequenceGestureRecognizer> recognizerFactory,
555 ) {
556 final OneSequenceGestureRecognizer gestureRecognizer = recognizerFactory.constructor();
557 gestureRecognizer.team = team;
558 // The below gesture recognizers requires at least one non-empty callback to
559 // compete in the gesture arena.
560 // https://github.com/flutter/flutter/issues/35394#issuecomment-562285087
561 if (gestureRecognizer is LongPressGestureRecognizer) {
562 gestureRecognizer.onLongPress ??= () {};
563 } else if (gestureRecognizer is DragGestureRecognizer) {
564 gestureRecognizer.onDown ??= (_) {};
565 } else if (gestureRecognizer is TapGestureRecognizer) {
566 gestureRecognizer.onTapDown ??= (_) {};
567 }
568 return gestureRecognizer;
569 }).toSet();
570 _handlePointerEvent = handlePointerEvent;
571 }
572
573 late _HandlePointerEvent _handlePointerEvent;
574
575 // Maps a pointer to a list of its cached pointer events.
576 // Before the arena for a pointer is resolved all events are cached here, if we win the arena
577 // the cached events are dispatched to `_handlePointerEvent`, if we lose the arena we clear the cache for
578 // the pointer.
579 final Map<int, List<PointerEvent>> cachedEvents = <int, List<PointerEvent>>{};
580
581 // Pointer for which we have already won the arena, events for pointers in this set are
582 // immediately dispatched to `_handlePointerEvent`.
583 final Set<int> forwardedPointers = <int>{};
584
585 // We use OneSequenceGestureRecognizers as they support gesture arena teams.
586 // TODO(amirh): get a list of GestureRecognizers here.
587 // https://github.com/flutter/flutter/issues/20953
588 final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizerFactories;
589 late Set<OneSequenceGestureRecognizer> _gestureRecognizers;
590
591 @override
592 void addAllowedPointer(PointerDownEvent event) {
593 super.addAllowedPointer(event);
594 for (final OneSequenceGestureRecognizer recognizer in _gestureRecognizers) {
595 recognizer.addPointer(event);
596 }
597 }
598
599 @override
600 String get debugDescription => 'Platform view';
601
602 @override
603 void didStopTrackingLastPointer(int pointer) {}
604
605 @override
606 void handleEvent(PointerEvent event) {
607 if (!forwardedPointers.contains(event.pointer)) {
608 _cacheEvent(event);
609 } else {
610 _handlePointerEvent(event);
611 }
612 stopTrackingIfPointerNoLongerDown(event);
613 }
614
615 @override
616 void acceptGesture(int pointer) {
617 _flushPointerCache(pointer);
618 forwardedPointers.add(pointer);
619 }
620
621 @override
622 void rejectGesture(int pointer) {
623 stopTrackingPointer(pointer);
624 cachedEvents.remove(pointer);
625 }
626
627 void _cacheEvent(PointerEvent event) {
628 if (!cachedEvents.containsKey(event.pointer)) {
629 cachedEvents[event.pointer] = <PointerEvent>[];
630 }
631 cachedEvents[event.pointer]!.add(event);
632 }
633
634 void _flushPointerCache(int pointer) {
635 cachedEvents.remove(pointer)?.forEach(_handlePointerEvent);
636 }
637
638 @override
639 void stopTrackingPointer(int pointer) {
640 super.stopTrackingPointer(pointer);
641 forwardedPointers.remove(pointer);
642 }
643
644 void reset() {
645 forwardedPointers.forEach(super.stopTrackingPointer);
646 forwardedPointers.clear();
647 cachedEvents.keys.forEach(super.stopTrackingPointer);
648 cachedEvents.clear();
649 resolve(GestureDisposition.rejected);
650 }
651}
652
653/// A render object for embedding a platform view.
654///
655/// [PlatformViewRenderBox] presents a platform view by adding a [PlatformViewLayer] layer,
656/// integrates it with the gesture arenas system and adds relevant semantic nodes to the semantics tree.
657class PlatformViewRenderBox extends RenderBox with _PlatformViewGestureMixin {
658 /// Creating a render object for a [PlatformViewSurface].
659 PlatformViewRenderBox({
660 required PlatformViewController controller,
661 required PlatformViewHitTestBehavior hitTestBehavior,
662 required Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers,
663 }) : assert(controller.viewId > -1),
664 _controller = controller {
665 this.hitTestBehavior = hitTestBehavior;
666 updateGestureRecognizers(gestureRecognizers);
667 }
668
669 /// The controller for this render object.
670 PlatformViewController get controller => _controller;
671 PlatformViewController _controller;
672
673 /// Setting this value to a new value will result in a repaint.
674 set controller(covariant PlatformViewController controller) {
675 assert(controller.viewId > -1);
676
677 if (_controller == controller) {
678 return;
679 }
680 final bool needsSemanticsUpdate = _controller.viewId != controller.viewId;
681 _controller = controller;
682 markNeedsPaint();
683 if (needsSemanticsUpdate) {
684 markNeedsSemanticsUpdate();
685 }
686 }
687
688 /// {@template flutter.rendering.PlatformViewRenderBox.updateGestureRecognizers}
689 /// Updates which gestures should be forwarded to the platform view.
690 ///
691 /// Gesture recognizers created by factories in this set participate in the gesture arena for each
692 /// pointer that was put down on the render box. If any of the recognizers on this list wins the
693 /// gesture arena, the entire pointer event sequence starting from the pointer down event
694 /// will be dispatched to the Android view.
695 ///
696 /// The `gestureRecognizers` property must not contain more than one factory with the same [Factory.type].
697 ///
698 /// Setting a new set of gesture recognizer factories with the same [Factory.type]s as the current
699 /// set has no effect, because the factories' constructors would have already been called with the previous set.
700 /// {@endtemplate}
701 ///
702 /// Any active gesture arena the `PlatformView` participates in is rejected when the
703 /// set of gesture recognizers is changed.
704 void updateGestureRecognizers(Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers) {
705 _updateGestureRecognizersWithCallBack(gestureRecognizers, _controller.dispatchPointerEvent);
706 }
707
708 @override
709 bool get sizedByParent => true;
710
711 @override
712 bool get alwaysNeedsCompositing => true;
713
714 @override
715 bool get isRepaintBoundary => true;
716
717 @override
718 @protected
719 Size computeDryLayout(covariant BoxConstraints constraints) {
720 return constraints.biggest;
721 }
722
723 @override
724 void paint(PaintingContext context, Offset offset) {
725 context.addLayer(PlatformViewLayer(rect: offset & size, viewId: _controller.viewId));
726 }
727
728 @override
729 void describeSemanticsConfiguration(SemanticsConfiguration config) {
730 super.describeSemanticsConfiguration(config);
731 config.isSemanticBoundary = true;
732 config.platformViewId = _controller.viewId;
733 }
734}
735
736/// The Mixin handling the pointer events and gestures of a platform view render box.
737mixin _PlatformViewGestureMixin on RenderBox implements MouseTrackerAnnotation {
738 /// How to behave during hit testing.
739 // Changing _hitTestBehavior might affect which objects are considered hovered over.
740 set hitTestBehavior(PlatformViewHitTestBehavior value) {
741 if (value != _hitTestBehavior) {
742 _hitTestBehavior = value;
743 if (owner != null) {
744 markNeedsPaint();
745 }
746 }
747 }
748
749 PlatformViewHitTestBehavior? _hitTestBehavior;
750
751 _HandlePointerEvent? _handlePointerEvent;
752
753 /// {@macro flutter.rendering.PlatformViewRenderBox.updateGestureRecognizers}
754 ///
755 /// Any active gesture arena the `PlatformView` participates in is rejected when the
756 /// set of gesture recognizers is changed.
757 void _updateGestureRecognizersWithCallBack(
758 Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers,
759 _HandlePointerEvent handlePointerEvent,
760 ) {
761 assert(
762 _factoriesTypeSet(gestureRecognizers).length == gestureRecognizers.length,
763 'There were multiple gesture recognizer factories for the same type, there must only be a single '
764 'gesture recognizer factory for each gesture recognizer type.',
765 );
766 if (_factoryTypesSetEquals(
767 gestureRecognizers,
768 _gestureRecognizer?.gestureRecognizerFactories,
769 )) {
770 return;
771 }
772 _gestureRecognizer?.dispose();
773 _gestureRecognizer = _PlatformViewGestureRecognizer(handlePointerEvent, gestureRecognizers);
774 _handlePointerEvent = handlePointerEvent;
775 }
776
777 _PlatformViewGestureRecognizer? _gestureRecognizer;
778
779 @override
780 bool hitTest(BoxHitTestResult result, {required Offset position}) {
781 if (_hitTestBehavior == PlatformViewHitTestBehavior.transparent || !size.contains(position)) {
782 return false;
783 }
784 result.add(BoxHitTestEntry(this, position));
785 return _hitTestBehavior == PlatformViewHitTestBehavior.opaque;
786 }
787
788 @override
789 bool hitTestSelf(Offset position) => _hitTestBehavior != PlatformViewHitTestBehavior.transparent;
790
791 @override
792 PointerEnterEventListener? get onEnter => null;
793
794 @override
795 PointerExitEventListener? get onExit => null;
796
797 @override
798 MouseCursor get cursor => MouseCursor.uncontrolled;
799
800 @override
801 bool get validForMouseTracker => true;
802
803 @override
804 void handleEvent(PointerEvent event, HitTestEntry entry) {
805 if (event is PointerDownEvent) {
806 _gestureRecognizer!.addPointer(event);
807 }
808 if (event is PointerHoverEvent) {
809 _handlePointerEvent?.call(event);
810 }
811 }
812
813 @override
814 void detach() {
815 _gestureRecognizer!.reset();
816 super.detach();
817 }
818
819 @override
820 void dispose() {
821 _gestureRecognizer?.dispose();
822 super.dispose();
823 }
824}
825