以下面代码为入口去分析 runApp( MaterialApp( home: Scaffold( appBar: AppBar(), body: GestureDetector( child: GestureDetector( child: const Text("点击事件"), onTap: () { log.log("GestureDetector ---2"); }, ), onTap: () { log.log("GestureDetector ---1"); }, ), ), ), ); 在分析前先说明下flutter整个分发的大概流程以便理解,flutter的手势入口是GestureBinding 当GestureBinding接收到PointerDownEvent时会调用hittest去保存命中测试结果(只要满足命中测试本身及父组件都会保存到HitTestResult) GestureBinding 数据接收入口 void _handlePointerDataPacket(ui.PointerDataPacket packet) { ...省略 if (!locked) _flushPointerEventQueue(); }
最终调用_handlePointerEventImmediately
void _handlePointerEventImmediately(PointerEvent event) { HitTestResult? hitTestResult; if (event is PointerDownEvent || event is PointerSignalEvent || event is PointerHoverEvent) { assert(!_hitTests.containsKey(event.pointer)); //创建命中测试集合 hitTestResult = HitTestResult(); //开始判断命中测试 hitTest(hitTestResult, event.position); if (event is PointerDownEvent) { _hitTests[event.pointer] = hitTestResult; } ...省略 } else if (event is PointerUpEvent || event is PointerCancelEvent) { //移除命中集合 hitTestResult = _hitTests.remove(event.pointer); } else if (event.down) { //理解为移动事件 hitTestResult = _hitTests[event.pointer]; } ...省略 return true; }()); if (hitTestResult != null || event is PointerAddedEvent || event is PointerRemovedEvent) { assert(event.position != null); dispatchEvent(event, hitTestResult); } }
RendererBinding
注意调用的是 RendererBindin 下的hitTest 。因为GestureBinding
绑定在WidgetsFlutterBinding上面. RendererBinding 会覆盖. mixin细节请看官方文档
void hitTest(HitTestResult result, Offset position) { ...省略 //开始调用根的hitTest renderView.hitTest(result, position: position); //还是会调用GestureBinding的hitTest super.hitTest(result, position); }
RenderView
bool hitTest(HitTestResult result, { required Offset position }) { if (child != null) //继续调用子类hitTest 以此递归下去直到没有子类,最后添加自身 child!.hitTest(BoxHitTestResult.wrap(result), position: position); result.add(HitTestEntry(this)); return true; }
RenderBox 根据入口代码 Text 为最深 text 对应的渲染树RenderParagraph 但是自身没有复写,调用父类RenderBox.hitTest bool hitTest(BoxHitTestResult result, { required Offset position }) { assert(() { //判断自己否有size if (!hasSize) { ...省略 return true; }()); //判断坐标点是否在部件里面 if (_size!.contains(position)) { //分发子部件,如果子组件满足添加自身 if (hitTestChildren(result, position: position) || hitTestSelf(position)) { result.add(BoxHitTestEntry(this, position)); return true; } } return false; }
RenderParagraph
@override bool hitTestChildren(BoxHitTestResult result, { required Offset position }) { // Hit test text spans. bool hitText = false; //转换坐标位置为文本内位置 final TextPosition textPosition = _textPainter.getPositionForOffset(position); // 根据文本内位置返回InlineSpan. text里面包含了textspan(细节自己查看源码) final InlineSpan? span = _textPainter.text!.getSpanForPosition(textPosition); if (span != null && span is HitTestTarget) { result.add(HitTestEntry(span as HitTestTarget)); hitText = true; } ...省略。 因为text 是没有子部件的所以就直接返回 return hitText; }
RenderBoxContainerDefaultsMixin 这时候看Row 因为Row是有多子部件的。Row的渲染组件是RenderFlex。RenderFlex.hitTestChildren最后调用了defaultHitTestChildren
bool defaultHitTestChildren(BoxHitTestResult result, { required Offset position }) { ChildType? child = lastChild; //循环取最后一个子部件 while (child != null) { //返回父组件的渲染数据 final ParentDataType childParentData = child.parentData! as ParentDataType; //判断是否命中子项 final bool isHit = result.addWithPaintOffset( offset: childParentData.offset, position: position, //命中直接返回,继续调用子项hitTest hitTest: (BoxHitTestResult result, Offset transformed) { assert(transformed == position - childParentData.offset); return child!.hitTest(result, position: transformed); }, ); if (isHit) return true; //取上一个兄弟项 child = childParentData.previousSibling; } return false; }
直到所有的都循环完成添加hitTestResult 里面
最后以入口源码为例, 最终保存的hitTestResult 路径值