Skip to content

Instantly share code, notes, and snippets.

@xsahil03x
Created May 20, 2025 14:48
Show Gist options
  • Save xsahil03x/7300e17e3616f12c47e52a239e9973ff to your computer and use it in GitHub Desktop.
Save xsahil03x/7300e17e3616f12c47e52a239e9973ff to your computer and use it in GitHub Desktop.
ReactionBubblePainter
import 'package:flutter/material.dart';
enum TailAlignment { start, center, end }
class ReactionBubblePainter extends CustomPainter {
ReactionBubblePainter({
required Color fillColor,
required Color maskColor,
required Color borderColor,
this.flipTail = false,
this.tailAlignment = TailAlignment.center,
double borderWidth = 1.0,
this.maskWidth = 2.0,
this.bigTailCircleRadius = 4.0,
this.smallTailCircleRadius = 2.0,
}) : _fillPaint = Paint()
..color = fillColor
..style = PaintingStyle.fill,
_maskPaint = Paint()
..color = maskColor
..style = PaintingStyle.fill,
_borderPaint = Paint()
..color = borderColor
..style = PaintingStyle.stroke
..strokeWidth = borderWidth;
final double maskWidth;
final double bigTailCircleRadius;
final double smallTailCircleRadius;
final bool flipTail;
final TailAlignment tailAlignment;
final Paint _fillPaint;
final Paint _borderPaint;
final Paint _maskPaint;
@override
void paint(Canvas canvas, Size size) {
final tailHeight = bigTailCircleRadius * 2 + smallTailCircleRadius * 2;
final fullHeight = size.height + tailHeight;
final bubbleHeight = fullHeight - tailHeight;
final bubbleWidth = size.width;
final bubbleRect = RRect.fromRectAndRadius(
Rect.fromLTRB(0, 0, bubbleWidth, bubbleHeight),
Radius.circular(bubbleHeight / 2),
);
final bigTailCircleLeft = switch (tailAlignment) {
TailAlignment.start => bubbleRect.left + 8,
TailAlignment.center => bubbleRect.center.dx,
TailAlignment.end => bubbleRect.right - 8,
};
final bigTailCircleRect = Rect.fromCircle(
center: Offset(bigTailCircleLeft, bubbleRect.bottom),
radius: bigTailCircleRadius,
);
final smallTailCircleOffset = Offset(
flipTail ? bigTailCircleRect.right : bigTailCircleRect.left,
bigTailCircleRect.bottom + smallTailCircleRadius,
);
final smallTailCircleRect = Rect.fromCircle(
center: smallTailCircleOffset,
radius: smallTailCircleRadius,
);
final reactionBubbleMaskPath = _buildCombinedPath(
bubbleRect.inflate(maskWidth),
bigTailCircleRect.inflate(maskWidth),
smallTailCircleRect.inflate(maskWidth),
);
canvas.drawPath(reactionBubbleMaskPath, _maskPaint);
final reactionBubblePath = _buildCombinedPath(
bubbleRect,
bigTailCircleRect,
smallTailCircleRect,
);
canvas.drawPath(reactionBubblePath, _borderPaint);
canvas.drawPath(reactionBubblePath, _fillPaint);
}
Path _buildCombinedPath(
RRect bubble,
Rect bigCircle,
Rect smallCircle,
) {
final bubblePath = Path()..addRRect(bubble);
final bigTailPath = Path()..addOval(bigCircle);
final smallTailPath = Path()..addOval(smallCircle);
return Path.combine(
PathOperation.union,
Path.combine(PathOperation.union, bubblePath, bigTailPath),
smallTailPath,
);
}
@override
bool shouldRepaint(covariant ReactionBubblePainter oldDelegate) => false;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment