一直有个疑惑?
若是不规则图片,如何控制它的事件响应控制:如何控制哪些部分触发点击事件,哪些区域无响应。近期终于找到了解决办法,分享给大家。
实现核心是重写 RenderBox 子类中 hitTest 方法:
bool hitTest(BoxHitTestResult result, {required Offset position}) {
二、示例展示
示例效果:黄色部分触发点击事件,点击蓝色线框内的白色区域不会触发点击事件。
1、使用示例
Container(
decoration: BoxDecoration(
color: Colors.transparent,
border: Border.all(color: Colors.blue),
),
child: MyCustomHitTestWidget(
radius: 50,
color: Colors.orange,
onTap: () {
DLog.d('Custom hit test area tapped!');
},
textSpan: TextSpan(
text: '$runtimeType' * 1,
style: TextStyle(
color: Colors.yellow,
fontSize: 14,
),
),
),
),
2、MyCustomHitTestWidget 源码
/// 自定义圆形组件支持 HitTest 自定义
class MyCustomHitTestWidget extends SingleChildRenderObjectWidget {
MyCustomHitTestWidget({
super.key,
required this.radius,
required this.color,
this.onTap,
this.textSpan,
this.textPainter,
});
final double radius;
final Color color;
final VoidCallback? onTap;
final TextSpan? textSpan;
final TextPainter? textPainter;
@override
RenderObject createRenderObject(BuildContext context) {
return MyHitTestRenderBox(
radius: radius,
color: color,
onTap: onTap,
textSpan: textSpan,
textPainter: textPainter,
);
}
@override
void updateRenderObject(BuildContext context, covariant MyHitTestRenderBox renderObject) {
renderObject
..radius = radius
..color = color
..onTap = onTap
..textSpan = textSpan
..textPainter = textPainter;
}
}
class MyHitTestRenderBox extends RenderBox {
MyHitTestRenderBox({
required this.radius,
required this.color,
this.onTap,
this.textSpan,
this.textPainter,
});
double radius;
Color color;
VoidCallback? onTap;
TextSpan? textSpan;
TextPainter? textPainter;
@override
bool hitTest(BoxHitTestResult result, {required Offset position}) {
final center = Offset(constraints.maxWidth / 2, constraints.maxHeight / 2);
// final r = (position - center).distance <= radius;
final rect = RRect.fromRectAndCorners(
Rect.fromLTRB(0, 0, radius * 2, radius * 2),
topLeft: Radius.circular(radius),
topRight: Radius.circular(radius),
bottomLeft: Radius.circular(radius),
bottomRight: Radius.circular(radius),
);
final contains = rect.contains(position);
if (contains) {
result.add(BoxHitTestEntry(this, position));
return true;
}
return false;
}
@override
void handleEvent(PointerEvent event, HitTestEntry entry) {
if (event is PointerDownEvent) {
onTap?.call();
}
}
@override
void performLayout() {
size = constraints.constrain(Size(radius * 2, radius * 2));
}
@override
void paint(PaintingContext context, Offset offset) {
final canvas = context.canvas;
final paint = Paint()
..color = color
..style = PaintingStyle.fill;
// Draw the circular hit test area
canvas.drawCircle(
offset + Offset(radius, radius),
radius,
paint,
);
// Draw text at the center
final textPainterNew = textPainter ??
TextPainter(
text: textSpan ??
TextSpan(
text: '$runtimeType',
style: TextStyle(
color: Colors.red,
fontSize: 16,
),
),
textDirection: TextDirection.ltr,
);
textPainterNew.layout(maxWidth: size.width);
final textOffset = offset +
Offset(
(size.width - textPainterNew.width) / 2,
(size.height - textPainterNew.height) / 2,
);
textPainterNew.paint(canvas, textOffset);
}
}
3、hitTest 返回 true 会相应事件,返回 false 则不响应,这部分代码决定是否响应式事件
总结
1、事件分发流程:
当用户进行触摸操作时,Flutter 会通过以下流程分发事件:
- 触摸事件传播:当设备上发生触摸事件时,Flutter 的渲染层会从根节点开始,逐级传递触摸事件到子节点。
- HitTest 过程:在每个
RenderObject
上,Flutter 会通过hitTest
方法来判断当前的触摸位置是否位于该组件的区域内。如果是,它会处理事件。 - 事件消费:如果某个组件的
hitTest
检测到触摸点并处理了这个事件,它就会“消费”该事件,其他组件将不再接收到该事件。
简而言之:
hitTest
是一个重要的机制,负责将触摸事件分发到对应的 Widget。- 它是 Flutter 渲染引擎处理触摸事件的核心部分,通过检查触摸位置来决定哪个组件接收到事件。
- 在自定义 Widget 时,我们可以通过重写
hitTest
方法来实现自定义的触摸事件处理。