web-dev-qa-db-fra.com

Priorité de défilement lors de la combinaison du défilement horizontal avec WebView

J'ai un défilement vertical WebView à l'intérieur d'un PageView défilement horizontal. Quelque chose comme ça:

PageView.builder(
  itemCount: 5,
  itemBuilder: (context, index) {
    return WebView(
      initialUrl: 'https://flutter.dev/docs',
      gestureRecognizers: [
        Factory(() => VerticalDragGestureRecognizer()),
      ].toSet(),
    );
  },
);

Avec la version stable précédente de Flutter (1.5.4), cela fonctionnait comme prévu - le défilement vertical déplacerait le contenu à l'intérieur de la WebView et le défilement horizontal déplacerait la PageView.

Cela s'est cassé après la mise à niveau vers Flutter v1.7.8+hotfix.3. Maintenant, le défilement horizontal semble toujours gagner, même si le geste est très clairement presque entièrement vertical. Si la page défile verticalement, ce n'est qu'après l'arrêt du geste (c'est-à-dire lorsque j'arrête de toucher l'écran après un geste) - il n'y a pas de défilement vertical pendant que le geste se produit.

L'ajout et la suppression de VerticalDragGestureRecognizer de gestureRecognizers n'ont aucun effet maintenant - dans les deux cas, le programme fonctionne comme si le module de reconnaissance n'était pas dans la liste (bien que ce ne soit pas que gestureRecognizers soit complètement ignoré car l'ajout de EagerGestureRecognizer A un effet).

Voici la sortie de débogage de l'arène des gestes (gardez à l'esprit que j'essayais de garder mon geste aussi vertical que possible, mais même un léger mouvement du doigt sur les côtés était suffisant pour que le HorizontalDragGestureRecognizer gagne, même si Je me déplaçais aussi verticalement tout le temps):

I/flutter (30125): Gesture arena 14   ❙ ★ Opening new gesture arena.
I/flutter (30125): Gesture arena 14   ❙ Adding: Instance of '_CombiningGestureArenaMember'
I/flutter (30125): Gesture arena 14   ❙ Adding: LongPressGestureRecognizer#9cad1(debugOwner: GestureDetector, state: ready)
I/flutter (30125): Gesture arena 14   ❙ Adding: HorizontalDragGestureRecognizer#69b8f(start behavior: start)
I/flutter (30125): Gesture arena 14   ❙ Closing with 3 members.
I/flutter (30125): Gesture arena 14   ❙ Rejecting: LongPressGestureRecognizer#9cad1(debugOwner: GestureDetector, state: possible)
I/flutter (30125): Gesture arena 14   ❙ Accepting: HorizontalDragGestureRecognizer#69b8f(start behavior: start)
I/flutter (30125): Gesture arena 14   ❙ Self-declared winner: HorizontalDragGestureRecognizer#69b8f(start behavior: start)

Et c'est ce qui se passe lorsque vous parvenez à garder votre geste entièrement vertical (semble être plus facile sur un émulateur avec une souris), alors que le geste de glisser est en cours:

flutter: Gesture arena 30   ❙ ★ Opening new gesture arena.
flutter: Gesture arena 30   ❙ Adding: Instance of '_CombiningGestureArenaMember'
flutter: Gesture arena 30   ❙ Adding: HorizontalDragGestureRecognizer#11e7f(start behavior: down)
flutter: Gesture arena 30   ❙ Closing with 2 members.

