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 =
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
538typedef _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`.
546class _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.
655class 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.
735mixin _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

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com