在學習flutter
的時候忽然想到,flutter
既然不像其餘跨平臺框架那樣採用系統原生渲染,那麼flutter
就應該擁有本身的事件處理機制。本着好奇的心理,來對flutter
的事件處理機制一窺究竟。html
事件都是由硬件收集起來的,而後傳遞給軟件。那麼在flutter
中,事件的源頭在哪尼?前端
通過分析源碼能夠發現。在類window
中,flutter
經過方法onPointerDataPacket
來接收硬件傳遞過來的事件,也就是說該方法是flutter
接收事件的源頭,而該方法是在類GestureBinding
初始化的時候設置的。因此在類GestureBinding
的方法_handlePointerDataPacket
中開始處理事件。segmentfault
mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, HitTestTarget {
@override
void initInstances() {
super.initInstances();
_instance = this;
//_handlePointerDataPacket(是一個私有方法)接收系統傳遞過來的事件,
window.onPointerDataPacket = _handlePointerDataPacket;
}
...
//一個FIFO的雙端隊列,用來存儲事件
final Queue<PointerEvent> _pendingPointerEvents = Queue<PointerEvent>();
void _handlePointerDataPacket(ui.PointerDataPacket packet) {
//這裏作了如下兩個操做
//一、將pointer數據轉換爲邏輯像素,從而隔離真實設備。
//二、將轉換後的數據加入到FIFO雙端隊列中
_pendingPointerEvents.addAll(PointerEventConverter.expand(packet.data, window.devicePixelRatio));
if (!locked){
//開始處理事件
_flushPointerEventQueue();
}
}
...
void _flushPointerEventQueue() {
assert(!locked);
//若是隊列中有數據,就處理
while (_pendingPointerEvents.isNotEmpty)
_handlePointerEvent(_pendingPointerEvents.removeFirst());
}
...
final Map<int, HitTestResult> _hitTests = <int, HitTestResult>{};
void _handlePointerEvent(PointerEvent event) {
assert(!locked);
HitTestResult hitTestResult;
//若是是Down事件
if (event is PointerDownEvent) {
assert(!_hitTests.containsKey(event.pointer));
hitTestResult = HitTestResult();
//將事件要通過的全部Widget添加到一個集合中
hitTest(hitTestResult, event.position);
_hitTests[event.pointer] = hitTestResult;
assert(() {
if (debugPrintHitTestResults)
debugPrint('$event: $hitTestResult');
return true;
}());
} else if (event is PointerUpEvent || event is PointerCancelEvent) {
hitTestResult = _hitTests.remove(event.pointer);
} else if (event.down) {
//若是是move事件
hitTestResult = _hitTests[event.pointer];
}
assert(() {
if (debugPrintMouseHoverEvents && event is PointerHoverEvent)
debugPrint('$event');
return true;
}());
if (hitTestResult != null ||
event is PointerHoverEvent ||
event is PointerAddedEvent ||
event is PointerRemovedEvent) {
//分發事件
dispatchEvent(event, hitTestResult);
}
}
@override // from HitTestable
void hitTest(HitTestResult result, Offset position) {
result.add(HitTestEntry(this));
}
//事件的分發
@override // from HitTestDispatcher
void dispatchEvent(PointerEvent event, HitTestResult hitTestResult) {
assert(!locked);
...
for (HitTestEntry entry in hitTestResult.path) {
try {
//分發給子Widget處理
entry.target.handleEvent(event, entry);
} catch (exception, stack) {
...
}
}
}
//
@override // from HitTestTarget
void handleEvent(PointerEvent event, HitTestEntry entry) {
pointerRouter.route(event);
if (event is PointerDownEvent) {
gestureArena.close(event.pointer);
} else if (event is PointerUpEvent) {
gestureArena.sweep(event.pointer);
}
}
}
複製代碼
上面就是處理事件分發的源碼,它主要作了如下三步操做。瀏覽器
因爲接收硬件的數據都是與真實設備相關的,因此須要經過PointerEventConverter
的expand
方法來隔離設備相關性。相似Android
中的px
轉dp
。markdown
只有將數據進行設備隔離後才能放入FIFO的雙端隊列——ListQueue
中。筆者認爲這裏使用隊列是爲了防止Widget
處理不及時從而致使阻塞。以下圖所示。框架
與Android
同樣,Down
事件在flutter
中也是很是重要的一個事件,它是從ListQueue
中取出的第一個事件。經過該事件,flutter
能夠獲取到目標Widget
到根Widget
的路徑,並將路徑上的全部Widget
添加到一個集合List
中。以下圖所示。 less
Widget
添加到集合後,就會遍歷該集合,並將
Down
事件交給集合中的全部
Widget
處理。以下圖所示。
在Down
事件處理完畢之後,其餘事件(如move
、up
等)會遍歷集合,並將事件傳遞給集合中的全部Widget
處理,以下圖所示。ide
Widget
往
RenderView
(根
Widget
)傳遞的。若是是作前端開發的,想必對這一流程比較熟悉,由於這與前端開發中瀏覽器的事件
冒泡機制類似, 但在
flutter
中是沒有機制來取消或中止」冒泡「過程的,而瀏覽器的冒泡是能夠中止的。
事件既然有分發,那麼確定也可以攔截,相對於Android
而言,筆者認爲flutter
的事件攔截要簡單不少。在flutter
中,能夠經過AbsorbPointer
與IgnorePointer
這兩個Widget
來攔截事件。post
AbsorbPointer
是一個Widget
,它的主要做用就是攔截其子Widget
響應事件。它的實現原理其實很簡單,就是在響應Donw
事件時,不會將其子Widget
添加到集合中,這樣其子Widget
就沒法接收到事件。以下圖所示。 學習
AbsorbPointer
中能夠修改
absorbing
的值來讓子
Widget
響應事件。
注意:AbsorbPointer自己能夠響應事件
AbsorbPointer
是一個Widget
,它的主要做用就是攔截其子Widget
響應事件。它的實現原理其實很簡單,就是在響應Donw
事件時,不會將自身及其子Widget
添加到集合中,這樣本身及其子Widget
就沒法接收到事件。以下圖所示。
IgnorePointer
中能夠修改
ignoring
的值來讓本身及其子
Widget
響應事件。
注意:IgnorePointer自己沒法響應事件
以AbsorbPointer
爲例,下面來看一下如何攔截事件。
class MyHomePage extends StatelessWidget {
_onPointerDown(PointerDownEvent event) {
print("_onPointerDown:" + event.toString());
}
_onPointerMove(PointerMoveEvent event) {
print("_onPointerMove:" + event.toString());
}
_onPointerUp(PointerUpEvent event) {
print("_onPointerUp:" + event.toString());
}
_onPointerDown1(PointerDownEvent event) {
print("_onPointerDown1:" + event.toString());
}
_onPointerMove1(PointerMoveEvent event) {
print("_onPointerMove1:" + event.toString());
}
_onPointerUp1(PointerUpEvent event) {
print("_onPointerUp1:" + event.toString());
}
@override
Widget build(BuildContext context) {
return Listener(
onPointerDown: _onPointerDown,
onPointerMove: _onPointerMove,
onPointerUp: _onPointerUp,
child: AbsorbPointer(
//該值爲false則下面的Listener不會打印任何信息
//absorbing: false,
child: Listener(
onPointerDown: _onPointerDown1,
onPointerMove: _onPointerMove1,
onPointerUp: _onPointerUp1,
child: Container(
color: Colors.red,
width: 200.0,
height: 100.0,
),
)));
}
}
複製代碼
Listener
能夠監聽最原始的事件,經過該Widget
能夠拿到事件的相關信息。經過運行上面代碼能夠發現第二個Listener
中的相關信息都不會被打印。但若是將absorbing
的值改成false
。則第二個Listener
的信息都會打印出來。
IgnorePointer
與AbsorbPointer
的用法基本一致。
前面介紹了flutter
中的事件分發及處理,但都是基於最原始的指針信息。當若是咱們想要實現點擊、雙擊、快速滑動等功能時,經過最原始的指針信息就比較麻煩了,這時候就須要使用flutter
給咱們封裝好了的Widget
——GestureDetector
。
GestureDetector
封裝了點擊、雙擊、滑動等大量功能,使開發者能夠快速使用這些基礎性功能。
GestureDetector({
Key key,
this.child,
this.onTapDown,
this.onTapUp,
this.onTap,
this.onTapCancel,
this.onDoubleTap,
this.onLongPress,
this.onLongPressUp,
this.onLongPressDragStart,
this.onLongPressDragUpdate,
this.onLongPressDragUp,
this.onVerticalDragDown,
this.onVerticalDragStart,
this.onVerticalDragUpdate,
this.onVerticalDragEnd,
this.onVerticalDragCancel,
this.onHorizontalDragDown,
this.onHorizontalDragStart,
this.onHorizontalDragUpdate,
this.onHorizontalDragEnd,
this.onHorizontalDragCancel,
this.onForcePressStart,
this.onForcePressPeak,
this.onForcePressUpdate,
this.onForcePressEnd,
this.onPanDown,
this.onPanStart,
this.onPanUpdate,
this.onPanEnd,
this.onPanCancel,
this.onScaleStart,
this.onScaleUpdate,
this.onScaleEnd,
this.behavior,
this.excludeFromSemantics = false,
this.dragStartBehavior = DragStartBehavior.down,
})
複製代碼
從上面代碼能夠看出,GestureDetector
的功能仍是蠻豐富的。下面來看如何使用GestureDetector
,以點擊事件爲例。
class MyHomePage extends StatelessWidget {
_onPointerDown(PointerDownEvent event) {
print("_onPointerDown:" + event.toString());
}
_onPointerMove(PointerMoveEvent event) {
print("_onPointerMove:" + event.toString());
}
_onPointerUp(PointerUpEvent event) {
print("_onPointerUp:" + event.toString());
}
_onPointerEnter(PointerEnterEvent event) {
print("_onPointerEnter:" + event.toString());
}
_onPointerExit(PointerExitEvent event) {
print("_onPointerExit:" + event.toString());
}
_onPointerHover(PointerHoverEvent event) {
print("_onPointerHover:" + event.toString());
}
_onPointerDown1(PointerDownEvent event) {
print("_onPointerDown1:" + event.toString());
}
_onPointerMove1(PointerMoveEvent event) {
print("_onPointerMove1:" + event.toString());
}
_onPointerUp1(PointerUpEvent event) {
print("_onPointerUp1:" + event.toString());
}
@override
Widget build(BuildContext context) {
return Listener(
onPointerDown: _onPointerDown,
onPointerMove: _onPointerMove,
onPointerUp: _onPointerUp,
onPointerEnter: _onPointerEnter,
onPointerExit: _onPointerExit,
onPointerHover: _onPointerHover,
child: Stack(
children: <Widget>[
GestureDetector(
//監聽點擊事件
onTap: () => {print("點擊事件")},
//監聽橫向滑動事件
onVerticalDragUpdate: (DragUpdateDetails details) =>
{print("橫向滑動:" + details.toString())},
child: Listener(
onPointerDown: _onPointerDown1,
onPointerMove: _onPointerMove1,
onPointerUp: _onPointerUp1,
child: Center(
child: Container(
color: Colors.red,
width: 200.0,
height: 100.0,
),
)),
),
],
));
}
}
複製代碼
經過上面代碼就能夠實現Widget
的點擊功能。使用起來蠻簡單,但實現原理仍是比較複雜的。
在前面說過,事件會從最底層的Widget
往RenderView
傳遞。但其實事件傳遞給RenderView
後,還會傳遞給一個類GestureBinding
,在該類中會對事件作最終處理。
也就是說事件開始於GestureBinding
的_handlePointerDataPacket
方法,結束於GestureBinding
的handleEvent
方法,而GestureDetector
的一系列事件(如點擊、雙擊、滑動等)也是在GestureBinding
的handleEvent
方法中轉發的。
final PointerRouter pointerRouter = PointerRouter();
//該方法是作事件的最後處理,不會在往其餘地方傳遞
@override // from HitTestTarget
void handleEvent(PointerEvent event, HitTestEntry entry) {
//轉發在GestureBinding中添加的事件
pointerRouter.route(event);
if (event is PointerDownEvent) {
gestureArena.close(event.pointer);
} else if (event is PointerUpEvent) {
//解決手勢衝突,會響應最底層的Widget
gestureArena.sweep(event.pointer);
}
}
複製代碼
在類PointerRouter
中有一個Map
。當GestureDetector
響應按下事件時就會往PointerRouter
中的Map
添加一個回調方法。而後經過PointerRouter
的route
方法將事件交給不一樣GestureDetector
來處理。
在GestureBinding
內部會用Listener
對子Widget
進行一層包裹。這樣能夠監聽到Down事件
並向PointerRouter
中的Map
添加回調方法。
class RawGestureDetector extends StatefulWidget {
...
@override
RawGestureDetectorState createState() => RawGestureDetectorState();
}
/// State for a [RawGestureDetector].
class RawGestureDetectorState extends State<RawGestureDetector> {
Map<Type, GestureRecognizer> _recognizers = const <Type, GestureRecognizer>{};
...
void _handlePointerDown(PointerDownEvent event) {
assert(_recognizers != null);
//註冊回調方法
for (GestureRecognizer recognizer in _recognizers.values)
recognizer.addPointer(event);
}
...
@override
Widget build(BuildContext context) {
//建立一個Listener
Widget result = Listener(
onPointerDown: _handlePointerDown,
behavior: widget.behavior ?? _defaultBehavior,
child: widget.child
);
//Listener包裹子Widget
if (!widget.excludeFromSemantics)
result = _GestureSemantics(owner: this, child: result);
return result;
}
...
}
//以點擊事件(TapGestureRecognizer)爲例,TapGestureRecognizer繼承自PrimaryPointerGestureRecognizer
abstract class PrimaryPointerGestureRecognizer extends OneSequenceGestureRecognizer {
...
//抽象的回調方法,須要在子類實現
@protected
void handleEvent(PointerEvent event);
@override
void addPointer(PointerDownEvent event) {
//註冊回調方法
startTrackingPointer(event.pointer);
if (state == GestureRecognizerState.ready) {
state = GestureRecognizerState.possible;
primaryPointer = event.pointer;
initialPosition = event.position;
if (deadline != null)
_timer = Timer(deadline, didExceedDeadline);
}
}
//響應事件
@override
void handleEvent(PointerEvent event) {
assert(state != GestureRecognizerState.ready);
if (event.pointer == primaryPointer) {
final bool isPreAcceptSlopPastTolerance =
state == GestureRecognizerState.possible &&
preAcceptSlopTolerance != null &&
_getDistance(event) > preAcceptSlopTolerance;
final bool isPostAcceptSlopPastTolerance =
state == GestureRecognizerState.accepted &&
postAcceptSlopTolerance != null &&
_getDistance(event) > postAcceptSlopTolerance;
if (event is PointerMoveEvent && (isPreAcceptSlopPastTolerance || isPostAcceptSlopPastTolerance)) {
resolve(GestureDisposition.rejected);
//移除回調方法
stopTrackingPointer(primaryPointer);
} else {
handlePrimaryPointer(event);
}
}
stopTrackingIfPointerNoLongerDown(event);
}
...
}
abstract class OneSequenceGestureRecognizer extends GestureRecognizer {
...
@protected
void startTrackingPointer(int pointer) {
//向PointerRouter中的Map添加回調方法handleEvent
GestureBinding.instance.pointerRouter.addRoute(pointer, handleEvent);
_trackedPointers.add(pointer);
assert(!_entries.containsValue(pointer));
_entries[pointer] = _addPointerToArena(pointer);
}
@protected
void stopTrackingPointer(int pointer) {
if (_trackedPointers.contains(pointer)) {
//向PointerRouter中的Map移除回調方法handleEvent
GestureBinding.instance.pointerRouter.removeRoute(pointer, handleEvent);
_trackedPointers.remove(pointer);
if (_trackedPointers.isEmpty)
didStopTrackingLastPointer(pointer);
}
}
...
}
複製代碼
在recognizer.addPointer(event)
中就會向PointerRouter
中的Map
添加回調方法。這樣就GestureDetector
就可以響應點擊、滑動等一系列事件了。固然這些事件處理完畢後,也會從PointerRouter
的Map
中移除回調方法。
flutter
的事件處理整體上來講比Android
要簡單一些,因此也相對而言好理解一些。本文從總體邏輯上分析了flutter
的事件處理,但願能幫助你們。
【參考資料】