flutter 事件分发源码解析1 hitTest

以下面代码为入口去分析
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 路径值 

flutter 事件分发源码解析1 hitTest_第1张图片

 

你可能感兴趣的:(flutter)