You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何裁剪自定义Marker多余区域,解决Google Map点击触发错误UI问题

问题描述

我在Google Map中添加了自定义Marker:

  • 标记对应儿童时,显示带右下箭头的深蓝色Marker
  • 否则显示带左下箭头的红色Marker

我用CustomPainter绘制这些Marker,为了让箭头精准定位,把Container的宽度设为预期值的2倍(当前是170,原本应该是85)。点击Marker时会显示对应的自定义UI组件,但现在有个问题:当2个及以上Marker位置靠近时,点击某个Marker会错误触发相邻Marker的UI组件。

想请教有没有办法避免点击蓝色区域(非Marker图形的部分)时触发自定义UI组件?

相关代码

Container 实现

return Container(
      color: Colors.blue,
      height: 45,
      width: 170, // 85,
      child: CustomPaint(
        painter: CustomMarker(
            iskid: widget.isKid)),
        child: Column(.... text widget....)
);

CustomPainter 实现

///painter
class CustomMarker extends CustomPainter {
  CustomMarker({this.isKid});
  final bool? isKid;

  @override
  void paint(Canvas canvas, Size size) {
    final double height = size.height;
    final double markerHeight = height - 10;
    final double width = size.width / 2;
    if (!isKid!) {
        Path path = Path()
          ..moveTo(0, 0)
          ..lineTo(width, 0)
          ..lineTo(width, height + 2)
          ..lineTo(width - 10, markerHeight)
          ..lineTo(0, markerHeight)
          ..close();

        canvas.drawPath(path, fillPaint1);
        canvas.drawPath(path, strokePaint);
      } else {
        Path path = Path()
          ..moveTo(width, 0)
          ..lineTo(size.width, 0)
          ..lineTo(size.width, markerHeight)
          ..lineTo(width + 10, markerHeight)
          ..lineTo(width, height + 2)
          ..close();

        canvas.drawPath(path, fillPaint2);
        canvas.drawPath(path, strokePaint);
      }
}
解决方案

核心问题是Container的点击区域是完整的170px宽度,但实际可视的Marker只占一半,导致相邻Marker的点击区域重叠。推荐两种解决方式:

方法1:用ClipPath裁剪点击区域

把Container用ClipPath包裹,裁剪成和CustomPainter绘制的Path完全一致的形状,这样点击只会响应实际Marker图形的区域:

// 抽出生成Marker路径的公共方法,避免重复代码
Path getMarkerPath(Size size, bool isKid) {
  final double height = size.height;
  final double markerHeight = height - 10;
  final double width = size.width / 2;
  if (!isKid) {
    return Path()
      ..moveTo(0, 0)
      ..lineTo(width, 0)
      ..lineTo(width, height + 2)
      ..lineTo(width - 10, markerHeight)
      ..lineTo(0, markerHeight)
      ..close();
  } else {
    return Path()
      ..moveTo(width, 0)
      ..lineTo(size.width, 0)
      ..lineTo(size.width, markerHeight)
      ..lineTo(width + 10, markerHeight)
      ..lineTo(width, height + 2)
      ..close();
  }
}

// 修改原组件结构
return ClipPath(
  clipper: _MarkerClipper(isKid: widget.isKid),
  child: Container(
    color: Colors.blue,
    height: 45,
    width: 170,
    child: CustomPaint(
      painter: CustomMarker(isKid: widget.isKid),
      child: Column(.... text widget....),
    ),
  ),
);

// 自定义裁剪器
class _MarkerClipper extends CustomClipper<Path> {
  final bool isKid;
  _MarkerClipper({required this.isKid});

  @override
  Path getClip(Size size) {
    return getMarkerPath(size, isKid);
  }

  @override
  bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
    return (oldClipper as _MarkerClipper).isKid != isKid;
  }
}

方法2:在GestureDetector中自定义命中检测

如果不想用ClipPath,可以给组件套一个GestureDetector,重写hitTestSelf方法,判断点击坐标是否落在Marker的Path范围内:

return GestureDetector(
  onTap: () {
    // 原点击触发逻辑
  },
  hitTestSelf: (Offset position) {
    final Size size = Size(170, 45);
    Path path = getMarkerPath(size, widget.isKid);
    return path.contains(position);
  },
  child: Container(
    color: Colors.blue,
    height: 45,
    width: 170,
    child: CustomPaint(
      painter: CustomMarker(isKid: widget.isKid),
      child: Column(.... text widget....),
    ),
  ),
);

两种方法都需要复用同一个Path生成逻辑,确保点击区域和绘制的Marker图形完全匹配。

内容的提问来源于stack exchange,提问作者Moony-Stary

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 14:24:49