Flutter自定义容器形状实现求助:按指定样式裁剪容器
实现带1/3位置内凹裁剪的自定义形状容器(Flutter)
我想做一个符合指定样式的自定义形状容器——在长容器左侧约1/3宽度的位置做上下对称的内凹裁剪,容器内可以填充文字或图标。我试了下面的Flutter代码,但效果没达到预期,求实现思路。

尝试的代码
body: Center( child: Row( children: [ Container( width: 80, height: 200, decoration: BoxDecoration( borderRadius: const BorderRadius.only( bottomRight: Radius.circular(30.0), topLeft: Radius.circular(10.0), bottomLeft: Radius.circular(10.0), ), border: Border.all( color: Colors.white, width: 2, style: BorderStyle.solid), ), child: IconButton( onPressed: () {}, icon: const Icon(Icons.arrow_back_ios), ), ), Expanded( child: Container( height: 200, margin: const EdgeInsets.only(left: 5.0), child: ClipPath( clipper: CustomClipperExample(), child: Container( decoration: BoxDecoration( color: Colors.blue, border: Border.all( color: Colors.white, width: 2, style: BorderStyle.solid), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.center, children: [ IconButton( onPressed: () {}, icon: const Icon(Icons.arrow_back, color: Colors.white)), Text( 'Hewan', style: Theme.of(context).textTheme.headlineLarge, ), IconButton( onPressed: () {}, icon: const Icon(Icons.arrow_forward, color: Colors.white)), ], ), ), ), ), ), ], ), ); class CustomClipperExample extends CustomClipper<Path> { @override Path getClip(Size size) { final path = Path(); path.moveTo(0, size.height * 0.2); path.quadraticBezierTo(size.width * 0.1, size.height * 0.2, size.width * 0.2, 0); path.lineTo(size.width, 0); path.lineTo(size.width, size.height); path.lineTo(size.width * 0.2, size.height); path.quadraticBezierTo(size.width * 0.1, size.height * 0.8, 0, size.height * 0.8); path.lineTo(0, size.height * 0.2); path.close(); return path; } @override bool shouldReclip(CustomClipperExample oldClipper) => false; }
实现思路与修正方案
核心问题分析
- 拆分左右容器拼接,易出现间隙和边框不连贯问题;
- 裁剪路径参数错误:目标是1/3位置内凹,但代码用了0.2宽度位置,曲线走向也不符合对称内凹需求;
ClipPath加边框会导致裁剪处边框断裂,因为边框是在裁剪后的容器上绘制的。
修正后的实现方案
直接用CustomPaint绘制完整形状和边框,同时封装成可复用组件:
class CustomShapeContainer extends StatelessWidget { final Widget child; final Color backgroundColor; final Color borderColor; final double borderWidth; const CustomShapeContainer({ super.key, required this.child, this.backgroundColor = Colors.blue, this.borderColor = Colors.white, this.borderWidth = 2.0, }); @override Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { final clipOffset = constraints.maxWidth / 3 + 16; return CustomPaint( painter: _CustomShapePainter( backgroundColor: backgroundColor, borderColor: borderColor, borderWidth: borderWidth, ), child: Padding( padding: EdgeInsets.only(left: clipOffset, right: 16), child: child, ), ); }, ); } } class _CustomShapePainter extends CustomPainter { final Color backgroundColor; final Color borderColor; final double borderWidth; _CustomShapePainter({ required this.backgroundColor, required this.borderColor, required this.borderWidth, }); @override void paint(Canvas canvas, Size size) { final paint = Paint() ..color = backgroundColor ..style = PaintingStyle.fill; // 定义内凹位置和深度 final clipPosition = size.width / 3; final clipDepth = 30.0; // 绘制完整形状路径 final path = Path(); path.moveTo(0, 0); // 左上角 path.lineTo(clipPosition - clipDepth, 0); // 上内凹起点 // 对称内凹曲线 path.quadraticBezierTo(clipPosition, size.height / 2, clipPosition - clipDepth, size.height); path.lineTo(0, size.height); // 左下角 path.close(); // 填充背景 canvas.drawPath(path, paint); // 绘制边框 final borderPaint = Paint() ..color = borderColor ..style = PaintingStyle.stroke ..strokeWidth = borderWidth; canvas.drawPath(path, borderPaint); } @override bool shouldRepaint(covariant _CustomShapePainter oldDelegate) { return oldDelegate.backgroundColor != backgroundColor || oldDelegate.borderColor != borderColor || oldDelegate.borderWidth != borderWidth; } } // 调用示例 body: Center( child: SizedBox( width: MediaQuery.of(context).size.width * 0.9, height: 200, child: CustomShapeContainer( child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.center, children: [ IconButton( onPressed: () {}, icon: const Icon(Icons.arrow_back, color: Colors.white), ), Text( 'Hewan', style: Theme.of(context).textTheme.headlineLarge?.copyWith(color: Colors.white), ), IconButton( onPressed: () {}, icon: const Icon(Icons.arrow_forward, color: Colors.white), ), ], ), ), ), ),
关键优化点
- 用
CustomPaint一次性绘制形状和边框,解决边框断裂问题; - 精准控制1/3宽度位置的对称内凹曲线,匹配目标样式;
- 封装成可复用组件,方便调整背景色、边框色等参数;
- 通过
Padding让内容避开内凹区域,避免被裁剪遮挡。
内容的提问来源于stack exchange,提问作者Dika Haryadi
相关产品推荐
相关产品推荐

