ps: 文中flutter源碼版本 1.0.0
1. 手勢分配流程
我們從頭開始分析类嗤,先看runApp(rootWidget):
void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized()
..attachRootWidget(app)
..scheduleWarmUpFrame();
}
ensureInitialized()進行了一系列綁定,包含了手勢
class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
static WidgetsBinding ensureInitialized() {
//不存在則會創(chuàng)建一個
if (WidgetsBinding.instance == null)
WidgetsFlutterBinding();
return WidgetsBinding.instance;
}
}
這是一個典型的單例模式复哆,調(diào)用構(gòu)造函數(shù)论皆,然后調(diào)用父類的構(gòu)造函數(shù)
abstract class BindingBase {
BindingBase() {
developer.Timeline.startSync('Framework initialization');
assert(!_debugInitialized);
initInstances();
assert(_debugInitialized);
assert(!_debugServiceExtensionsRegistered);
initServiceExtensions();
assert(_debugServiceExtensionsRegistered);
developer.postEvent('Flutter.FrameworkInitialization', <String, String>{});
developer.Timeline.finishSync();
}
...
}
構(gòu)造函數(shù)中會調(diào)用initInstances()方法莹痢,那么這個方法在哪實現(xiàn)的背零?
關(guān)鍵點在于with(dart語法,混合)情组,重復的屬性或方法取最后的mixin類,即initInstances()和instance等方法和屬性取WidgetsBinding中的
mixin WidgetsBinding on BindingBase, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
@override
void initInstances() {
//注金刁,這個super是找的上一級混合類
super.initInstances();
_instance = this;
buildOwner.onBuildScheduled = _handleBuildScheduled;
ui.window.onLocaleChanged = handleLocaleChanged;
ui.window.onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged;
SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation);
SystemChannels.system.setMessageHandler(_handleSystemMessage);
}
static WidgetsBinding get instance => _instance;
static WidgetsBinding _instance;
...
}
通過super.initInstances()辜王,會逐漸往前調(diào)用,簡單理解就是所有混合類的initInstances()都將被調(diào)用
最終會調(diào)用我們所要找的手勢綁定類
mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, HitTestTarget {
@override
void initInstances() {
super.initInstances();
_instance = this;
//1.調(diào)用_handlePointerDataPacket方法
ui.window.onPointerDataPacket = _handlePointerDataPacket;
}
@override
void unlocked() {
super.unlocked();
_flushPointerEventQueue();
}
static GestureBinding get instance => _instance;
static GestureBinding _instance;
final Queue<PointerEvent> _pendingPointerEvents = Queue<PointerEvent>();
void _handlePointerDataPacket(ui.PointerDataPacket packet) {
_pendingPointerEvents.addAll(PointerEventConverter.expand(packet.data, ui.window.devicePixelRatio));
if (!locked)
//2.刷新手勢事件隊列
_flushPointerEventQueue();
}
...
void _flushPointerEventQueue() {
assert(!locked);
//3. 處理手勢事件
while (_pendingPointerEvents.isNotEmpty)
_handlePointerEvent(_pendingPointerEvents.removeFirst());
}
final Map<int, HitTestResult> _hitTests = <int, HitTestResult>{};
void _handlePointerEvent(PointerEvent event) {
assert(!locked);
HitTestResult result;
if (event is PointerDownEvent) {
assert(!_hitTests.containsKey(event.pointer));
result = HitTestResult();
//4. 手勢添加到測試result列表中
hitTest(result, event.position);
_hitTests[event.pointer] = result;
assert(() {
if (debugPrintHitTestResults)
debugPrint('$event: $result');
return true;
}());
} else if (event is PointerUpEvent || event is PointerCancelEvent) {
result = _hitTests.remove(event.pointer);
} else if (event.down) {
result = _hitTests[event.pointer];
} else {
return;
}
if (result != null)
//5.分發(fā)事件
dispatchEvent(event, result);
}
@override
void hitTest(HitTestResult result, Offset position) {
//WidgetsFlutterBinding調(diào)用時添加到result中
result.add(HitTestEntry(this));
}
@override
void dispatchEvent(PointerEvent event, HitTestResult result) {
assert(!locked);
assert(result != null);
//只有在result列表中才會進行事件處理
for (HitTestEntry entry in result.path) {
try {
//6. 處理事件撕瞧,加入了列表陵叽,包裝了一層
entry.target.handleEvent(event, entry);
} catch (exception, stack) {
//處理異常錯誤,忽略
...
}
}
}
//WidgetsFlutterBinding默認調(diào)用
@override
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);
}
}
}
按著注釋順序逐個分析下來丛版,在第四步時巩掺,如果不注意,可能就會犯錯
回到最開始页畦,WidgetsFlutterBinding混合了許多方法胖替,其中的 RendererBinding混合了HitTestable,重寫了hitTest(HitTestResult result, Offset position)方法豫缨,所以這的hitTest是使用RendererBinding中的而非GestureBinding的
mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, SemanticsBinding, HitTestable {
@override
void initInstances() {
super.initInstances();
_instance = this;
_pipelineOwner = PipelineOwner(
onNeedVisualUpdate: ensureVisualUpdate,
onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
);
ui.window
..onMetricsChanged = handleMetricsChanged
..onTextScaleFactorChanged = handleTextScaleFactorChanged
..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
..onSemanticsAction = _handleSemanticsAction;
initRenderView();
_handleSemanticsEnabledChanged();
assert(renderView != null);
addPersistentFrameCallback(_handlePersistentFrameCallback);
}
void initRenderView() {
assert(renderView == null);
renderView = RenderView(configuration: createViewConfiguration());
renderView.scheduleInitialFrame();
}
...
@override
void hitTest(HitTestResult result, Offset position) {
assert(renderView != null);
//唯一的區(qū)別是使用了renderView中的hitTest
//這么寫独令,猜猜也知道肯定要去RenderView尋找答案
renderView.hitTest(result, position: position);
//當上面遍歷完后,仍然會調(diào)用GestureBinding中的hitTest
super.hitTest(result, position);
}
...
}
renderView在initInstances中創(chuàng)建好芭,實際使用中燃箭,初始化時是無手勢的,真正進行變化的在unlocked() 方法中(同步鎖舍败,用于處理手勢事件)
所以招狸,這里又回到了RenderView的hitTest方法中來
bool hitTest(HitTestResult result, { Offset position }) {
//child的類型是RenderBox(即RenderObject)
if (child != null)
child.hitTest(result, position: position);
result.add(HitTestEntry(this));
return true;
}
HitTestEntry是什么敬拓,其實就主要包含一個HitTestTarget,也就是handleEvent(PointerEvent event, HitTestEntry entry)方法的抽象類
class HitTestEntry {
const HitTestEntry(this.target);
final HitTestTarget target;
@override
String toString() => '$target';
}
回到之前第六步瓢颅,entry.target.handleEvent(event, entry)中的target也是一個RenderView恩尾,而child.hitTest(result, position: position)是這樣的
bool hitTest(HitTestResult result, { @required Offset position }) {
//斷言判斷,省略
...
if (_size.contains(position)) {
if (hitTestChildren(result, position: position) || hitTestSelf(position)) {
result.add(BoxHitTestEntry(this, position));
return true;
}
}
return false;
}
這也是為什么要重寫hitTestChildren()或hitTestSelf(position)的原因挽懦,當他們都為false時翰意,result就不會添加這個控件,即事件分發(fā)不會分配到該控件上信柿。
2. 手勢控件分析
分析完流程冀偶,再看看手勢監(jiān)聽的控件
前面我們知道,常用手勢控件有Listener和GestureDetector渔嚷,后者是對前者的封裝进鸠,這里對基礎(chǔ)手勢簡單分析下
我們按照以下的結(jié)構(gòu)進行分析,手勢監(jiān)聽控件里添加一個文本
Listener(
child: Text("這是一個測試"),
)
a. Listener中的流程
逐一分析形病,先分析Listener:
class Listener extends SingleChildRenderObjectWidget {
const Listener({
Key key,
this.onPointerDown,
this.onPointerMove,
this.onPointerUp,
this.onPointerCancel,
this.behavior = HitTestBehavior.deferToChild,
Widget child
}) : assert(behavior != null),
super(key: key, child: child);
final PointerDownEventListener onPointerDown;
final PointerMoveEventListener onPointerMove;
final PointerUpEventListener onPointerUp;
final PointerCancelEventListener onPointerCancel;
final HitTestBehavior behavior;
@override
RenderPointerListener createRenderObject(BuildContext context) {
return RenderPointerListener(
onPointerDown: onPointerDown,
onPointerMove: onPointerMove,
onPointerUp: onPointerUp,
onPointerCancel: onPointerCancel,
behavior: behavior
);
}
@override
void updateRenderObject(BuildContext context, RenderPointerListener renderObject) {
renderObject
..onPointerDown = onPointerDown
..onPointerMove = onPointerMove
..onPointerUp = onPointerUp
..onPointerCancel = onPointerCancel
..behavior = behavior;
}
...
}
直接查看RenderPointerListener源碼客年,這里傳遞了幾個回調(diào)方法
class RenderPointerListener extends RenderProxyBoxWithHitTestBehavior {
RenderPointerListener({
this.onPointerDown,
this.onPointerMove,
this.onPointerUp,
this.onPointerCancel,
HitTestBehavior behavior = HitTestBehavior.deferToChild,
RenderBox child
}) : super(behavior: behavior, child: child);
PointerDownEventListener onPointerDown;
PointerMoveEventListener onPointerMove;
PointerUpEventListener onPointerUp;
PointerCancelEventListener onPointerCancel;
@override
void performResize() {
size = constraints.biggest;
}
@override
void handleEvent(PointerEvent event, HitTestEntry entry) {
assert(debugHandleEvent(event, entry));
if (onPointerDown != null && event is PointerDownEvent)
return onPointerDown(event);
if (onPointerMove != null && event is PointerMoveEvent)
return onPointerMove(event);
if (onPointerUp != null && event is PointerUpEvent)
return onPointerUp(event);
if (onPointerCancel != null && event is PointerCancelEvent)
return onPointerCancel(event);
}
...
}
這里有handleEvent方法,符合了之前的猜測漠吻,然后使用回調(diào)方法處理事件
繼續(xù)往下量瓜,看其子類,RenderProxyBoxWithHitTestBehavior中有hitTest方法
@override
bool hitTest(HitTestResult result, { Offset position }) {
bool hitTarget = false;
if (size.contains(position)) {
hitTarget = hitTestChildren(result, position: position) || hitTestSelf(position);
//要往result中添加數(shù)據(jù)需要滿足3個條件中任意一個即可
//1. hitTestChildren為true途乃,重寫了绍傲,看下面說明(默認是false)
//2. hitTestSelf為true,并未重寫(默認是false)
//3. behavior為translucent(默認類型是deferToChild)
if (hitTarget || behavior == HitTestBehavior.translucent)
result.add(BoxHitTestEntry(this, position));
}
return hitTarget;
}
接著再在其子類RenderProxyBoxMixin中找到了hitTestChildren方法:
@override
bool hitTestChildren(HitTestResult result, { Offset position }) {
return child?.hitTest(result, position: position) ?? false;
}
b. child值獲取過程
child?.hitTest(result, position: position)耍共,這的child是什么烫饼?
child位于RenderObjectWithChildMixin(RenderPointerListener繼承的RenderProxyBox的混合類)中,是一個RenderObject试读,直接猜測的話應該就是我們傳的Text控件
那么并未通過構(gòu)造函數(shù)傳值杠纵,值如何獲取到的呢?
之前我們知道鹏往,控件都需要經(jīng)過build過程淡诗,通過rebuild()接著執(zhí)行performRebuild()
@override
void performRebuild() {
//斷言判斷和錯誤處理省略
...
Widget built;
try {
//實際上這就是StatelessWidget.build或State.build
built = build();
debugWidgetBuilderValue(widget, built);
} catch (e, stack) {
...
} finally {
...
}
try {
//這個built即是后面的newWidget
_child = updateChild(_child, built, slot);
assert(_child != null);
} catch (e, stack) {
...
}
...
}
更新子孩子
@protected
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
...
return inflateWidget(newWidget, newSlot);
}
@protected
Element inflateWidget(Widget newWidget, dynamic newSlot) {
...
final Element newChild = newWidget.createElement();
//登記
newChild.mount(this, newSlot);
assert(newChild._debugLifecycleState == _ElementLifecycle.active);
return newChild;
}
Element中的createElement()是一個抽象方法,我們尋找他的實現(xiàn)類SingleChildRenderObjectElement(因為Listener是一個SingleChildRenderObjectWidget)
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
//向下子控件遍歷
_child = updateChild(_child, widget.child, null);
}
這里有個循環(huán)伊履,不斷遍歷下去韩容,直到無子類控件,我們看看父類的mount做了什么?
//RenderObjectElement中mount
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
//關(guān)鍵唐瀑,widget如果是Listener群凶,_renderObject則是返回的RenderPointerListener,基類方法哄辣,通用的
_renderObject = widget.createRenderObject(this);
assert(() { _debugUpdateRenderObjectOwner(); return true; }());
assert(_slot == newSlot);
//關(guān)聯(lián)object對象
attachRenderObject(newSlot);
_dirty = false;
}
//RenderObjectElement中attachRenderObject
@override
void attachRenderObject(dynamic newSlot) {
assert(_ancestorRenderObjectElement == null);
_slot = newSlot;
//找到父控件的RenderObjectElement请梢,因為都是單孩子控件赠尾,所以也是SingleChildRenderObjectElement
//父類的添加在 inflateWidget,這里并不詳述
_ancestorRenderObjectElement = _findAncestorRenderObjectElement();
//單從單詞意思上就能猜到是這個了
_ancestorRenderObjectElement?.insertChildRenderObject(renderObject, newSlot);
final ParentDataElement<RenderObjectWidget> parentDataElement = _findAncestorParentDataElement();
if (parentDataElement != null)
_updateParentData(parentDataElement.widget);
}
RenderObjectElement中的insertChildRenderObject是一個抽象類毅弧,我們再次回到SingleChildRenderObjectElement
@override
void insertChildRenderObject(RenderObject child, dynamic slot) {
//指定是RenderObjectWithChildMixin類型气嫁,和前面對應上了
final RenderObjectWithChildMixin<RenderObject> renderObject = this.renderObject;
assert(slot == null);
assert(renderObject.debugValidateChild(child));
//終于找到了,給child賦值了
renderObject.child = child;
assert(renderObject == this.renderObject);
}
c. Text中手勢分析
前面推測出child是一個RenderObject够坐,通過widget.createRenderObject(this)返回的寸宵,但是Text是一個StatelessWidget,并沒有createRenderObject方法
大膽的假設一下元咙,內(nèi)部肯定間接的實現(xiàn)了一個RenderObject類
來看源碼:
class Text extends StatelessWidget {
...
@override
Widget build(BuildContext context) {
final DefaultTextStyle defaultTextStyle = DefaultTextStyle.of(context);
TextStyle effectiveTextStyle = style;
if (style == null || style.inherit)
effectiveTextStyle = defaultTextStyle.style.merge(style);
if (MediaQuery.boldTextOverride(context))
effectiveTextStyle = effectiveTextStyle.merge(const TextStyle(fontWeight: FontWeight.bold));
//內(nèi)部使用的是 RichText
Widget result = RichText(
textAlign: textAlign ?? defaultTextStyle.textAlign ?? TextAlign.start,
textDirection: textDirection, // RichText uses Directionality.of to obtain a default if this is null.
locale: locale, // RichText uses Localizations.localeOf to obtain a default if this is null
softWrap: softWrap ?? defaultTextStyle.softWrap,
overflow: overflow ?? defaultTextStyle.overflow,
textScaleFactor: textScaleFactor ?? MediaQuery.textScaleFactorOf(context),
maxLines: maxLines ?? defaultTextStyle.maxLines,
text: TextSpan(
style: effectiveTextStyle,
text: data,
children: textSpan != null ? <TextSpan>[textSpan] : null,
),
);
if (semanticsLabel != null) {
result = Semantics(
textDirection: textDirection,
label: semanticsLabel,
child: ExcludeSemantics(
child: result,
)
);
}
return result;
}
...
}
查看RichText:
class RichText extends LeafRenderObjectWidget {
//找到了該方法
@override
RenderParagraph createRenderObject(BuildContext context) {
assert(textDirection != null || debugCheckHasDirectionality(context));
return RenderParagraph(text,
textAlign: textAlign,
textDirection: textDirection ?? Directionality.of(context),
softWrap: softWrap,
overflow: overflow,
textScaleFactor: textScaleFactor,
maxLines: maxLines,
locale: locale ?? Localizations.localeOf(context, nullOk: true),
);
}
符合之前的假設梯影,里面真創(chuàng)建了RenderObject對象
class RenderParagraph extends RenderBox {
...
//自身可以點擊
@override
bool hitTestSelf(Offset position) => true;
//重寫了事件處理方式
@override
void handleEvent(PointerEvent event, BoxHitTestEntry entry) {
assert(debugHandleEvent(event, entry));
if (event is! PointerDownEvent)
return;
_layoutTextWithConstraints(constraints);
final Offset offset = entry.localPosition;
final TextPosition position = _textPainter.getPositionForOffset(offset);
final TextSpan span = _textPainter.text.getSpanForPosition(position);
span?.recognizer?.addPointer(event);
}
...
}
RenderParagraph使用的是RenderBox中的hitTest方法
bool hitTest(HitTestResult result, { @required Offset position }) {
//斷言判斷,省略
...
if (_size.contains(position)) {
if (hitTestChildren(result, position: position) || hitTestSelf(position)) {
result.add(BoxHitTestEntry(this, position));
return true;
}
}
return false;
}
hitTestSelf通過庶香,會將RenderParagraph加入列表中甲棍,同時返回true,然后父類也會添加到列表中赶掖,這樣都會接收到分發(fā)的事件
d. 理一理
理一下順序:
3. 總結(jié)
hitTestChildren:點擊事件傳給子控件
hitTestSelf:自己接收到事件
handleEvent:處理事件