Même un léger mouvement vertical fera que le HorizontalDragGestureRecognizer annoncera une victoire, mais le VerticalDragGestureRecognizer (qui semble être enveloppé à l'intérieur du _CombiningGestureArenaMember) ne revendique jamais une victoire. Il semble en fait être complètement ignoré - la sortie de l'arène gestuelle avec VerticalDragGestureRecognizer dans gestureRecognizers et sans elle est absolument identique.

Il peut s'agir d'un bug dans Flutter, j'ai donc également créé n problème sur le GitHub de Flutter . Mais de toute façon - comment puis-je obtenir cet effet avec la version actuelle de Flutter? Toute solution de contournement ou solution canonique serait très appréciée.

9
Ludwik Trammer

Il semble que les règles de l'arène aient changé. Maintenant, l'arène déclare des victoires pour les gestes qui ont des récepteurs actifs. Cela augmente en effet encore plus la réactivité des gestes. Cependant, comme les vues natives ne revendiquent pas les gestes et ne les consomment que lorsqu'aucun autre détecteur/récepteur actif ne les réclame, je soupçonne que la traînée verticale n'entre même pas dans l'arène comme un geste de la WebView. C'est pourquoi toute légère traînée horizontale fait gagner le geste de traînée horizontale - car tout simplement aucun autre widget ne réclame aucun geste.

Vous pouvez étendre VerticalDragGestureRecognizer, il accepte donc les gestes:

class PlatformViewVerticalGestureRecognizer
    extends VerticalDragGestureRecognizer {
  PlatformViewVerticalGestureRecognizer({PointerDeviceKind kind})
      : super(kind: kind);

  Offset _dragDistance = Offset.zero;

  @override
  void addPointer(PointerEvent event) {
    startTrackingPointer(event.pointer);
  }

  @override
  void handleEvent(PointerEvent event) {
    _dragDistance = _dragDistance + event.delta;
    if (event is PointerMoveEvent) {
      final double dy = _dragDistance.dy.abs();
      final double dx = _dragDistance.dx.abs();

      if (dy > dx && dy > kTouchSlop) {
        // vertical drag - accept
        resolve(GestureDisposition.accepted);
        _dragDistance = Offset.zero;
      } else if (dx > kTouchSlop && dx > dy) {
        // horizontal drag - stop tracking
        stopTrackingPointer(event.pointer);
        _dragDistance = Offset.zero;
      }
    }
  }

  @override
  String get debugDescription => 'horizontal drag (platform view)';

  @override
  void didStopTrackingLastPointer(int pointer) {}
}

Après cela, vous pouvez utiliser la nouvelle classe dans gestureRecognizers:

PageView.builder(
  itemCount: 5,
  itemBuilder: (context, index) {
    return WebView(
      initialUrl: 'https://flutter.dev/docs',
      gestureRecognizers: [
        Factory(() => PlatformViewVerticalGestureRecognizer()),
      ].toSet(),
    );
  },
);
8
Saed Nabil

J'ai mis à jour mon sdk uniquement pour avoir ce problème que vous avez décrit. Le problème est trop ennuyeux et j'ai trouvé ce hack plutôt moche.

Ce CustomGestureRecognizer ignorera le comportement indésirable lorsque l'événement se produit au milieu (généralement là où nous faisons défiler). Cela vient avec des ombres de défilement excessif qui, je crois, peuvent être gérées, cela pourrait prendre plus de temps.

class CustomGestureRecognizer extends OneSequenceGestureRecognizer {
  double maxScreenOffsetX;
  final double edgeMargin = 20.0;

  CustomGestureRecognizer({@required this.maxScreenOffsetX});

  @override
  void addAllowedPointer(PointerDownEvent event) {

    print("CustomGestureRecognizer: Screen Width: "+ maxScreenOffsetX.toString());
    print("CustomGestureRecognizer: dx: "+event.position.dx.toString());

    if (event.position.dx < edgeMargin || event.position.dx > (maxScreenOffsetX - edgeMargin)) {
      print("CustomGestureRecognizer: At the Edge.");
      return;
    }
    print("CustomGestureRecognizer: Inside Safe Zone");
    startTrackingPointer(event.pointer, event.transform);
    resolve(GestureDisposition.accepted);
    stopTrackingPointer(event.pointer);
  }

Widget PageView

PageView.builder(
        itemCount: 5,
        physics: CustomScrollPhysics(),
        itemBuilder: (context, index) {
          return WebView(
            initialUrl: 'https://flutter.dev/docs',
            gestureRecognizers: [
              Factory(() => CustomGestureRecognizer(maxScreenOffsetX: screenWidth)),
            ].toSet(),
          );
        });

Largeur de l'écran

  @override
  Widget build(BuildContext context) {
    screenWidth = MediaQuery.of(context).size.width;
    return Scaffold(//...
1
shb