web-dev-qa-db-ja.com

水平スクロールとWebViewを組み合わせる場合のスクロール優先度

水平方向にスクロールするWebViewの中に、垂直方向にスクロールする PageView があります。このようなもの:

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

Flutterの以前の安定バージョン(1.5.4)では、これは期待どおりに機能しました。垂直にスクロールするとWebView内のコンテンツが移動し、水平にスクロールするとPageViewが移動します。

これはFlutter v1.7.8+hotfix.3にアップグレードした後で壊れました。これで、ジェスチャがほぼ完全に垂直である場合でも、水平スクロールが常に勝つようです。ページが垂直方向にスクロールされる場合、それはジェスチャーが停止した後(つまり、ジェスチャーの後に画面へのタッチを停止したとき)のみです-ジェスチャーの発生中に垂直方向のスクロールはありません。

VerticalDragGestureRecognizergestureRecognizersに追加したり削除したりしても、効果はありません。プログラムは、認識機能がリストにないかのように機能します(ただし、gestureRecognizersを追加すると効果があるため、EagerGestureRecognizerが完全に無視されるわけではありません)。

これはジェスチャーアリーナのデバッグ出力です(私はジェスチャーをできるだけ垂直に保つように心がけていましたが、側面を少し指で動かしただけでも、HorizontalDragGestureRecognizerが勝つには十分でした。ずっと):

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)

そして、これは、ドラッグジェスチャの処理中に、ジェスチャを完全に垂直に保つことができた場合(エミュレータをマウスで使用する方が簡単に思われる)に発生します。

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.

わずかな垂直方向の動きでも、HorizontalDragGestureRecognizerが勝利を発表しますが、VerticalDragGestureRecognizer_CombiningGestureArenaMember内にラップされているようです)が勝利を宣言することはありません。実際には完全に無視されているようです-VerticalDragGestureRecognizergestureRecognizersがあり、それがない場合のジェスチャーアリーナ出力はまったく同じです。

これはFlutterのバグの可能性があるため、 FlutterのGitHubの問題 も作成しました。しかしどちらにしても、現在のバージョンのFlutterでこの効果を実現するにはどうすればよいですか?回避策や正規の解決策があれば、高く評価されます。

9
Ludwik Trammer

アリーナのルールが変わったようです。現在、アリーナは、アクティブなレシーバーを持つジェスチャーの勝利を宣言しています。これにより、ジェスチャーの応答性がさらに向上します。ただし、ネイティブビューはジェスチャーを要求せず、他のアクティブな検出器/受信者がジェスチャーを要求しない場合にのみジェスチャーを消費するため、垂直方向のドラッグはWebViewからのジェスチャーとしてアリーナに入らないように思われます。そのため、わずかな水平方向のドラッグで水平方向のドラッグジェスチャーが発生します。これは、他のウィジェットがジェスチャーを要求しないためです。

VerticalDragGestureRecognizerを拡張できるため、ジェスチャーを受け入れます。

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) {}
}

その後、gestureRecognizersで新しいクラスを使用できます。

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

私がSDKをアップグレードしたのは、あなたが説明したこの問題が発生するためだけです。この問題は非常に煩わしく、私はこのやや醜いハックを思いつきました。

このCustomGestureRecognizerは、イベントが中央(通常はスクロールする場所)で発生しているときに、不要な動作を無視します。これには、私が処理できると私が信じるいくつかのオーバースクロールシャドウが付属していますが、それだけの時間がかかる可能性があります。

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);
  }

PageViewウィジェット

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

画面の幅

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