作者:Dreamy
問題描述
在PageView外部使用GestureDetector監(jiān)聽不到左滑手勢。
如下代碼,水平滑動的PageView只監(jiān)聽到了垂直方向的拖拽手勢,卻監(jiān)聽不到水平方向的拖拽手勢。
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),
],
),
);

原因分析
水平手勢已經(jīng)被橫向滑動的PageView監(jiān)聽,所以通過GestureDetector是無法再次監(jiān)聽的,只能使用Listener對原始指針進(jìn)行監(jiān)聽,進(jìn)而達(dá)到監(jiān)聽橫向滑動的效果。下文具體介紹Flutter的手勢監(jiān)聽機(jī)制。
Pointer
Pointer Event指針事件是指最原始的觸摸事件,一次事件包括觸摸到屏幕、在屏幕上移動到離開屏幕。 使用Listener組件來監(jiān)聽指針事件,基本的回調(diào)有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參數(shù)
// 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,
}
首先要了解兩個(gè)概念 一個(gè)是渲染層級,還有一個(gè)是Widget的樹結(jié)構(gòu)。當(dāng)手指點(diǎn)擊之后會先通過渲染層級和behavior參數(shù)確定HitTest命中的組件,然后根據(jù)樹結(jié)構(gòu)依次向上傳遞Pointer Event。透明Widget一般HitTest都會失敗,但是behavior參數(shù)可以依照如下說明進(jìn)行設(shè)定。
deferToChild:當(dāng)子節(jié)點(diǎn)widget的HitTest命中測試成功時(shí),該節(jié)點(diǎn)一定會響應(yīng)。
opaque:將當(dāng)前節(jié)點(diǎn)Widget當(dāng)作是不透明的進(jìn)行處理(就算是透明的組件)。
translucent:透傳,正常情況下HitTest只會響應(yīng)渲染層級上面的組件。
IgnorePointer組件 被IgnorePointer組件包裹的子樹及其本身都不會處理PointerEvent事件。
AbsorbPointer組件 被AbsorbPointer組件包裹的子樹不會處理PointerEvent事件,但是AbsorbPointer組件本身會處理。
GestureDetector組件
GestureDetector組件對基本的PointerEvent事件進(jìn)行了語義封裝,通過GestureRecognizer使用Listener將PointerEvent轉(zhuǎn)義成onTap、onDoubleTap、onLongPress、onPanDown、onVeticalDragUpdate等等接口。
Arena: Flutter定義了一個(gè)Arena手勢競技場,對有沖突的手勢進(jìn)行判斷最后選出唯一的一個(gè)獲勝者并處理事件。例如水平和垂直的ListView會根據(jù)橫豎的滑動分量進(jìn)行判斷。
Tip: 手勢沖突只是手勢級別的,而手勢是對原始指針的語義化的識別,所以在遇到復(fù)雜的沖突場景時(shí),都可以通過Listener直接識別原始指針事件來解決沖突。
Notification
NotificationListener組件可以用來監(jiān)聽從子結(jié)構(gòu)傳遞過來的通知,和手勢通知不同,這種Notification可以選擇是否還要往上傳遞。 常用的是ScrollStartNotification、ScrollUpdateNotification等滑動通知,也可以自定義通知dispatch向上分發(fā)。