如何实现点击Product按钮后展示参考图样式的Flutter自定义异形盒子?

实现方案
如果你之前用ClipPath、CustomPaint没成功,大概率是路径坐标计算出错,下面两种方案都可以快速实现效果,推荐优先用第一种。
方案1:自定义ShapeBorder实现(推荐,支持阴影、边框、圆角等原生属性)
该方案本质是封装了路径绘制逻辑,直接套在Container的decoration属性里即可,不需要额外处理绘制细节,适配性更高。
首先定义自定义形状类:
class CustomPopupShape extends ShapeBorder { // 顶部三角箭头的尺寸 final double arrowSize; // 三角箭头距离盒子左侧的偏移量,根据Product按钮位置调整即可对齐 final double arrowOffset; // 盒子的圆角半径 final double borderRadius; const CustomPopupShape({ required this.arrowSize, required this.arrowOffset, this.borderRadius = 12, }); @override EdgeInsetsGeometry get dimensions => EdgeInsets.zero; @override Path getInnerPath(Rect rect, {TextDirection? textDirection}) { return getOuterPath(rect, textDirection: textDirection); } @override Path getOuterPath(Rect rect, {TextDirection? textDirection}) { final path = Path(); // 起始点:左上角圆角起点 path.moveTo(rect.left + borderRadius, rect.top + arrowSize); // 左上角圆角 path.quadraticBezierTo(rect.left, rect.top + arrowSize, rect.left, rect.top + arrowSize + borderRadius); // 左侧边线 path.lineTo(rect.left, rect.bottom - borderRadius); // 左下角圆角 path.quadraticBezierTo(rect.left, rect.bottom, rect.left + borderRadius, rect.bottom); // 底部边线 path.lineTo(rect.right - borderRadius, rect.bottom); // 右下角圆角 path.quadraticBezierTo(rect.right, rect.bottom, rect.right, rect.bottom - borderRadius); // 右侧边线 path.lineTo(rect.right, rect.top + arrowSize + borderRadius); // 右上角圆角 path.quadraticBezierTo(rect.right, rect.top + arrowSize, rect.right - borderRadius, rect.top + arrowSize); // 顶部边线到箭头右侧 path.lineTo(rect.left + arrowOffset + arrowSize, rect.top + arrowSize); // 箭头右斜边 path.lineTo(rect.left + arrowOffset + arrowSize / 2, rect.top); // 箭头左斜边 path.lineTo(rect.left + arrowOffset, rect.top + arrowSize); // 闭合路径 path.close(); return path; } @override void paint(Canvas canvas, Rect rect, {TextDirection? textDirection}) {} @override ShapeBorder scale(double t) { return CustomPopupShape( arrowSize: arrowSize * t, arrowOffset: arrowOffset * t, borderRadius: borderRadius * t, ); } }
使用示例:
Visibility( visible: isShowPopup, // 你的显隐控制变量 child: Container( width: 300, // 按需调整盒子宽度 padding: const EdgeInsets.all(16), decoration: ShapeDecoration( color: Colors.white, shape: CustomPopupShape( arrowSize: 10, arrowOffset: 24, // 调整该值让箭头正好指向Product按钮中心 borderRadius: 12, ), // 支持加阴影 shadows: const [ BoxShadow( color: Colors.black12, blurRadius: 8, offset: Offset(0, 2), ) ], // 支持加边框,自动适配形状 // side: BorderSide(color: Colors.grey.shade200, width: 1), ), child: const Column( mainAxisSize: MainAxisSize.min, children: [ // 这里放盒子内的自定义内容 Text("Product Item 1"), SizedBox(height: 12), Text("Product Item 2"), SizedBox(height: 12), Text("Product Item 3"), ], ), ), )
方案2:组合Widget实现(无需写路径,逻辑简单)
如果不想自定义路径,也可以用Stack把三角箭头和圆角盒子拼接起来,三角可以直接用旋转45度的半切Container实现,优点是调整逻辑简单,缺点是阴影、边框需要单独对三角做适配,对齐精度比自定义Shape稍低。
内容的提问来源于stack exchange,提问作者Taufiq Ahommed Neloy
相关产品推荐
相关产品推荐

