Flutter中PageView与嵌入WebView的手势冲突问题:滑动PageView后WebView无法接收手势事件
你遇到的问题是Flutter手势识别器的优先级竞争导致WebView手势被PageView永久拦截。当你第一次滑动PageView时,PageView的PageViewGestureRecognizer在Flutter的手势竞技场中获胜,Flutter会记住这个获胜者,后续所有的手势事件都会优先交给PageView处理,导致WebView的手势识别器再也无法捕获到交互事件。
你当前在WebView中使用的EagerGestureRecognizer虽然会尝试优先处理手势,但它无法在PageView已经获胜过的手势竞技场中重新获得优先级。
我们可以通过以下几个步骤来修复这个问题:
1. 简化手势嵌套,避免上层Widget拦截WebView手势
你在ArtifactView中用Listener和GestureDetector包裹了ThreeJSViewer,这两个Widget可能会拦截原本应该传递给WebView的手势事件。我们可以简化这个嵌套,改用HitTestBehavior.translucent的GestureDetector来处理全屏逻辑,同时让手势透传给WebView:
修改ArtifactView中的相关代码:
// 替换原来的Listener和GestureDetector嵌套 GestureDetector( behavior: HitTestBehavior.translucent, onTap: () => startFullScreen(), child: ThreeJSViewer( modelFilePath: state.modelPath, initScale: artifact.cameraZoom.toInt(), autoRotateSpeed: 2, state: state, ), )
2. 调整WebView的手势识别器,重新获得手势优先级
接下来,我们需要修改ThreeJSViewer中WebView的手势识别器配置,替换原来的EagerGestureRecognizer,并添加逻辑确保WebView在用户点击时能重新参与手势竞争:
修改ThreeJSViewer中WebViewPlus的gestureRecognizers属性:
WebViewPlus( // ... 其他原有配置 gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{ // 添加水平拖拽识别器,确保WebView能处理旋转/平移手势 Factory(() => HorizontalDragGestureRecognizer() ..onStart = (details) { // 当WebView接收到水平拖拽开始时,强制让它在手势竞技场中优先处理 GestureBinding.instance.gestureArenaManager.hold(details.pointer); // 延迟释放,确保WebView的识别器能捕获事件 Future.delayed(const Duration(milliseconds: 50), () { GestureBinding.instance.gestureArenaManager.release(details.pointer); }); }), // 添加垂直拖拽识别器 Factory(() => VerticalDragGestureRecognizer() ..onStart = (details) { GestureBinding.instance.gestureArenaManager.hold(details.pointer); Future.delayed(const Duration(milliseconds: 50), () { GestureBinding.instance.gestureArenaManager.release(details.pointer); }); }), // 添加缩放识别器,支持ThreeJS模型的缩放操作 Factory(() => ScaleGestureRecognizer()), }.toSet(), // ... 其他原有配置 )
3. (可选)监听PageView滑动状态,动态调整手势优先级
如果上述方法还不够稳定,我们可以在FocusPointList中监听PageView的滑动状态,当PageView停止滑动后,主动让WebView的手势识别器重新获得优先级:
首先,给FocusPointList添加一个回调参数,通知父组件PageView的滑动状态:
class FocusPointList extends StatefulWidget { final ArtifactsState state; final void Function(bool isScrolling)? onScrollStateChanged; // 添加回调 const FocusPointList({ Key? key, required this.state, this.onScrollStateChanged, }) : super(key: key); // ... 其他代码 }
然后在PageView.builder中监听滚动状态:
PageView.builder( // ... 其他原有配置 controller: PageController(viewportFraction: 0.75) ..addListener(() { final isScrolling = widget.state.pageController?.position.isScrollingNotifier.value ?? false; widget.onScrollStateChanged?.call(isScrolling); }), // ... 其他原有配置 )
接着在ArtifactView中接收这个回调,当PageView停止滑动时,重置WebView的手势状态:
FocusPointList( state: state, onScrollStateChanged: (isScrolling) { if (!isScrolling) { // 当PageView停止滑动后,清空手势竞技场,让WebView能重新参与竞争 GestureBinding.instance.gestureArenaManager.clear(); } }, )
- Flutter的手势竞技场会跟踪每个手势的获胜者,当PageView滑动一次后,它会成为水平/垂直拖拽手势的默认获胜者,后续手势会直接交给它处理。
- 通过
GestureBinding.instance.gestureArenaManager.hold()和release(),我们可以在用户点击WebView时,暂时阻止其他识别器获胜,让WebView的识别器有机会捕获事件。 - 简化上层手势嵌套可以避免额外的手势拦截,确保WebView能接收到原始的触摸事件。
内容的提问来源于stack exchange,提问作者MuscleNerd

