如何在Flutter中创建底部中心带平滑凹口的容器?
Flutter 实现带底部中心平滑凹口的圆角容器形状
设计效果展示
无内容的设计效果:
有内容的设计效果:
需求说明
- 容器四个角均为圆角;
- 底部中心有一个向内的平滑凹口;
- 支持添加阴影或 elevation 效果。
问题描述
使用ClipPath搭配自定义CustomClipper<Path>尝试实现,但无法复现精确形状,尤其是凹口部分难以做到平滑自然。以下是原实现代码:
原使用代码
PhysicalShape( clipper: NotchedClipper(), elevation: 8, color: Colors.white, shadowColor: Colors.black26, child: Container( width: 200, height: 400, child: Center(child: Text("Shadow Shape")), ), );
原NotchedClipper代码
class NotchedClipper extends CustomClipper<Path> { @override Path getClip(Size size) { final path = Path(); final notchWidth = 90.0; final notchDepth = 70.0; final radius = 50.0; path.moveTo(0, radius); path.quadraticBezierTo(0, 0, radius, 0); path.lineTo(size.width - radius, 0); path.quadraticBezierTo(size.width, 0, size.width, radius); path.lineTo(size.width, size.height - radius); path.quadraticBezierTo(size.width, size.height, size.width - radius, size.height); // Right side of notch path.lineTo(size.width / 2 + notchWidth / 2, size.height); path.quadraticBezierTo( size.width / 2, size.height + notchDepth, size.width / 2 - notchWidth / 2, size.height); // Left side of notch path.lineTo(radius, size.height); path.quadraticBezierTo(0, size.height, 0, size.height - radius); path.close(); return path; } @override bool shouldReclip(CustomClipper<Path> oldClipper) => false; }
解决方案
原代码核心问题是凹口曲线控制点方向错误(向外凸而非向内凹),且路径衔接存在冗余线条。以下是修正后的实现:
修正后的NotchedClipper
class NotchedClipper extends CustomClipper<Path> { final double cornerRadius; final double notchWidth; final double notchDepth; NotchedClipper({ this.cornerRadius = 50.0, this.notchWidth = 90.0, this.notchDepth = 30.0, // 根据设计图调整深度,避免过度凹陷 }); @override Path getClip(Size size) { final path = Path(); final halfWidth = size.width / 2; final notchHalfWidth = notchWidth / 2; // 左上角圆角 path.moveTo(cornerRadius, 0); path.quadraticBezierTo(0, 0, 0, cornerRadius); // 左侧边 path.lineTo(0, size.height - cornerRadius); // 左下角圆角 path.quadraticBezierTo(0, size.height, cornerRadius, size.height); // 从左下角到凹口左侧起点 path.lineTo(halfWidth - notchHalfWidth, size.height); // 绘制向内平滑凹口(二次贝塞尔曲线) path.quadraticBezierTo( halfWidth, size.height - notchDepth, halfWidth + notchHalfWidth, size.height, ); // 从凹口右侧到右下角 path.lineTo(size.width - cornerRadius, size.height); // 右下角圆角 path.quadraticBezierTo(size.width, size.height, size.width, size.height - cornerRadius); // 右侧边 path.lineTo(size.width, cornerRadius); // 右上角圆角 path.quadraticBezierTo(size.width, 0, size.width - cornerRadius, 0); // 顶部边回到起始点 path.lineTo(cornerRadius, 0); path.close(); return path; } @override bool shouldReclip(CustomClipper<Path> oldClipper) => false; }
使用方式
保持原PhysicalShape用法,可根据需求调整参数:
PhysicalShape( clipper: NotchedClipper( cornerRadius: 50.0, notchWidth: 90.0, notchDepth: 30.0, ), elevation: 8, color: Colors.white, shadowColor: Colors.black26, child: Container( width: 200, height: 400, padding: const EdgeInsets.all(16.0), child: const Center(child: Text("Shadow Shape")), ), );
优化说明
- 调整凹口曲线控制点位置,将
size.height + notchDepth改为size.height - notchDepth,实现向内凹陷效果; - 重新梳理路径绘制顺序,确保线段与曲线平滑衔接,消除突兀转角;
- 将参数封装为可配置构造函数,方便根据设计需求调整圆角大小、凹口宽度和深度;
- 保留
PhysicalShape组件,确保阴影(elevation)效果正常生效。
内容的提问来源于stack exchange,提问作者Mojtaba
相关产品推荐
相关产品推荐

