2025-09-22 17:15:44 +08:00
|
|
|
|
import 'package:flutter/material.dart';
|
2025-09-23 09:53:49 +08:00
|
|
|
|
import 'package:flutter/rendering.dart';
|
2025-09-22 17:15:44 +08:00
|
|
|
|
|
2025-09-23 09:53:49 +08:00
|
|
|
|
// 一个带洞的遮罩层,点击洞外区域触发onTap事件
|
2025-09-22 17:15:44 +08:00
|
|
|
|
class HoleOverlay extends StatelessWidget {
|
|
|
|
|
|
final Offset iconPosition;
|
|
|
|
|
|
final double iconSize;
|
|
|
|
|
|
final VoidCallback? onTap;
|
2025-09-23 09:53:49 +08:00
|
|
|
|
final Widget? child;
|
2025-09-22 17:15:44 +08:00
|
|
|
|
|
|
|
|
|
|
const HoleOverlay({
|
|
|
|
|
|
super.key,
|
|
|
|
|
|
required this.iconPosition,
|
|
|
|
|
|
required this.iconSize,
|
|
|
|
|
|
this.onTap,
|
2025-09-23 09:53:49 +08:00
|
|
|
|
this.child,
|
2025-09-22 17:15:44 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
Widget build(BuildContext context) {
|
2025-09-23 09:53:49 +08:00
|
|
|
|
debugPrint('🔧 HoleOverlay build: iconPosition=$iconPosition, iconSize=$iconSize');
|
|
|
|
|
|
|
|
|
|
|
|
final holeRect = Rect.fromLTWH(
|
|
|
|
|
|
iconPosition.dx,
|
|
|
|
|
|
iconPosition.dy,
|
|
|
|
|
|
iconSize,
|
|
|
|
|
|
iconSize,
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
return CustomPaint(
|
|
|
|
|
|
painter: _HolePainter(
|
|
|
|
|
|
iconPosition: iconPosition,
|
|
|
|
|
|
iconSize: iconSize,
|
|
|
|
|
|
),
|
|
|
|
|
|
child: GestureDetector(
|
|
|
|
|
|
onTapDown: (details) {
|
|
|
|
|
|
final tapPosition = details.localPosition;
|
|
|
|
|
|
final isInHole = holeRect.contains(tapPosition);
|
|
|
|
|
|
debugPrint('🎯 HoleOverlay onTapDown: position=$tapPosition, holeRect=$holeRect, isInHole=$isInHole');
|
|
|
|
|
|
|
|
|
|
|
|
if (!isInHole) {
|
|
|
|
|
|
debugPrint('🔥 HoleOverlay: 点击洞外,触发onTap');
|
|
|
|
|
|
onTap?.call();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
debugPrint('🚫 HoleOverlay: 点击洞内,不触发onTap');
|
2025-09-22 17:15:44 +08:00
|
|
|
|
}
|
2025-09-23 09:53:49 +08:00
|
|
|
|
},
|
|
|
|
|
|
child: child,
|
|
|
|
|
|
behavior: HitTestBehavior.translucent,
|
2025-09-22 17:15:44 +08:00
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class _HolePainter extends CustomPainter {
|
|
|
|
|
|
final Offset iconPosition;
|
|
|
|
|
|
final double iconSize;
|
|
|
|
|
|
|
2025-09-23 09:53:49 +08:00
|
|
|
|
_HolePainter({
|
|
|
|
|
|
required this.iconPosition,
|
|
|
|
|
|
required this.iconSize,
|
|
|
|
|
|
});
|
2025-09-22 17:15:44 +08:00
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
void paint(Canvas canvas, Size size) {
|
|
|
|
|
|
final holeRect = Rect.fromLTWH(
|
|
|
|
|
|
iconPosition.dx,
|
|
|
|
|
|
iconPosition.dy,
|
|
|
|
|
|
iconSize,
|
|
|
|
|
|
iconSize,
|
|
|
|
|
|
);
|
2025-09-23 09:53:49 +08:00
|
|
|
|
|
|
|
|
|
|
debugPrint('🎨 HolePainter paint: size=$size, holeRect=$holeRect');
|
|
|
|
|
|
|
|
|
|
|
|
// 使用 saveLayer 确保混合模式正确
|
|
|
|
|
|
canvas.saveLayer(Offset.zero & size, Paint());
|
|
|
|
|
|
|
|
|
|
|
|
// 画半透明背景
|
|
|
|
|
|
final backgroundPaint = Paint()
|
|
|
|
|
|
..color = Colors.black.withOpacity(0.3);
|
|
|
|
|
|
canvas.drawRect(Offset.zero & size, backgroundPaint);
|
|
|
|
|
|
debugPrint('🔲 HolePainter paint: 绘制背景完成');
|
|
|
|
|
|
|
|
|
|
|
|
// 挖洞:使用 BlendMode.clear
|
|
|
|
|
|
final holePaint = Paint()
|
|
|
|
|
|
..blendMode = BlendMode.clear;
|
|
|
|
|
|
canvas.drawOval(holeRect, holePaint);
|
|
|
|
|
|
debugPrint('⭕ HolePainter paint: 挖洞完成,holeRect=$holeRect');
|
|
|
|
|
|
|
|
|
|
|
|
canvas.restore();
|
|
|
|
|
|
|
|
|
|
|
|
// 调试:画红色边框显示洞的位置
|
|
|
|
|
|
final debugPaint = Paint()
|
|
|
|
|
|
..color = Colors.red
|
|
|
|
|
|
..style = PaintingStyle.stroke
|
|
|
|
|
|
..strokeWidth = 2.0;
|
|
|
|
|
|
canvas.drawOval(holeRect, debugPaint);
|
|
|
|
|
|
debugPrint('🔴 HolePainter paint: 绘制调试边框完成');
|
2025-09-22 17:15:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
bool shouldRepaint(covariant _HolePainter oldDelegate) {
|
2025-09-23 09:53:49 +08:00
|
|
|
|
return oldDelegate.iconPosition != iconPosition ||
|
|
|
|
|
|
oldDelegate.iconSize != iconSize;
|
2025-09-22 17:15:44 +08:00
|
|
|
|
}
|
2025-09-23 09:53:49 +08:00
|
|
|
|
}
|