UME: Widget信息获取
昨天介绍了主要是通过RenderObject实现的,至于什么是RenderObject就不多介绍了可以去看下flutter的三棵树
首先在WidgetInfoInspector中有一段获取点击事件的代码
void _inspectAt(Offset? position) {
final List<RenderObject> selected =
HitTest.hitTest(position, edgeHitMargin: 2.0);
setState(() {
selection.candidates = selected;
});
}
然后我们看下HitTest类中实现
class HitTest {
// all of RenderObjects of current point
static List<RenderObject> hitTest(
Offset? position, {
double edgeHitMargin = 0.0,
}) {
final dynamic ignorePointer = rootKey.currentContext!.findRenderObject();
final RenderObject userRender = ignorePointer.child;
bool _hitTestHelper(
List<RenderObject> hits,
List<RenderObject> edgeHits,
Offset? position,
RenderObject object,
Matrix4 transform,
) {
bool hit = false;
final Matrix4? inverse = Matrix4.tryInvert(transform);
if (inverse == null) {
return false;
}
final Offset localPosition =
MatrixUtils.transformPoint(inverse, position!);
final List<DiagnosticsNode> children = object.debugDescribeChildren();
for (int i = children.length - 1; i >= 0; i -= 1) {
final DiagnosticsNode diagnostics = children[i];
assert(diagnostics != null);
if (diagnostics.style == DiagnosticsTreeStyle.offstage ||
diagnostics.value is! RenderObject) continue;
final RenderObject child = diagnostics.value as RenderObject;
final Rect? paintClip = object.describeApproximatePaintClip(child);
if (paintClip != null && !paintClip.contains(localPosition)) continue;
final Matrix4 childTransform = transform.clone();
object.applyPaintTransform(child, childTransform);
if (_hitTestHelper(hits, edgeHits, position, child, childTransform))
hit = true;
}
final Rect bounds = object.semanticBounds;
if (bounds.contains(localPosition)) {
hit = true;
if (!bounds.deflate(edgeHitMargin).contains(localPosition))
edgeHits.add(object);
}
if (hit) hits.add(object);
return hit;
}
final List<RenderObject> regularHits = <RenderObject>[];
final List<RenderObject> edgeHits = <RenderObject>[];
_hitTestHelper(regularHits, edgeHits, position, userRender,
userRender.getTransformTo(null));
double _area(RenderObject object) {
final Size size = object.semanticBounds.size;
return size.width * size.height;
}
regularHits
.sort((RenderObject a, RenderObject b) => _area(a).compareTo(_area(b)));
final Set<RenderObject> hits = Set<RenderObject>();
hits.addAll(edgeHits);
hits.addAll(regularHits);
return hits.toList();
}
}
通过rootKey.currentContext!.findRenderObject()
获取到RenderObject
,
然后通过_hitTestHelper
方法做了一个递归把所有包含点击点的renderObject
都加入到list并返回
这个时候我们返回到_inspectAt
方法中,将list获取的所有元素赋值给了selection.candidates
在InspectorSelection
中写了candidates的set方法,将candidates中的第一个元素取了出来
set candidates(List<RenderObject> value) {
_candidates = value;
_index = 0;
_computeCurrent();
}
void _computeCurrent() {
if (_index < candidates.length) {
_current = candidates[index];
_currentElement = (_current?.debugCreator as DebugCreator?)?.element;
} else {
_current = null;
_currentElement = null;
}
}
万事俱备,目前已经拿到了当前点击的RenderObject,那么如何显示出来,在WidgetInfoInspector
的build中添加了Overlay进行显示,并把刚才写好的selection传入
Widget build(BuildContext context) {
List<Widget> children = <Widget>[];
GestureDetector gesture = GestureDetector(
onTap: _handleTap,
onPanDown: _handlePanDown,
onPanEnd: _handlePanEnd,
behavior: HitTestBehavior.opaque,
child: IgnorePointer(
child: Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height)),
);
children.add(gesture);
children.add(InspectorOverlay(selection: selection));
return Stack(children: children, textDirection: TextDirection.ltr);
}
在Overlay中有个_SelectionInfo
类会将selection.current的file及line取出,加上selection.currentElement
的组件类型及size,组成显示的信息
转载自:https://juejin.cn/post/7025965713424662565