作者:Dreamy
问题描述
在PageView外部使用GestureDetector监听不到左滑手势。
如下代码,水平滑动的PageView只监听到了垂直方向的拖拽手势,却监听不到水平方向的拖拽手势。
GestureDetector(
onVerticalDragStart: (details) => print('onVerticalDragStart'),
onVerticalDragEnd: (details) => print('onVerticalDragEnd'),
onHorizontalDragStart: (details) => print('onHorizontalDragStart'),
onHorizontalDragEnd: (details) => print('onHorizontalDragEnd'),
child: PageView(
children: [
Container(color: Colors.amber),
Container(color: Colors.blue),
Container(color: Colors.deepOrangeAccent),
],
),
);
原因分析
水平手势已经被横向滑动的PageView监听,所以通过GestureDetector是无法再次监听的,只能使用Listener对原始指针进行监听,进而达到监听横向滑动的效果。下文具体介绍Flutter的手势监听机制。
Pointer
Pointer Event指针事件是指最原始的触摸事件,一次事件包括触摸到屏幕、在屏幕上移动到离开屏幕。 使用Listener组件来监听指针事件,基本的回调有onPointerDown、onPointerMove、onPointerUp。
Listener(
child: Container(
alignment: Alignment.center,
color: Colors.blue,
width: 100,
height: 100,
),
onPointerDown: (event) => print('onPointerDown'),
onPointerMove: (event) => print('onPointerMove'),
onPointerUp: (event) => print('onPointerUp'),
),
Listener的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,
}
首先要了解两个概念 一个是渲染层级,还有一个是Widget的树结构。当手指点击之后会先通过渲染层级和behavior参数确定HitTest命中的组件,然后根据树结构依次向上传递Pointer Event。透明Widget一般HitTest都会失败,但是behavior参数可以依照如下说明进行设定。
deferToChild:当子节点widget的HitTest命中测试成功时,该节点一定会响应。
opaque:将当前节点Widget当作是不透明的进行处理(就算是透明的组件)。
translucent:透传,正常情况下HitTest只会响应渲染层级上面的组件。
IgnorePointer组件 被IgnorePointer组件包裹的子树及其本身都不会处理PointerEvent事件。
AbsorbPointer组件 被AbsorbPointer组件包裹的子树不会处理PointerEvent事件,但是AbsorbPointer组件本身会处理。
GestureDetector组件
GestureDetector组件对基本的PointerEvent事件进行了语义封装,通过GestureRecognizer使用Listener将PointerEvent转义成onTap、onDoubleTap、onLongPress、onPanDown、onVeticalDragUpdate等等接口。
Arena: Flutter定义了一个Arena手势竞技场,对有冲突的手势进行判断最后选出唯一的一个获胜者并处理事件。例如水平和垂直的ListView会根据横竖的滑动分量进行判断。
Tip: 手势冲突只是手势级别的,而手势是对原始指针的语义化的识别,所以在遇到复杂的冲突场景时,都可以通过Listener直接识别原始指针事件来解决冲突。
Notification
NotificationListener组件可以用来监听从子结构传递过来的通知,和手势通知不同,这种Notification可以选择是否还要往上传递。 常用的是ScrollStartNotification、ScrollUpdateNotification等滑动通知,也可以自定义通知dispatch向上分发。