Flutter Web如何实现仅点击MyWidget周边空白触发指定函数
Flutter Web 空白区域点击事件实现方案
问题场景
开发Flutter Web项目时,使用Row组件横向排布3个核心子组件,从左到右顺序为:
- Sidebar
- Sidebar content
- body
初始_buildBody实现代码如下:
Widget _buildBody() { final screenwidth = MediaQuery.of(context).size.width; editpanel = screenwidth * 0.3; final editor = ViewProvider.of(context).isEditPanelOpen ? (screenwidth - sidebar - editpanel) : (screenwidth - sidebar); final ViewProvider viewProvider = Provider.of<ViewProvider>(context); return Row( children: [ Sidebar(), _loadSidebarContent(bloc.editPanelIndex), _sidebarHandler(viewProvider), Center( child: SizedBox( width: editor * 0.8, child: Center( child: MyWidget(), ), ), ), ], ); }
需求逻辑:
- 仅点击Appbar、Sidebar、Sidebarcontent、MyWidget之外的空白区域时,触发指定逻辑函数
- 点击上述4个组件本身时,不触发该函数
最初尝试用GestureDetector包裹整个Scaffold,同时给指定组件加IgnorePointer实现,对应build代码:
Widget build(BuildContext context) { return GestureDetector( onTap: () { // 调用指定逻辑函数 specificFunc(); }, child: Scaffold( backgroundColor: Colors.white, appBar: PreferredSize( preferredSize: Size( MediaQuery.of(context).size.width, height + 80, ), child: IgnorePointer( ignoring: true, child: Appbar(), ), ), body: _buildBody(), ), ); }
该方案存在的问题:MyWidget的点击事件被永久忽略,不符合预期。
正确实现方案
不要用IgnorePointer拦截子组件点击。Flutter手势事件命中测试是自底向上传递的,只需要给不需要触发全局点击的组件单独配置手势消费,外层的全局GestureDetector只会在点击没有被内层组件消费的空白区域时触发。
实现步骤
- 移除所有给业务组件加的
IgnorePointer包裹 - 保留外层Scaffold上包裹的
GestureDetector作为空白区域点击监听 - 给4个不需要触发全局点击的组件分别包裹透明的
GestureDetector,消费点击事件阻止事件向外冒泡;如果组件本身已经自带点击/手势回调,不需要额外包裹,组件自身消费事件后就不会触发外层回调
修正后代码
调整build方法:
Widget build(BuildContext context) { return GestureDetector( onTap: () { // 仅空白区域会触发这里 specificFunc(); }, child: Scaffold( backgroundColor: Colors.white, // AppBar包裹GestureDetector消费点击 appBar: PreferredSize( preferredSize: Size( MediaQuery.of(context).size.width, height + 80, ), child: GestureDetector( onTap: () {}, child: Appbar(), ), ), body: _buildBody(), ), ); }
调整_buildBody内的组件配置:
Widget _buildBody() { final screenwidth = MediaQuery.of(context).size.width; editpanel = screenwidth * 0.3; final editor = ViewProvider.of(context).isEditPanelOpen ? (screenwidth - sidebar - editpanel) : (screenwidth - sidebar); final ViewProvider viewProvider = Provider.of<ViewProvider>(context); return Row( children: [ // Sidebar包裹GestureDetector消费点击 GestureDetector( onTap: () {}, child: Sidebar(), ), // SidebarContent包裹GestureDetector消费点击 GestureDetector( onTap: () {}, child: _loadSidebarContent(bloc.editPanelIndex), ), _sidebarHandler(viewProvider), Center( child: SizedBox( width: editor * 0.8, child: Center( // 仅给MyWidget本身包裹GestureDetector消费点击,外层SizedBox留白区域保留全局点击触发 child: GestureDetector( onTap: () {}, child: MyWidget(), ), ), ), ), ], ); }
原理说明
Flutter命中测试规则:
- 点击位置的组件从最内层开始匹配,只要组件声明了手势回调,就会消费事件,事件不会继续向外层传递
- 空白区域没有子组件消费事件,最终会匹配到最外层包裹Scaffold的
GestureDetector,触发specificFunc() - 给业务组件加空实现的
onTap不会影响组件本身原有点击逻辑,只会阻止事件向外冒泡触发全局的空白点击回调
内容的提问来源于stack exchange,提问作者Arnob
相关产品推荐
相关产品推荐

