事件机制:
- 命中测试的过程是从上层组件到下层组件,但是加入 HitTestResult 的顺序是从下到上,分发事件的顺序同加入顺序。
- 通常,若用户点击坐标不在当前节点的 size 范围内,则 hitTest 直接返回 false。
- 命中测试过程中,① 兄弟节点的加入顺序是倒序的,这个可以结合 Stack 组件来理解,即上层组件(children 中靠后)优先遍历。② 一旦有一个子节点的 hitTest 返回了 true,就会终止遍历。因为通常兄弟节点通常是不重叠的,且用户点击的坐标位置只会有一个节点,所以只要一个子节点命中了 hitTest 就返回 true,并且终止遍历。
- 节点自身是否通过命中测试的标志是它被添加到 HitTestResult 列表中,而不是它 hitTest 的返回值。但是 hitTest 的返回值会影响父组件是否被添加到 HitTestResult 中。
示例:
① RenderBox
bool hitTest(BoxHitTestResult result, { required Offset position }) {
if (_size!.contains(position)) {
if (hitTestChildren(result, position: position) || hitTestSelf(position)) {
//添加到命中测试列表
result.add(BoxHitTestEntry(this, position));
return true;
}
}
return false;
}
// 通常会该重写该方法
@protected
bool hitTestSelf(Offset position) => false;
② RenderProxyBoxWithHitTestBehavior
// ColoredBox(初始化 behavior 是 HitTestBehavior.opaque)、
// Listener(初始化 behavior 是 HitTestBehavior.deferToChild) 均继承自该组件
@override
bool hitTest(BoxHitTestResult result, { required Offset position }) {
bool hitTarget = false;
if (size.contains(position)) {
hitTarget = hitTestChildren(result, position: position) || hitTestSelf(position);
if (hitTarget || behavior == HitTestBehavior.translucent) {
result.add(BoxHitTestEntry(this, position));
}
}
return hitTarget;
}
@override
bool hitTestSelf(Offset position) => behavior == HitTestBehavior.opaque;
③ IgnorePointer
@override
bool hitTest(BoxHitTestResult result, { required Offset position }) {
// 未调用 result.add,则当前节点没有命中测试;
// hitTest 返回 false(ignoring 默认为true),表示当前节点不参与测试,
// 如果有其它兄弟节点,则会继续测试下一个兄弟节点。
return !ignoring && super.hitTest(result, position: position);
}
④ AbsorbPointer
@override
bool hitTest(BoxHitTestResult result, { required Offset position }) {
// 未调用 result.add,则当前节点没有命中测试;
// hitTest 返回 true(absorbing 默认为true),表示前节点已参与测试,
// 如果有其它兄弟节点,则不会继续测试下一个兄弟节点。
return absorbing
? size.contains(position)
: super.hitTest(result, position: position);
}
⑤ 自定义事件透传组件
代码:
class PassPointer extends SingleChildRenderObjectWidget {
const PassPointer({this.onTap, super.child});
final VoidCallback? onTap;
@override
RenderObject createRenderObject(BuildContext context) {
return RenderPassPointer(onTap: onTap);
}
}
class RenderPassPointer extends RenderProxyBox {
RenderPassPointer({
this.onTap,
RenderBox? child,
}) : super(child);
final VoidCallback? onTap;
@override
bool hitTest(BoxHitTestResult result, {required Offset position}) {
if (size.contains(position)) {
// 将当前节点添加到 HitTestResult 表中,即通过命中测试
result.add(BoxHitTestEntry(this, position));
// 返回 false,表示当前节点不参与测试,则会继续测试下一个兄弟节点
return false;
} else {
return super.hitTest(result, position: position);
}
}
@override
void handleEvent(PointerEvent event, HitTestEntry entry) {
if (event is PointerDownEvent) {
onTap?.call();
}
}
}
// 展示效果
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Stack(
alignment: Alignment.center,
// 多个兄弟节点,事件的遍历从下到上
children: [
Container(
width: 150,
height: 150,
color: Colors.yellow,
child: InkWell(
onTap: () {
print('C');
},
),
),
Listener(
behavior: HitTestBehavior.translucent,
onPointerDown: (e) => print('bbb'),
child: IgnorePointer(
child: Container(
width: 100,
height: 100,
color: Colors.green,
child: InkWell(
onTap: () {
print('B');
},
),
),
),
),
PassPointer(
onTap: () => print("aaa"),
child: Container(
width: 60,
height: 60,
color: Colors.pink,
child: InkWell(
onTap: () {
print('A');
},
),
),
),
],
)),
);
}
}
标签:节点,child,监听,hitTest,result,onTap,position,flutter,事件
From: https://www.cnblogs.com/lemos/p/17041045.html