如何在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
相关产品推荐
相关产品推荐

