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

如何在Flutter中实现带圆角的自定义斜切形状?

解决方案

方法一:使用自定义ShapeBorder(推荐)

这种方式可以直接在Container的decoration中配置,同时支持背景色和边框,无需嵌套多个ClipPath,实现更简洁。

步骤1:创建自定义ShapeBorder

class CustomTrapezoidBorder extends ShapeBorder {
  final double cornerRadius;
  final double notchSize;

  CustomTrapezoidBorder({
    this.cornerRadius = 8.0,
    this.notchSize = 30.0,
  });

  @override
  EdgeInsetsGeometry get dimensions => EdgeInsets.zero;

  @override
  Path getInnerPath(Rect rect, {TextDirection? textDirection}) {
    return getOuterPath(rect).shift(EdgeInsets.all(borderSide.width));
  }

  @override
  Path getOuterPath(Rect rect, {TextDirection? textDirection}) {
    final path = Path();
    final width = rect.width;
    final height = rect.height;
    final r = cornerRadius;

    // 左上角缺口圆角起点
    path.moveTo(notchSize + r, rect.top);
    // 右上角圆角
    path.lineTo(width - r, rect.top);
    path.arcToPoint(
      Offset(width, rect.top + r),
      radius: Radius.circular(r),
      clockwise: true,
    );
    // 右下角缺口圆角起点
    path.lineTo(width - notchSize, height - r);
    path.arcToPoint(
      Offset(width - notchSize - r, height),
      radius: Radius.circular(r),
      clockwise: true,
    );
    // 左下角圆角
    path.lineTo(r, height);
    path.arcToPoint(
      Offset(rect.left, height - r),
      radius: Radius.circular(r),
      clockwise: true,
    );
    // 左上角缺口圆角终点
    path.lineTo(rect.left, notchSize + r);
    path.arcToPoint(
      Offset(rect.left + r, notchSize),
      radius: Radius.circular(r),
      clockwise: true,
    );

    path.close();
    return path;
  }

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

  @override
  ShapeBorder scale(double t) => CustomTrapezoidBorder(
        cornerRadius: cornerRadius * t,
        notchSize: notchSize * t,
      );
}

步骤2:在UI中使用

Center(
  child: Container(
    width: 200,
    height: 200,
    decoration: BoxDecoration(
      color: Colors.red,
      border: Border.all(color: Colors.black, width: 2),
      shape: CustomTrapezoidBorder(
        cornerRadius: 8.0, // 自定义圆角半径
        notchSize: 30.0, // 对应原代码的缺口大小
      ),
    ),
    child: const Center(
      child: Text(
        'Clipped Container',
        style: TextStyle(color: Colors.white),
      ),
    ),
  ),
)

方法二:使用ClipPath + Stack实现边框

如果坚持使用ClipPath,可以通过Stack嵌套两个裁剪后的Container,外层作为边框,内层作为背景。

步骤1:修改CustomClipper添加圆角

class MyCustomClipper extends CustomClipper<Path> {
  final double cornerRadius;
  final double notchSize;

  MyCustomClipper({
    this.cornerRadius = 8.0,
    this.notchSize = 30.0,
  });

  @override
  Path getClip(Size size) {
    final path = Path();
    final r = cornerRadius;

    path.moveTo(notchSize + r, 0);
    // 右上角圆角
    path.lineTo(size.width - r, 0);
    path.arcToPoint(
      Offset(size.width, r),
      radius: Radius.circular(r),
      clockwise: true,
    );
    // 右下角缺口圆角
    path.lineTo(size.width - notchSize, size.height - r);
    path.arcToPoint(
      Offset(size.width - notchSize - r, size.height),
      radius: Radius.circular(r),
      clockwise: true,
    );
    // 左下角圆角
    path.lineTo(r, size.height);
    path.arcToPoint(
      Offset(0, size.height - r),
      radius: Radius.circular(r),
      clockwise: true,
    );
    // 左上角缺口圆角
    path.lineTo(0, notchSize + r);
    path.arcToPoint(
      Offset(r, notchSize),
      radius: Radius.circular(r),
      clockwise: true,
    );

    path.close();
    return path;
  }

  @override
  bool shouldReclip(CustomClipper<Path> oldClipper) {
    return oldClipper is MyCustomClipper &&
        (oldClipper.cornerRadius != cornerRadius || oldClipper.notchSize != notchSize);
  }
}

步骤2:用Stack组合实现边框

Center(
  child: Stack(
    alignment: Alignment.center,
    children: [
      // 外层边框
      ClipPath(
        clipper: MyCustomClipper(cornerRadius: 8.0, notchSize: 30.0),
        child: Container(
          width: 200,
          height: 200,
          color: Colors.black, // 边框颜色
        ),
      ),
      // 内层背景(尺寸比外层小2倍边框宽度)
      ClipPath(
        clipper: MyCustomClipper(cornerRadius: 8.0, notchSize: 30.0),
        child: Container(
          width: 196, // 200 - 2*2(边框宽度)
          height: 196,
          color: Colors.red,
          child: const Center(
            child: Text(
              'Clipped Container',
              style: TextStyle(color: Colors.white),
            ),
          ),
        ),
      ),
    ],
  ),
)

关键说明

  • 两种方法核心都是通过arcToPoint给自定义路径的转角添加圆角,这是直接给Container加border无法实现的(默认border仅支持矩形圆角)。
  • 方法一更符合Flutter的组件设计逻辑,自动处理边框与背景的层级关系,无需手动调整尺寸。
  • 方法二更直观,适合理解ClipPath的工作原理,需要手动控制内外层尺寸差等于边框宽度的2倍。

内容的提问来源于stack exchange,提问作者moath

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 12:35:24