如何在Flutter中创建自定义形状控件以实现菜单页异形菜单项
Flutter 自定义形状菜单项实现方案
核心实现逻辑是通过 Flutter 自带的 ShapeBorder 、CustomClipper<Path> 绘制能力配合可点击组件实现异形菜单项,不需要引入第三方依赖,各分类对应实现方式如下:
- 美食分类(圆形):直接通过
BoxDecoration自带的圆形属性实现 - 饮品、促销活动分类(圆角矩形):通过
BoxDecoration的圆角属性实现 - 甜点分类(圆角变形三角形):自定义
CustomClipper绘制带圆角的轻微变形三角形
各分类代码示例
1. 美食分类圆形菜单项
InkWell( onTap: () { // 处理美食分类点击逻辑 }, child: Container( width: 80, height: 80, decoration: const BoxDecoration( color: Colors.orange, shape: BoxShape.circle, ), child: const Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.restaurant, color: Colors.white, size: 32), Text("美食", style: TextStyle(color: Colors.white, fontSize: 12)) ], ), ), )
2. 饮品/促销活动圆角矩形菜单项
InkWell( onTap: () { // 处理对应分类点击逻辑 }, child: Container( width: 140, height: 60, decoration: BoxDecoration( // 促销活动可更换为对应的主题色 color: Colors.blue, borderRadius: BorderRadius.circular(16), ), child: const Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.local_drink, color: Colors.white), SizedBox(width: 6), Text("饮品", style: TextStyle(color: Colors.white)) ], ), ), )
3. 甜点分类圆角变形三角形菜单项
首先自定义三角形裁剪器:
class RoundedDeformTriangleClipper extends CustomClipper<Path> { final double radius; // 调整该参数控制三角形变形程度,1为正三角形,小于1底部边缩短 final double deformRate; RoundedDeformTriangleClipper({ this.radius = 10, this.deformRate = 0.85, }); @override Path getClip(Size size) { Path path = Path(); // 三个顶点坐标,按变形率调整底部边长度 Offset topPoint = Offset(size.width / 2, 0); Offset bottomLeft = Offset(size.width * (1 - deformRate) / 2, size.height - radius); Offset bottomRight = Offset(size.width * (1 + deformRate) / 2, size.height - radius); // 绘制带圆角的三条边 path.moveTo(topPoint.dx, topPoint.dy + radius); path.arcToPoint(Offset(topPoint.dx + radius, topPoint.dy), radius: Radius.circular(radius)); path.lineTo(bottomRight.dx - radius * 0.6, bottomRight.dy - radius * 0.6); path.arcToPoint(Offset(bottomRight.dx, bottomRight.dy + radius), radius: Radius.circular(radius)); path.lineTo(bottomLeft.dx, bottomLeft.dy + radius); path.arcToPoint(Offset(bottomLeft.dx - radius, bottomLeft.dy), radius: Radius.circular(radius)); path.lineTo(topPoint.dx - radius, topPoint.dy + radius); path.arcToPoint(Offset(topPoint.dx, topPoint.dy + radius), radius: Radius.circular(radius)); path.close(); return path; } @override bool shouldReclip(covariant CustomClipper<Path> oldClipper) => false; }
使用裁剪器实现菜单项:
InkWell( onTap: () { // 处理甜点分类点击逻辑 }, child: ClipPath( clipper: RoundedDeformTriangleClipper(deformRate: 0.85, radius: 10), child: Container( width: 90, height: 90, color: Colors.pinkAccent, child: const Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.cake, color: Colors.white, size: 30), Text("甜点", style: TextStyle(color: Colors.white, fontSize: 12)) ], ), ), ), )
页面布局建议
所有菜单项可使用 Wrap 或者 GridView 组件排列,根据实际设计稿调整组件尺寸、间距即可。如果需要点击水波纹效果,确保 InkWell 的父级包含 Material 组件即可。
设计参考图:
内容的提问来源于stack exchange,提问作者Muhammad Ibrahim
相关产品推荐
相关产品推荐


