如何在模态底部弹窗中实现可滚动且支持滑动关闭的WebViewWidget?
问题
我把WebViewWidget放在模态底部弹窗(modal bottom sheet)里,想要实现两个效果:
- WebView可以滚动;
- 底部弹窗能通过滑动手势关闭。
具体来说,当WebView处于顶部位置时,向下滚动的手势要传递给底部弹窗,类似原生Android的嵌套滚动机制。
默认情况下WebView没法滚动,我知道WebViewWidget支持通过gestureRecognizers指定手势处理器,用EagerGestureRecognizer能实现WebView滚动,但会导致底部弹窗关不了。有没有常规的解决方案?
两种配置的表现:
- 手势识别器设为
gestureRecognizers: const {}时:
- 使用
gestureRecognizers: { Factory(() => EagerGestureRecognizer()) }时:
最小可复现代码:
import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:webview_flutter/webview_flutter.dart'; Future<void> main() async { runApp(const MaterialApp(home: DemoApp())); } class DemoApp extends StatelessWidget { const DemoApp({super.key}); @override Widget build(BuildContext context) { return Scaffold( body: Center( child: FilledButton( child: const Text('Show WebView'), onPressed: () => showWebView(context), ), ), ); } void showWebView(BuildContext context) { final controller = WebViewController .fromPlatformCreationParams(const PlatformWebViewControllerCreationParams()) ..loadRequest(Uri.parse('https://stackoverflow.com/questions/62276050/why-cant-i-scroll-custom-webview-in-flutter-modalbottomsheet')); showModalBottomSheet( context: context, builder: (BuildContext context) => Scaffold( body: WebViewWidget( controller: controller, gestureRecognizers: { Factory(() => EagerGestureRecognizer()) }, ), ), ); } }
解决方案
要实现WebView滚动和底部弹窗滑动关闭的嵌套效果,核心是让手势能根据WebView的滚动状态动态决定由谁处理,可通过以下步骤实现:
- 自定义手势识别器:创建继承自
VerticalDragGestureRecognizer的类,在WebView滚动到顶部时放弃手势优先级,让底部弹窗的滑动手势生效;否则拦截手势给WebView处理。 - 监听WebView滚动位置:通过WebViewController的滚动监听,实时获取当前WebView的滚动偏移,判断是否处于顶部。
- 配置WebViewWidget手势识别器:将自定义的手势识别器传入
gestureRecognizers参数。
修改后的完整代码:
import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:webview_flutter/webview_flutter.dart'; Future<void> main() async { runApp(const MaterialApp(home: DemoApp())); } class DemoApp extends StatelessWidget { const DemoApp({super.key}); @override Widget build(BuildContext context) { return Scaffold( body: Center( child: FilledButton( child: const Text('显示WebView'), onPressed: () => showWebView(context), ), ), ); } void showWebView(BuildContext context) { final controller = WebViewController .fromPlatformCreationParams(const PlatformWebViewControllerCreationParams()) ..loadRequest(Uri.parse('https://stackoverflow.com/questions/62276050/why-cant-i-scroll-custom-webview-in-flutter-modalbottomsheet')); // 存储WebView滚动位置 ValueNotifier<double> scrollOffset = ValueNotifier(0); // 监听WebView滚动事件 controller.addScrollListener((x, y) { scrollOffset.value = y; }); showModalBottomSheet( context: context, builder: (BuildContext context) => Scaffold( body: WebViewWidget( controller: controller, gestureRecognizers: { Factory(() => _NestedScrollGestureRecognizer(scrollOffset: scrollOffset)) }, ), ), ); } } // 自定义嵌套滚动手势识别器 class _NestedScrollGestureRecognizer extends VerticalDragGestureRecognizer { final ValueNotifier<double> scrollOffset; _NestedScrollGestureRecognizer({required this.scrollOffset}); @override void acceptGesture(int pointer) { // WebView不在顶部时,由WebView处理滚动手势 if (scrollOffset.value > 1) { super.acceptGesture(pointer); } else { // WebView在顶部时,放弃手势,让底部弹窗处理滑动关闭 rejectGesture(pointer); } } }
说明
- 自定义的
_NestedScrollGestureRecognizer会根据WebView的滚动偏移判断手势归属:滚动偏移大于1时(避免精度误差),WebView处理滚动;处于顶部时,让底部弹窗的滑动关闭手势生效。 - 通过
controller.addScrollListener实时同步WebView的滚动位置,保证手势判断的准确性。 - 该方案模拟原生嵌套滚动逻辑,同时满足WebView滚动和底部弹窗关闭的需求。
内容的提问来源于stack exchange,提问作者SqueezyMo
相关产品推荐
相关产品推荐

