水平滚动与 WebView 结合时的滚动优先级

Scrolling priority when combining horizontal scrolling with WebView

我在水平滚动 PageView 中有一个垂直滚动 WebView。像这样:

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。现在水平滚动似乎总是赢,即使手势非常明显几乎完全垂直。如果页面完全垂直滚动,那只是在手势停止后(即,当我在手势后停止触摸屏幕时)——手势发生时没有垂直滚动。

gestureRecognizers 添加和删除 VerticalDragGestureRecognizer 现在没有效果 - 无论哪种方式,程序都像识别器不在列表中一样工作(尽管并不是 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 中)永远不会宣布胜利。事实上,它似乎完全被忽略了——在 gestureRecognizers 中使用 VerticalDragGestureRecognizer 和不使用它的手势竞技场输出是完全相同的。

这可能是 Flutter 的一个错误,所以我也创建了 an issue on Flutter's GitHub。但无论哪种方式——如何使用当前版本的 Flutter 实现这种效果?任何解决方法或规范解决方案将不胜感激。

我升级了我的 sdk 只是为了遇到你描述的这个问题。这个问题太烦人了,我想出了这个相当丑陋的黑客。

当事件发生在中间(通常是我们滚动的地方)时,这个 CustomGestureRecognizer 将忽略不需要的行为。 这确实带有一些我认为可以处理的 over-scrolling 阴影,可能需要更多时间。

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.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(//...

看来江湖规则变了。现在,竞技场宣布拥有主动接收者的手势获胜。这确实进一步提高了手势的响应能力。但是,由于本机视图不声明手势并且仅在没有其他活动 detector/receiver 声明它们时才使用它们,我怀疑垂直拖动甚至没有作为 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中使用新的class:

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