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() {
//不存在则会创建一个
if (WidgetsBinding.instance == null)
WidgetsFlutterBinding();
return WidgetsBinding.instance;
}
}
这是一个典型的单例模式,调用构造函数,然后调用父类的构造函数
abstract class BindingBase {
BindingBase() {
developer.Timeline.startSync('Framework initialization');
assert(!_debugInitialized);
initInstances();
assert(_debugInitialized);
assert(!_debugServiceExtensionsRegistered);
initServiceExtensions();
assert(_debugServiceExtensionsRegistered);
developer.postEvent('Flutter.FrameworkInitialization', {});
developer.Timeline.finishSync();
}
...
}
构造函数中会调用initInstances()方法,那么这个方法在哪实现的?
关键点在于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(),会逐渐往前调用,简单理解就是所有混合类的initInstances()都将被调用
最终会调用我们所要找的手势绑定类
mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, HitTestTarget {
@override
void initInstances() {
super.initInstances();
_instance = this;
//1.调用_handlePointerDataPacket方法
ui.window.onPointerDataPacket = _handlePointerDataPacket;
}
@override
void unlocked() {
super.unlocked();
_flushPointerEventQueue();
}
static GestureBinding get instance => _instance;
static GestureBinding _instance;
final Queue _pendingPointerEvents = Queue();
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 _hitTests = {};
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.分发事件
dispatchEvent(event, result);
}
@override
void hitTest(HitTestResult result, Offset position) {
//WidgetsFlutterBinding调用时添加到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默认调用
@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);
//唯一的区别是使用了renderView中的hitTest
//这么写,猜猜也知道肯定要去RenderView寻找答案
renderView.hitTest(result, position: position);
//当上面遍历完后,仍然会调用GestureBinding中的hitTest
super.hitTest(result, position);
}
...
}
renderView在initInstances中创建,实际使用中,初始化时是无手势的,真正进行变化的在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就不会添加这个控件,即事件分发不会分配到该控件上。
2. 手势控件分析
分析完流程,再看看手势监听的控件
前面我们知道,常用手势控件有Listener和GestureDetector,后者是对前者的封装,这里对基础手势简单分析下
我们按照以下的结构进行分析,手势监听控件里添加一个文本
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源码,这里传递了几个回调方法
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方法,符合了之前的猜测,然后使用回调方法处理事件
继续往下,看其子类,RenderProxyBoxWithHitTestBehavior中有hitTest方法
@override
bool hitTest(HitTestResult result, { Offset position }) {
bool hitTarget = false;
if (size.contains(position)) {
hitTarget = hitTestChildren(result, position: position) || hitTestSelf(position);
//要往result中添加数据需要满足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控件
那么并未通过构造函数传值,值如何获取到的呢?
之前我们知道,控件都需要经过build过程,通过rebuild()接着执行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()是一个抽象方法,我们寻找他的实现类SingleChildRenderObjectElement(因为Listener是一个SingleChildRenderObjectWidget)
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
//向下子控件遍历
_child = updateChild(_child, widget.child, null);
}
这里有个循环,不断遍历下去,直到无子类控件,我们看看父类的mount做了什么?
//RenderObjectElement中mount
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
//关键,widget如果是Listener,_renderObject则是返回的RenderPointerListener,基类方法,通用的
_renderObject = widget.createRenderObject(this);
assert(() { _debugUpdateRenderObjectOwner(); return true; }());
assert(_slot == newSlot);
//关联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 parentDataElement = _findAncestorParentDataElement();
if (parentDataElement != null)
_updateParentData(parentDataElement.widget);
}
RenderObjectElement中的insertChildRenderObject是一个抽象类,我们再次回到SingleChildRenderObjectElement
@override
void insertChildRenderObject(RenderObject child, dynamic slot) {
//指定是RenderObjectWithChildMixin类型,和前面对应上了
final RenderObjectWithChildMixin 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方法
大胆的假设一下,内部肯定间接的实现了一个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));
//内部使用的是 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] : 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),
);
}
符合之前的假设,里面真创建了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,然后父类也会添加到列表中,这样都会接收到分发的事件
d. 理一理
理一下顺序:
3. 总结
hitTestChildren:点击事件传给子控件
hitTestSelf:自己接收到事件
handleEvent:处理事件
下一篇:深入分析GestureDetector