Skip to main content

Flutter GestureDetector无法获取点击事件的问题

· 3 min read

哪些情况

在有TextField的表单界面中,点击空白部分隐藏键盘是基本功能。但是如果GestureDetector直接包裹着TextField是无法响应onTap事件的,比如下面这种情况:

/// 1
GestureDetector(
onTap: () => FocusManager.instance.primaryFocus?.unfocus(),
child: TextField(),
)

即便再给TextField套一层Container,也是没有响应:

/// 2
GestureDetector(
onTap: () => FocusManager.instance.primaryFocus?.unfocus(),
child: Container(
height: 300,
color: Colors.grey[200],
child: TextField(),
),
),

再或者嵌套一个Column

/// 3
GestureDetector(
onTap: () => FocusManager.instance.primaryFocus?.unfocus(),
child: Column(
children: [
TextField(),
Spacer(),
],
),
)

解决办法

  • 方案一Column套一层Container,而且必须设置颜色才会生效。
Container(
color: Colors.red,// 必须设置颜色
child: Column(
children: [
TextField(),
Spacer(),
],
),
),
  • 方案二 使用ListView包含TextField
ListView(
children: [
TextField(),
],
),

什么原因

这个问题有人在 flutter issues提过:GestureDetector onTap doesn't work with Column widget,相关问题和解答可以看看。

下面来聊聊具体的东西。

GestureDetector有个behavior字段,这是个枚举类型:

/// How to behave during hit tests.
enum HitTestBehavior {
/// Targets that defer to their children receive events within their bounds
/// only if one of their children is hit by the hit test.
deferToChild,

/// Opaque targets can be hit by hit tests, causing them to both receive
/// events within their bounds and prevent targets visually behind them from
/// also receiving events.
opaque,

/// Translucent targets both receive events within their bounds and permit
/// targets visually behind them to also receive events.
translucent,
}

这三种点击事件的作用就是:

  • deferToChild:child处理事件,默认
  • opaque:自己处理事件
  • translucent:自己和child都可以接收事件

Container有个隐藏彩蛋,即当给 Container设置color的时候,点击有响应。而color为空,则点击无响应。具体原因查看: Flutter : 关于 HitTestBehavior

基于上面两个前提条件,我们可以得出以下情况:

// 不响应onTap
GestureDetector(
behavior: HitTestBehavior.deferToChild, // 1
onTap: () => print('hit'),
child: Container(
height: 300,
//color: Colors.red, // 2
),
)
// 响应onTap
GestureDetector(
behavior: HitTestBehavior.opaque, // 1
onTap: () => print('hit'),
child: Container(
height: 300,
//color: Colors.red, // 2
),
)
// 响应onTap
GestureDetector(
behavior: HitTestBehavior.deferToChild, // 1
onTap: () => print('hit'),
child: Container(
height: 300,
color: Colors.red, // 2
),
)