skip to Main Content

The following is a reference.
How can I create a border-only bubble with CustomPainter?

But what I want to achieve is a balloon for the circle. The image will be as follows.

enter image description here

If implemented as follows, they will be separated and drawn as shown in the example.

final path = Path();

// create circle
final center = Offset(size.width / 2, size.height / 2 - 10);
final radius = size.width / 2;
path.addOval(Rect.fromCircle(center: center, radius: radius));

// create tip
path.moveTo(center.dx - 10, center.dy + radius);
path.lineTo(center.dx, center.dy + radius + 12);
path.lineTo(center.dx + 10, center.dy + radius);
path.close();

// draw path
canvas.drawPath(path, paint);
canvas.drawPath(path, borderPaint);

enter image description here

This may be a rudimentary question, but please answer.

2

Answers


  1. Chosen as BEST ANSWER

    I was able to implement it in my own way and share it with you.Better.I'm sure there is a better way.If you have a better way, please let me know.

    class BorderBubblePainter extends CustomPainter {
      BorderBubblePainter({
        this.color = Colors.red,
      });
    
      final Color color;
    
      @override
      void paint(Canvas canvas, Size size) {
        final width = size.width;
        // Equivalent to width since it is circular.
        // Define a variable with a different name for easier understanding.
        final height = width;
        const strokeWidth = 1.0;
    
        final paint = Paint()
          ..isAntiAlias = true
          ..color = color
          ..strokeWidth = strokeWidth
          ..style = PaintingStyle.stroke;
    
        final triangleH = height / 10;
        final triangleW = width / 8;
    
        // NOTE: Set up a good beginning and end.
        const startAngle = 7;
        // NOTE: The height is shifted slightly upward to cover the circle.
        final heightPadding = triangleH / 10;
    
        final center = Offset(width / 2, height / 2);
        final radius = (size.width - strokeWidth) / 2;
    
        final trianglePath = Path()
          ..moveTo(width / 2 - triangleW / 2, height - heightPadding)
          ..lineTo(width / 2, triangleH + height)
          ..lineTo(width / 2 + triangleW / 2, height - heightPadding)
          ..addArc(
            Rect.fromCircle(center: center, radius: radius),
            // θ*π/180=rad
            (90 + startAngle) * pi / 180,
            (360 - (2 * startAngle)) * pi / 180,
          );
    
        canvas.drawPath(trianglePath, paint);
      }
    
      @override
      bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
    }
    
    

    usage

    class BubbleWidget extends StatelessWidget {
      const BubbleWidget({
        super.key,
      });
    
      static const double _width = 100.0;
      static const double _height = 108.0;
    
      @override
      Widget build(BuildContext context) {
        return Stack(
          clipBehavior: Clip.none,
          alignment: Alignment.center,
          children: [
            SizedBox(
              width: _width,
              height: _height,
              child: CustomPaint(
                painter: BorderBubblePainter(),
              ),
            ),
            Transform.translate(
              offset: const Offset(
                0,
                -(_height - _width) / 2,
              ),
              child: Icon(
                Icons.check,
                color: Theme.of(context).colorScheme.primary,
                size: 16,
              ),
            ),
          ],
        );
      }
    }
    

  2. Try below code with Stack and CustomPainter. Change offset values as your requirement.

              Stack(
                  alignment: Alignment.center,
                  children: [
                    Column(
                      children: [
                        Container(
                          width: 200,
                          height: 200,
                          decoration: BoxDecoration(
                            color: Colors.red,
                            borderRadius: BorderRadius.circular(100),
                          ),
                        ),
                        Transform.translate(
                            offset: Offset(0, -8), child: VShape(50, Colors.red)),
                      ],
                    ),
                    Transform.translate(
                      offset: Offset(0, -5),
                      child: Column(
                        children: [
                          Container(
                            width: 170,
                            height: 170,
                            decoration: BoxDecoration(
                              color: Colors.white,
                              borderRadius: BorderRadius.circular(100),
                            ),
                          ),
                          Transform.translate(
                              offset: Offset(0, -8),
                              child: VShape(40, Colors.white)),
                        ],
                      ),
                    )
                  ],
                ),
    

    Here is the ‘V’ shape CustomPainter class

    class VShape extends StatelessWidget {
      double size = 0;
      Color color;
      VShape(this.size, this.color);
    
      @override
      Widget build(BuildContext context) {
        return CustomPaint(
          size: Size(size, size), // Set the size of the canvas
          painter: VShapePainter(color),
        );
      }
    }
    
    class VShapePainter extends CustomPainter {
      Color color;
      VShapePainter(this.color);
    
      @override
      void paint(Canvas canvas, Size size) {
        final paint = Paint()
          ..color = color // Color of the reverse V shape
          ..style = PaintingStyle.fill;
    
        // Create the path for the reverse V shape
        Path path = Path();
        path.moveTo(0, 0); // Top left point
        path.lineTo(size.width / 2, size.height); // Bottom point
        path.lineTo(size.width, 0); // Top right point
        path.close(); // Close the path
    
        canvas.drawPath(path, paint); // Draw the path
      }
    
      @override
      bool shouldRepaint(CustomPainter oldDelegate) {
        return false; // No need to repaint unless something changes
      }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search