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

如何实现点击Product按钮后展示参考图样式的Flutter自定义异形盒子?

This is a custom box

实现方案

如果你之前用ClipPath、CustomPaint没成功,大概率是路径坐标计算出错,下面两种方案都可以快速实现效果,推荐优先用第一种。

方案1:自定义ShapeBorder实现(推荐,支持阴影、边框、圆角等原生属性)

该方案本质是封装了路径绘制逻辑,直接套在Container的decoration属性里即可,不需要额外处理绘制细节,适配性更高。
首先定义自定义形状类:

class CustomPopupShape extends ShapeBorder {
  // 顶部三角箭头的尺寸
  final double arrowSize;
  // 三角箭头距离盒子左侧的偏移量,根据Product按钮位置调整即可对齐
  final double arrowOffset;
  // 盒子的圆角半径
  final double borderRadius;

  const CustomPopupShape({
    required this.arrowSize,
    required this.arrowOffset,
    this.borderRadius = 12,
  });

  @override
  EdgeInsetsGeometry get dimensions => EdgeInsets.zero;

  @override
  Path getInnerPath(Rect rect, {TextDirection? textDirection}) {
    return getOuterPath(rect, textDirection: textDirection);
  }

  @override
  Path getOuterPath(Rect rect, {TextDirection? textDirection}) {
    final path = Path();
    // 起始点:左上角圆角起点
    path.moveTo(rect.left + borderRadius, rect.top + arrowSize);
    // 左上角圆角
    path.quadraticBezierTo(rect.left, rect.top + arrowSize, rect.left, rect.top + arrowSize + borderRadius);
    // 左侧边线
    path.lineTo(rect.left, rect.bottom - borderRadius);
    // 左下角圆角
    path.quadraticBezierTo(rect.left, rect.bottom, rect.left + borderRadius, rect.bottom);
    // 底部边线
    path.lineTo(rect.right - borderRadius, rect.bottom);
    // 右下角圆角
    path.quadraticBezierTo(rect.right, rect.bottom, rect.right, rect.bottom - borderRadius);
    // 右侧边线
    path.lineTo(rect.right, rect.top + arrowSize + borderRadius);
    // 右上角圆角
    path.quadraticBezierTo(rect.right, rect.top + arrowSize, rect.right - borderRadius, rect.top + arrowSize);
    // 顶部边线到箭头右侧
    path.lineTo(rect.left + arrowOffset + arrowSize, rect.top + arrowSize);
    // 箭头右斜边
    path.lineTo(rect.left + arrowOffset + arrowSize / 2, rect.top);
    // 箭头左斜边
    path.lineTo(rect.left + arrowOffset, rect.top + arrowSize);
    // 闭合路径
    path.close();
    return path;
  }

  @override
  void paint(Canvas canvas, Rect rect, {TextDirection? textDirection}) {}

  @override
  ShapeBorder scale(double t) {
    return CustomPopupShape(
      arrowSize: arrowSize * t,
      arrowOffset: arrowOffset * t,
      borderRadius: borderRadius * t,
    );
  }
}

使用示例:

Visibility(
  visible: isShowPopup, // 你的显隐控制变量
  child: Container(
    width: 300, // 按需调整盒子宽度
    padding: const EdgeInsets.all(16),
    decoration: ShapeDecoration(
      color: Colors.white,
      shape: CustomPopupShape(
        arrowSize: 10,
        arrowOffset: 24, // 调整该值让箭头正好指向Product按钮中心
        borderRadius: 12,
      ),
      // 支持加阴影
      shadows: const [
        BoxShadow(
          color: Colors.black12,
          blurRadius: 8,
          offset: Offset(0, 2),
        )
      ],
      // 支持加边框,自动适配形状
      // side: BorderSide(color: Colors.grey.shade200, width: 1),
    ),
    child: const Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        // 这里放盒子内的自定义内容
        Text("Product Item 1"),
        SizedBox(height: 12),
        Text("Product Item 2"),
        SizedBox(height: 12),
        Text("Product Item 3"),
      ],
    ),
  ),
)

方案2:组合Widget实现(无需写路径,逻辑简单)

如果不想自定义路径,也可以用Stack把三角箭头和圆角盒子拼接起来,三角可以直接用旋转45度的半切Container实现,优点是调整逻辑简单,缺点是阴影、边框需要单独对三角做适配,对齐精度比自定义Shape稍低。


内容的提问来源于stack exchange,提问作者Taufiq Ahommed Neloy

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.04 22:24:03