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

Posted

技术标签:

【中文标题】水平滚动与 WebView 结合时的滚动优先级【英文标题】:Scrolling priority when combining horizontal scrolling with WebView 【发布时间】:2019-11-25 22:12:39 【问题描述】:

我在水平滚动的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 实现这种效果?任何解决方法或规范的解决方案都将受到高度赞赏。

【问题讨论】:

可以做一个指定的垂直拖动区域。就像一个滚动条。当然不那么优雅。但是,如果您在截止日期方面遇到麻烦,它可能至少暂时有用。 @Doh09 感谢您的建议。我的“目前不太优雅的解决方案”坚持使用 Flutter 1.5.4。我只是不想永远被旧版本卡住。 【参考方案1】:

我升级了我的 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(//...

【讨论】:

这种解决方法对用户来说非常不直观,所以我认为我不能在生产中使用它,但这是第一个迹象表明我不是唯一一个努力解决这个问题并正在寻找的人一个解决方案,谢谢! 没问题的朋友!【参考方案2】:

竞技场的规则好像变了。现在,竞技场宣布具有活动接收器的手势获胜。这确实进一步增加了手势的响应能力。但是,由于本机视图不声明手势并且仅在没有其他活动检测器/接收器声明它们时才使用它们,我怀疑垂直拖动甚至没有作为 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(),
    );
  ,
);

【讨论】:

谢谢!我刚刚测试了它。起初它不起作用,但在我删除 else stopTrackingPointer(event.pointer); 部分后它开始起作用 - 似乎在每个手势的一开始,delta.dxdelta.dy 都等于 0,如果你停止跟踪指针这一点你失去了注意到他们进一步变化的机会。这可能与我在模拟器上的测试有关(在现实世界中用手指做出的手势可能太“杂乱”而不能以 0 开头)。 您愿意更新以从您的答案中删除 stopTrackingPointer(event.pointer); 吗?或者您是否看到这样做的潜在问题并想提出替代解决方案? @LudwikTrammer 你用哪个模拟器测试过? 这个版本完美运行。我继续清理了你的答案(所以它包含一个单一的、规范的、随时可用的答案 - 我希望你不介意),接受它并奖励你。谢谢大家的帮助! 谢谢,这个问题很有趣,今天学到了很多。

以上是关于水平滚动与 WebView 结合时的滚动优先级的主要内容,如果未能解决你的问题,请参考以下文章

如何更改 UIScrollView 中的滚动方向?

在颤动的webview中禁用水平滚动

android webview没有水平滚动

React Native Android WebView 如何禁用水平滚动?

Chrome App webview 和触摸滚动传播

iPad上的WKWebView水平滚动