如何用CustomPainter创建仅含边框的气泡形状?
Flutter 实现带箭头的空心气泡边框
问题现状
参考填充气泡的实现代码,将PaintingStyle改为stroke后,箭头与气泡边框出现断开缺口,当前效果如下:

用户完整代码:
import 'package:flutter/material.dart'; void main() { runApp( const _MyApp(), ); } class _MyApp extends StatelessWidget { const _MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( body: SafeArea( child: Container( color: Colors.grey, child: Padding( padding: const EdgeInsets.all(40.0), child: CustomPaint( painter: CustomStyleArrow(), child: Container( padding: const EdgeInsets.only( left: 15, right: 15, bottom: 20, top: 20, ), child: const Text( "This is the custom painter for arrow down curve", style: TextStyle( color: Colors.black, ), ), ), ), ), ), ), ), ); } } class CustomStyleArrow extends CustomPainter { @override void paint(Canvas canvas, Size size) { final Paint paint = Paint() ..color = Colors.white ..strokeWidth = 1 ..style = PaintingStyle.stroke; const double triangleH = 10; const double triangleW = 25.0; final double width = size.width; final double height = size.height; final Path trianglePath = Path() ..moveTo(width / 2 - triangleW / 2, height) ..lineTo(width / 2, triangleH + height) ..lineTo(width / 2 + triangleW / 2, height) ..lineTo(width / 2 - triangleW / 2, height); canvas.drawPath(trianglePath, paint); final BorderRadius borderRadius = BorderRadius.circular(15); final Rect rect = Rect.fromLTRB(0, 0, width, height); final RRect outer = borderRadius.toRRect(rect); canvas.drawRRect(outer, paint); } @override bool shouldRepaint(CustomPainter oldDelegate) => false; }
期望效果
实现箭头与气泡边框连贯的空心样式,效果如下:

解决方案
核心是将箭头与气泡的路径合并为单个闭合路径,而非分开绘制两个独立路径,这样边框会自然连贯。修改CustomStyleArrow的paint方法如下:
class CustomStyleArrow extends CustomPainter { @override void paint(Canvas canvas, Size size) { final Paint paint = Paint() ..color = Colors.white ..strokeWidth = 1 ..style = PaintingStyle.stroke; const double triangleH = 10; const double triangleW = 25.0; final double width = size.width; final double height = size.height; const double radius = 15; // 与原代码圆角半径一致 // 构建合并后的闭合路径 final Path path = Path(); // 左上角圆角 path.moveTo(radius, 0); path.arcToPoint( Offset(0, radius), radius: const Radius.circular(radius), clockwise: false, ); // 左边框 path.lineTo(0, height); // 左下角圆角 path.arcToPoint( Offset(radius, height), radius: const Radius.circular(radius), ); // 底部边框到箭头左侧起点 path.lineTo(width / 2 - triangleW / 2, height); // 绘制箭头部分 path.lineTo(width / 2, height + triangleH); path.lineTo(width / 2 + triangleW / 2, height); // 底部边框到右下角起点 path.lineTo(width - radius, height); // 右下角圆角 path.arcToPoint( Offset(width, height - radius), radius: const Radius.circular(radius), clockwise: false, ); // 右边框 path.lineTo(width, radius); // 右上角圆角 path.arcToPoint( Offset(width - radius, 0), radius: const Radius.circular(radius), ); // 上边框回到起点,闭合路径 path.lineTo(radius, 0); path.close(); // 绘制完整路径 canvas.drawPath(path, paint); } @override bool shouldRepaint(CustomPainter oldDelegate) => false; }
修改说明
- 放弃分开绘制三角形和圆角矩形,改用
Path手动构建完整闭合路径,确保箭头与气泡边框连接处无断点。 - 路径按顺时针方向绘制,依次完成圆角、边框、箭头的连接,最后通过
path.close()闭合路径,保证边框连贯性。
内容的提问来源于stack exchange,提问作者shingo.nakanishi
相关产品推荐
相关产品推荐

