〈原始指標事件〉中看過,透過 Listerner
只能處理基本的指標事件,如果想處理拖曳、雙連觸(Double tap)、縮放等事件,Flutter 中提供了 GestureDetector
,它基於原始指標事件,提供了各種手勢識別,最簡單的用法之一,在〈按鈕!按鈕?〉曾經看過:
...
class TextButton extends StatelessWidget {
final String text;
final VoidCallback onPressed;
TextButton(this.text, {this.onPressed});
@override
Widget build(BuildContext context) {
return GestureDetector(
child: Text(text),
onTap: onPressed == null ? () => {} : onPressed,
);
}
}
GestureDetector
可以傾聽的事件很多,逐一介紹實在沒什麼意思,如果想有個出發點,〈Flutter. GestureDetector in-depth〉中一些操作示範與簡單程式碼可以參考。
手勢偵測的實現也是蠻複雜的,若想要能從原始碼中瞭解一下實現原理,需要有一些出發點,這邊就來稍微看一下 GestureDetector
的實現好了,就從它的 build
方法來看看:
...
Widget build(BuildContext context) {
final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
if (
onTapDown != null ||
onTapUp != null ||
onTap != null ||
onTapCancel != null ||
onSecondaryTapDown != null ||
onSecondaryTapUp != null ||
onSecondaryTapCancel != null
) {
gestures[TapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
() => TapGestureRecognizer(debugOwner: this),
(TapGestureRecognizer instance) {
instance
..onTapDown = onTapDown
..onTapUp = onTapUp
..onTap = onTap
..onTapCancel = onTapCancel
..onSecondaryTapDown = onSecondaryTapDown
..onSecondaryTapUp = onSecondaryTapUp
..onSecondaryTapCancel = onSecondaryTapCancel;
},
);
}
if (onDoubleTap != null) {
gestures[DoubleTapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<DoubleTapGestureRecognizer>(
() => DoubleTapGestureRecognizer(debugOwner: this),
(DoubleTapGestureRecognizer instance) {
instance.onDoubleTap = onDoubleTap;
},
);
}
略...
return RawGestureDetector(
gestures: gestures,
behavior: behavior,
excludeFromSemantics: excludeFromSemantics,
child: child,
);
}
...
方才談到,GestureDetector
基於原始指標事件,提供了各種手勢識別,不同的手勢識別,是定義在不同的 GestureRecognizer
,在 build
中就可以看到,依指定要傾聽的事件而定,會加入 TapGestureRecognizer
、DoubleTapGestureRecognizer
等的工廠類別實例。
GestureRecognizer
最重要是幾個接受 PointerDownEvent
的方法:
abstract class GestureRecognizer extends GestureArenaMember with DiagnosticableTreeMixin {
...略
void addPointer(PointerDownEvent event) {
_pointerToKind[event.pointer] = event.kind;
if (isPointerAllowed(event)) {
addAllowedPointer(event);
} else {
handleNonAllowedPointer(event);
}
}
@protected
void addAllowedPointer(PointerDownEvent event) { }
@protected
void handleNonAllowedPointer(PointerDownEvent event) { }
@protected
bool isPointerAllowed(PointerDownEvent event) {
return _kindFilter == null || _kindFilter == event.kind;
}
...略
}
GestureRecognizer
的子類要實現的,就是識別這些原始指標事件各自代表著什麼手勢,有些手勢可能會有競爭問題,例如在拖曳時,你可能傾聽了垂直與水平拖曳事件,那麼最後會是觸發哪個事件呢?GestureRecognizer
實例必須競爭,就像是在競技場(Arena)中,每個參與競爭的實例就是個 GestureArenaMember
,最後只能有一個勝出。
GestureRecognizer
最後用 gestures
作為 RawGestureDetector
建構式的引數,而 RawGestureDetector
是個 StatefulWidget
,對應 State
的 build
方法實現是:
...略
void _handlePointerDown(PointerDownEvent event) {
assert(_recognizers != null);
for (final GestureRecognizer recognizer in _recognizers.values)
recognizer.addPointer(event);
}
...略
@override
Widget build(BuildContext context) {
Widget result = Listener(
onPointerDown: _handlePointerDown,
behavior: widget.behavior ?? _defaultBehavior,
child: widget.child,
);
if (!widget.excludeFromSemantics)
result = _GestureSemantics(
child: result,
assignSemantics: _updateSemanticsForRenderObject,
);
return result;
}
...略
這邊可以看到建立了 Listener
實例,而 onPointerDown
事件的處理器 _handlePointerDown
,其中取得每個 GestureRecognizer
,將指標事件 PointerDownEvent
指定給 addPointer
。
因此想要研讀一下手勢實現的相關原始碼的話,可以從 GestureDetector
、GestureRecognizer
、RawGestureDetector
開始著手,而看過以上的簡單原始碼後,應該也可以知道,GestureDetector
只是個空殼,若想直接使用 RawGestureDetector
也是可以的:
import 'package:flutter/material.dart';
import 'package:flutter/gestures.dart';
void main() => runApp(
MaterialApp(
home: Scaffold(
body: Center(
child: TextButton('按我',
onPressed: () => print('被按了XD'),
)
)
),
)
);
class TextButton extends StatelessWidget {
final String text;
final VoidCallback onPressed;
TextButton(this.text, {this.onPressed});
@override
Widget build(BuildContext context) {
final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{
TapGestureRecognizer : GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
() => TapGestureRecognizer(debugOwner: this),
(TapGestureRecognizer instance) => instance.onTap = onPressed,
)
};
return RawGestureDetector(
gestures: gestures,
child: Text(text),
);
}
}