feat: 改进聊天弹窗和遮罩层交互,优化状态管理和绘制逻辑

This commit is contained in:
2025-09-23 09:53:49 +08:00
parent b9ab384fde
commit 6523f8e512
2 changed files with 197 additions and 100 deletions

View File

@@ -1,46 +1,52 @@
import 'package:flutter/material.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
/// 遮罩层icon 区域挖洞可交互
// 一个带洞的遮罩层点击洞外区域触发onTap事件
class HoleOverlay extends StatelessWidget {
final Offset iconPosition;
final double iconSize;
final VoidCallback? onTap;
final Widget? child;
const HoleOverlay({
super.key,
required this.iconPosition,
required this.iconSize,
this.onTap,
this.child,
});
@override
Widget build(BuildContext context) {
return Listener(
behavior: HitTestBehavior.translucent,
onPointerDown: (PointerDownEvent event) {
final RenderBox box = context.findRenderObject() as RenderBox;
final Offset local = box.globalToLocal(event.position);
final iconRect = Rect.fromLTWH(
iconPosition.dx,
iconPosition.dy,
iconSize,
iconSize,
);
if (!iconRect.contains(local)) {
// 遮罩区域,拦截并关闭弹窗
if (onTap != null) {
onTap!();
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');
}
}
// 如果点在icon区域什么都不做事件会传递到下层
},
child: CustomPaint(
size: Size.infinite,
painter: _HolePainter(
iconPosition: iconPosition,
iconSize: iconSize,
),
},
child: child,
behavior: HitTestBehavior.translucent,
),
);
}
@@ -50,31 +56,51 @@ class _HolePainter extends CustomPainter {
final Offset iconPosition;
final double iconSize;
_HolePainter({required this.iconPosition, required this.iconSize});
_HolePainter({
required this.iconPosition,
required this.iconSize,
});
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.white.withOpacity(0.0)
..blendMode = BlendMode.srcOver;
// 画全屏遮罩
canvas.drawRect(Offset.zero & size, paint);
// 挖洞
final holeRect = Rect.fromLTWH(
iconPosition.dx,
iconPosition.dy,
iconSize,
iconSize,
);
final holePath = Path()..addOval(holeRect);
paint.blendMode = BlendMode.clear;
canvas.drawPath(holePath, paint);
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: 绘制调试边框完成');
}
@override
bool shouldRepaint(covariant _HolePainter oldDelegate) {
return iconPosition != oldDelegate.iconPosition || iconSize != oldDelegate.iconSize;
return oldDelegate.iconPosition != iconPosition ||
oldDelegate.iconSize != iconSize;
}
}
}