How to create a dotted border around a box in flutter? - flutter

I am building a list of boxes layouts in my app using flutter. I want dotted border around the box. I have used card widget to create the boxes. But, how can I get dotted border around the boxes?

EDIT
I have added this as a package in pub.
Now, all you need to do is
DottedBorder(
color: Colors.black,
gap: 3,
strokeWidth: 1,
child: FlutterLogo(size: 148),
)
Working Solution [Outdated]
Like tomerpacific said in this answer, Flutter does not have a default implementation for dashed border at the moment.
I worked for some time yesterday and was able to come up with a solution using CustomPainter. Hope this helps someone.
Add the DashedRect to your container, like so
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Container(
height: 400,
width: 300,
color: Colors.black12,
child: DashedRect(color: Colors.red, strokeWidth: 2.0, gap: 3.0,),
),
),
);
}
}
DashedRect.dart
import 'package:flutter/material.dart';
import 'dart:math' as math;
class DashedRect extends StatelessWidget {
final Color color;
final double strokeWidth;
final double gap;
DashedRect(
{this.color = Colors.black, this.strokeWidth = 1.0, this.gap = 5.0});
#override
Widget build(BuildContext context) {
return Container(
child: Padding(
padding: EdgeInsets.all(strokeWidth / 2),
child: CustomPaint(
painter:
DashRectPainter(color: color, strokeWidth: strokeWidth, gap: gap),
),
),
);
}
}
class DashRectPainter extends CustomPainter {
double strokeWidth;
Color color;
double gap;
DashRectPainter(
{this.strokeWidth = 5.0, this.color = Colors.red, this.gap = 5.0});
#override
void paint(Canvas canvas, Size size) {
Paint dashedPaint = Paint()
..color = color
..strokeWidth = strokeWidth
..style = PaintingStyle.stroke;
double x = size.width;
double y = size.height;
Path _topPath = getDashedPath(
a: math.Point(0, 0),
b: math.Point(x, 0),
gap: gap,
);
Path _rightPath = getDashedPath(
a: math.Point(x, 0),
b: math.Point(x, y),
gap: gap,
);
Path _bottomPath = getDashedPath(
a: math.Point(0, y),
b: math.Point(x, y),
gap: gap,
);
Path _leftPath = getDashedPath(
a: math.Point(0, 0),
b: math.Point(0.001, y),
gap: gap,
);
canvas.drawPath(_topPath, dashedPaint);
canvas.drawPath(_rightPath, dashedPaint);
canvas.drawPath(_bottomPath, dashedPaint);
canvas.drawPath(_leftPath, dashedPaint);
}
Path getDashedPath({
#required math.Point<double> a,
#required math.Point<double> b,
#required gap,
}) {
Size size = Size(b.x - a.x, b.y - a.y);
Path path = Path();
path.moveTo(a.x, a.y);
bool shouldDraw = true;
math.Point currentPoint = math.Point(a.x, a.y);
num radians = math.atan(size.height / size.width);
num dx = math.cos(radians) * gap < 0
? math.cos(radians) * gap * -1
: math.cos(radians) * gap;
num dy = math.sin(radians) * gap < 0
? math.sin(radians) * gap * -1
: math.sin(radians) * gap;
while (currentPoint.x <= b.x && currentPoint.y <= b.y) {
shouldDraw
? path.lineTo(currentPoint.x, currentPoint.y)
: path.moveTo(currentPoint.x, currentPoint.y);
shouldDraw = !shouldDraw;
currentPoint = math.Point(
currentPoint.x + dx,
currentPoint.y + dy,
);
}
return path;
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
I do not expect this to fit in with all use cases and there is a lot of room for customization and improvement. Comment if you find any bugs.

You can use dotted_border Flutter package
return DottedBorder(
borderType: BorderType.RRect,
radius: Radius.circular(20),
dashPattern: [10, 10],
color: Colors.grey,
strokeWidth: 2,
child: Card(
color: Colors.amber,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
child: Center(child: Text("hi")),
)

There is an one plugin for draw dotted border around widgets
https://pub.dev/packages/dotted_border
Using this plugin you can draw dotted or dashed border
//1. Install the plugin by add dependencies in pubspace.yaml
dotted_border: ^1.0.6
Add below code for show border
DottedBorder(
color: Colors.black,
strokeWidth: 1,
child: FlutterLogo(size: 148),
)

BorderStyle.none can be useful if you wanna apply some animation or remove\add border function onTap(like a lighting border) event or similar.

Related

How can i reverse container radius to specific form

i have the simple following code
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
width: 300,
height: 100,
decoration: const BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.only(topRight: Radius.circular(30.0),topLeft: Radius.circular(30.0),)
),
),
),
);
}
the outputs looks like following
but i am trying to reverse it to be like following (sorry for the bad drawing)
How could i achieve this with simple way ? best regards :)
This can be done with a CustomPainter:
class InvertedRoundedRectanglePainter extends CustomPainter {
InvertedRoundedRectanglePainter({
required this.radius,
required this.color,
});
final double radius;
final Color color;
#override
void paint(Canvas canvas, Size size) {
final cornerSize = Size.square(radius * 2);
canvas.drawPath(
Path()
..addArc(
// top-left arc
Offset(0, -radius) & cornerSize,
// 180 degree startAngle (left of circle)
pi,
// -90 degree sweepAngle (counter-clockwise to the bottom)
-pi / 2,
)
..arcTo(
// top-right arc
Offset(size.width - cornerSize.width, -radius) & cornerSize,
// 90 degree startAngle (bottom of circle)
pi / 2,
// -90 degree sweepAngle (counter-clockwise to the right)
-pi / 2,
false,
)
// bottom right of painter
..lineTo(size.width, size.height)
// bottom left of painter
..lineTo(0, size.height),
Paint()..color = color,
);
}
#override
bool shouldRepaint(InvertedRoundedRectanglePainter oldDelegate) =>
oldDelegate.radius != radius || oldDelegate.color != color;
}
You can play with this example on DartPad: https://dartpad.dartlang.org/?id=18cfbcc696b43c7c002a5ac3c94dd520

How to create dotted border around circular avatar in flutter

I'd like to display Instagram-like stories on my Flutter app and want to show the number of stories a user has uploaded by using borders around the user's avatar.
Say a user has uploaded 3 stories, i'll show 3 rounded border lines around the avatar image separated by equal number of spaces & if a user uploads 80 stories, i'll show 80 tiny round border lines separated by equal number of spaces.
I tried using plugins from pub.dev for this, like
dotted_border 2.0.0+2
fdottedline 1.0.1
just to name a few, but i can't seem to get an accurate count of spaces & dashes to fulfill the requirements above.
Below is an example:
FDottedLine(
color: Colors.black,
strokeWidth: 2.0,
dottedLength: 30,
space: 4,
corner: FDottedLineCorner.all(100.0),
child: Padding(
padding: const EdgeInsets.all(3.0),
child: SizedBox.square(
dimension: 0.055.h,
child: ClipRRect(
borderRadius: BorderRadius.circular(100),
child: ImageBox.network(
photo: user.photo.getOrEmpty,
elevation: 2,
replacement: Image.asset(AppAssets.defaultUserImage(user.gender.getOrNull)),
borderRadius: BorderRadius.circular(100),
),
),
),
),
),
No matter how i tweak the dottedLength & space params, i can't get equal number of spaces nor dashes.
I also tried using Path(), CustomPainter() but i barely know much about how to use it.
Any idea how i can achieve this using either CustomPainter() or a plugin?
Thanks for posting all your tries as it made me jump to CustomPaint directly to try
the approach that (could) work (but not tested well) is drawArc
the logic is simply to draw an arc based on the number of stories and start the next arc after leaving some space
the below code is looping for the number of stories to draw every story arc and start the next story arc (if stories > 1) after adding some value (the space between the stories) to the start of the next arc location (on the circle).
for(int i =0;i<numberOfStories;i++){
canvas.drawArc(
rect,
inRads(startOfArcInDegree),
inRads(arcLength),
false,
Paint()
..color = i==0||i==1?Colors.grey:Colors.teal
..strokeWidth =14.0
..style = PaintingStyle.stroke
);
startOfArcInDegree += arcLength + spaceLength;
}
full code with detailed explanation:
import 'dart:math';
import 'package:flutter/material.dart';
class DottedBorder extends CustomPainter {
//number of stories
final int numberOfStories;
//length of the space arc (empty one)
final int spaceLength;
//start of the arc painting in degree(0-360)
double startOfArcInDegree = 0;
DottedBorder({required this.numberOfStories, this.spaceLength = 10});
//drawArc deals with rads, easier for me to use degrees
//so this takes a degree and change it to rad
double inRads(double degree){
return (degree * pi)/180;
}
#override
bool shouldRepaint(DottedBorder oldDelegate) {
return true;
}
#override
void paint(Canvas canvas, Size size) {
//circle angle is 360, remove all space arcs between the main story arc (the number of spaces(stories) times the space length
//then subtract the number from 360 to get ALL arcs length
//then divide the ALL arcs length by number of Arc (number of stories) to get the exact length of one arc
double arcLength = (360 - (numberOfStories * spaceLength))/numberOfStories;
//be careful here when arc is a negative number
//that happens when the number of spaces is more than 360
//feel free to use what logic you want to take care of that
//note that numberOfStories should be limited too here
if(arcLength<=0){
arcLength = 360/spaceLength -1;
}
Rect rect = Rect.fromLTWH(0, 0, size.width, size.height);
//looping for number of stories to draw every story arc
for(int i =0;i<numberOfStories;i++){
//printing the arc
canvas.drawArc(
rect,
inRads(startOfArcInDegree),
//be careful here is: "double sweepAngle", not "end"
inRads(arcLength),
false,
Paint()
//here you can compare your SEEN story index with the arc index to make it grey
..color = i==0||i==1?Colors.grey:Colors.teal
..strokeWidth =14.0
..style = PaintingStyle.stroke
);
//the logic of spaces between the arcs is to start the next arc after jumping the length of space
startOfArcInDegree += arcLength + spaceLength;
}
}
}
class DottedBorderExample extends StatelessWidget {
const DottedBorderExample({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Arcs etc')),
body:Center(
child: Stack(
alignment: Alignment.center,
children: [
SizedBox(
width: 300,height: 300,
child: CustomPaint(
painter: DottedBorder(numberOfStories: 13,spaceLength:4 ),
),),
Container(child:const Center(child: Text("Some Image",style: TextStyle(fontSize: 18,color: Colors.black),)),width: 270,height: 270,decoration: const BoxDecoration(color: Colors.purple,shape: BoxShape.circle),)
],
),
),
);
}
}
void main() {
runApp(
const MaterialApp(
home: DottedBorderExample(),
),
);
}
click to look at images:
we should determine 2 things
color width
separation width\
color width can be measured through below function
double colorWidth(double radius, int statusCount, double separation)
{
return ((2 * pi * radius) - (statusCount * separation)) / statusCount;
}
2 * PI * radius >> Circumference of a circle
SO >> Circumference minus total separation pixels needed, Then result divided by total status count.
now we have the width of each status equally, To fit the circle border
measuring separation pixels width
depending on the status number to be more enhanced as WhatsApp
double separation(int statusCount) {
if (statusCount <= 20)
return 3.0;
else if (statusCount <= 30)
return 1.8;
else if (statusCount <= 60)
return 1.0;
else
return 0.3;
}
Now we add the dotted_border package to our project and import it
https://pub.dev/packages/dotted_border
import 'package:dotted_border/dotted_border.dart';
assuming we have some declarations above they are:
//each digit express a status number
List status = [1, 2, 5, 4, 9, 13, 15, 20, 30, 40, 80];
//circle radius
double radius = 27.0;
dashPattern:
we have two states
one status or more than one (multiple statuses)
dashPattern: status[index] == 1
? [
//one status
(2 * pi * (radius + 2)), // take all border
0, //zere separators
]
: [
//multiple status
colorWidth(radius + 2, status[index],
separation(status[index])),
separation(status[index]),
],
FULL CODE:
import 'dart:math';
import 'package:dotted_border/dotted_border.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'STATUS',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
List status = [1, 2, 5, 4, 9, 13, 15, 20, 30, 40, 80];
double radius = 27.0;
double colorWidth(double radius, int statusCount, double separation) {
return ((2 * pi * radius) - (statusCount * separation)) / statusCount;
}
double separation(int statusCount) {
if (statusCount <= 20)
return 3.0;
else if (statusCount <= 30)
return 1.8;
else if (statusCount <= 60)
return 1.0;
else
return 0.3;
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: ListView.separated(
itemCount: status.length,
separatorBuilder: (context, index) => Divider(
color: Colors.black,
height: 15,
),
itemBuilder: ((context, index) => Row(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child:
/// Creating a circle with a dotted border.
DottedBorder(
color: Colors.teal.shade300,
borderType: BorderType.Circle,
radius: Radius.circular(radius),
dashPattern: status[index] == 1
? [
//one status
(2 * pi * (radius + 2)),
0,
]
: [
//multiple status
colorWidth(radius + 2, status[index],
separation(status[index])),
separation(status[index]),
],
strokeWidth: 3,
child: CircleAvatar(
radius: radius,
backgroundColor: Colors.transparent,
child: CircleAvatar(
radius: radius - 2,
),
),
),
),
SizedBox(
width: 10,
),
Text(
'${status[index]}',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
],
)),
),
),
);
}
}
You can use a package named status_view:
Link:
status_view
Usage from official docs
StatusView(
radius: 40,
spacing: 15,
strokeWidth: 2,
indexOfSeenStatus: 2,
numberOfStatus: 5,
padding: 4,
centerImageUrl: "https://picsum.photos/200/300",
seenColor: Colors.grey,
unSeenColor: Colors.red,
),

GestureDetector on painted triangles in circle

I'm trying to handle taps for three painted "quartercirlces" using the CustomPaint widget. I've tried adding GestureDetectors around the QuarterCirclePainter class. Even tried using using a GestureRecognizer for the TextSpan without any success. Tried wrapping it in containers wrapped in gestureDetectors. Looked at similar posts about adding GestureDetectors around the CustomPaint, but none of them seems to work in this case.
How can this be achieved?
class CategoryCircle extends StatelessWidget {
final Color color;
final double startAngle;
final String category;
final Offset textOffset;
CategoryCircle({this.color, this.startAngle, this.category, this.textOffset});
Widget build(BuildContext context) {
FocusScope.of(context).nextFocus();
return FractionallySizedBox(
widthFactor: 1,
heightFactor: 1,
child: Center(
child: CustomPaint(
painter: QuarterCirclePainter(
context: context,
color: color,
startAngle: startAngle,
sweepAngle: math.pi * 2 / 3,
text: category,
textOffset: textOffset)),
),
);
}
}
class QuarterCirclePainter extends CustomPainter {
final BuildContext context;
final Color color;
final double startAngle;
final double sweepAngle;
final String text;
final Offset textOffset;
const QuarterCirclePainter(
{this.context,
this.color,
this.startAngle,
this.sweepAngle,
this.text,
this.textOffset});
#override
void paint(Canvas canvas, Size size) {
Offset center = Offset(size.width / 2, size.height / 2);
Rect rect = Rect.fromCircle(center: center, radius: 130);
Path path = Path()
// set the "current point"
..moveTo(center.dx, center.dy)
..arcTo(rect, startAngle, (math.pi * 2) / 3, false);
canvas.drawPath(path, Paint()..color = color);
TextSpan span = new TextSpan(
style: GoogleFonts.overpass(
textStyle: TextStyle(fontSize: 22, fontWeight: FontWeight.bold)),
text: text);
TextPainter tp = new TextPainter(
text: span,
textAlign: TextAlign.center,
textDirection: TextDirection.ltr);
tp.layout();
tp.paint(canvas, textOffset);
}
#override
bool shouldRepaint(QuarterCirclePainter oldDelegate) {
return false;
}
}
I made a CustomClipper with what you did in the QuarterCirclePainter, it looks like this:
class QuaterCircleClipper extends CustomClipper<Path>{
double startAngle, sweepAngle;
QuaterCircleClipper({#required this.startAngle, #required this.sweepAngle});
#override
Path getClip(Size size){
Offset center = Offset(size.width / 2, size.height / 2);
Rect rect = Rect.fromCircle(center: center, radius: 130);
Path path = Path()
// set the "current point"
..moveTo(center.dx, center.dy)
..arcTo(rect, startAngle, (math.pi * 2) / 3, false);
return path;
}
#override
bool shouldReclip(oldCliper) => false;
}
And used a ClipPath with the above CustomClipper as clipper to wrap a GestureDetector which has a blue Container as its child
Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: AspectRatio(
aspectRatio: 1,
child: ClipPath(
clipper: QuaterCircleClipper(startAngle: 0, sweepAngle: 120),
child: GestureDetector(
onTap: (){showDialog(context: context, builder: (_) => AlertDialog(content: Text("Tap Detected"),));},
child: Container(
color: Colors.blueAccent,
)
),
),
)
),

How BeveledRectangleBorder for TextField in flutter

I have TextField i need to change the border Corners Cut, I have try to do with wrap TextField on Container and apply BeveledRectangleBorder but is not proper.
Please suggest me how can do this.
Update
After searching a little i've found the source code of that design you shared with me. They have build a custom input border (CutCornersBorder) for that particular style. You can use that file and add that to your theme to get that look of your TextField without adding any extra code.
CutCornersBorder
return MaterialApp(
home: HomePage(),
theme: ThemeData(
inputDecorationTheme: InputDecorationTheme(border: CutCornersBorder()),
),
);
Full Preview
OLD
You can do it by creating your own custom painter implementation.
FlatCorneredBackgroundPainter
class FlatCorneredBackgroundPainter extends CustomPainter {
double radius, strokeWidth;
Color strokeColor;
FlatCorneredBackgroundPainter(
{this.radius = 10, this.strokeWidth = 4, this.strokeColor = Colors.blue});
#override
void paint(Canvas canvas, Size size) {
double w = size.width;
double h = size.height;
Paint paint = Paint()
..style = PaintingStyle.stroke
..strokeWidth = strokeWidth
..color = strokeColor;
Path path = Path()
..addPolygon([
Offset(radius, 0),
Offset(w - radius, 0),
Offset(w, radius),
Offset(w, h - radius),
Offset(w - radius, h),
Offset(radius, h),
Offset(0, h - radius),
Offset(0, radius),
], true);
canvas.drawPath(path, paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}
Usage
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: CustomPaint(
painter: FlatCorneredBackgroundPainter(radius: 10.0, strokeColor: Colors.red, strokeWidth: 2),
child: Container(
width: 300,
height: 70,
padding: const EdgeInsets.all(8.0),
child: TextField(),
),
),
),
);
}
}

How to implement a Chat bubble shaped widget in flutter

I want to design a widget of shape of a chat bubble where one corner is pinned and its height should adjust to the lines of the text? For now I'm using ClipRRect widget with some borderRadius. But I want one corner pinned. Any suggestions ?
UPDATE
I know this can be done using a stack but I'm looking for a better solution since I have to use it many times in a single view and using many stacks might affect the performs. ( correct me here if I'm wrong )
For someone who want this get done with library. You can add bubble: ^1.1.9+1 (Take latest) package from pub.dev and wrap your message with Bubble.
Bubble(
style: right ? styleMe : styleSomebody,
//Your message content child here...
)
Here right is boolean which tells the bubble is at right or left, Write your logic for that and add the style properties styleMe and styleSomebody inside your widget as shown below. Change style according to your theme.
double pixelRatio = MediaQuery.of(context).devicePixelRatio;
double px = 1 / pixelRatio;
BubbleStyle styleSomebody = BubbleStyle(
nip: BubbleNip.leftTop,
color: Colors.white,
elevation: 1 * px,
margin: BubbleEdges.only(top: 8.0, right: 50.0),
alignment: Alignment.topLeft,
);
BubbleStyle styleMe = BubbleStyle(
nip: BubbleNip.rightTop,
color: Colors.grey,
elevation: 1 * px,
margin: BubbleEdges.only(top: 8.0, left: 50.0),
alignment: Alignment.topRight,
);
Hi im also searching for a chat bubble shaped widget finally i made one.
I have made this in custom Painter and i'm not good at it.
import 'package:flutter/material.dart';
class ChatBubble extends CustomPainter {
final Color color;
final Alignment alignment;
ChatBubble({
#required this.color,
this.alignment,
});
var _radius = 10.0;
var _x = 10.0;
#override
void paint(Canvas canvas, Size size) {
if (alignment == Alignment.topRight) {
canvas.drawRRect(
RRect.fromLTRBAndCorners(
0,
0,
size.width - 8,
size.height,
bottomLeft: Radius.circular(_radius),
topRight: Radius.circular(_radius),
topLeft: Radius.circular(_radius),
),
Paint()
..color = this.color
..style = PaintingStyle.fill);
var path = new Path();
path.moveTo(size.width - _x, size.height - 20);
path.lineTo(size.width - _x, size.height);
path.lineTo(size.width, size.height);
canvas.clipPath(path);
canvas.drawRRect(
RRect.fromLTRBAndCorners(
size.width - _x,
0.0,
size.width,
size.height,
topRight: Radius.circular(_radius),
),
Paint()
..color = this.color
..style = PaintingStyle.fill);
} else {
canvas.drawRRect(
RRect.fromLTRBAndCorners(
_x,
0,
size.width,
size.height,
bottomRight: Radius.circular(_radius),
topRight: Radius.circular(_radius),
topLeft: Radius.circular(_radius),
),
Paint()
..color = this.color
..style = PaintingStyle.fill);
var path = new Path();
path.moveTo(0, size.height);
path.lineTo(_x, size.height);
path.lineTo(_x, size.height-20);
canvas.clipPath(path);
canvas.drawRRect(
RRect.fromLTRBAndCorners(
0,
0.0,
_x,
size.height,
topRight: Radius.circular(_radius),
),
Paint()
..color = this.color
..style = PaintingStyle.fill);
}
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
copy and paste the above code and paste in your project
Align(
alignment: alignment, //Change this to Alignment.topRight or Alignment.topLeft
child: CustomPaint(
painter: ChatBubble(color: Colors.blue, alignment: alignment),
child: Container(
margin: EdgeInsets.all(10),
child: Stack(
children: <Widget>[
TextView("Hello World"),
],
),
),
),
)
paste this code where you have to show chatBubble Widget.
And i have also upload this code in bitbucket ChatBubble Widget Please be free if you have anything to contribute.
Sorry I am not able to show you the code for it but I can present an idea that might work if you implement it correctly. Suppose the Widget you made with ClipRect is called MyChatBubbleRect. Now, make another widget that draws a triangle using CustomPainter, let's call it MyChatBubbleTriangle, of course fill it with same color as the chat bubble but you can use a different color initially for debugging. Now that we have two widgets we can stack 'em together on top of each other and using Positioned widget over the MyChatBubbleTriangle. Something like this:
Stack(
children : [
MyChatBubbleRect(), // Maybe decrease the width a bit
Positioned(
top: 0,
right: 0,
child: MyChatBubbleTriangle()
)
]
)
This is just an idea I think you can pursue. Sorry couldn't provide the proper source code.
Chat Body
DecoratedBox(
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(8.0),
),
child: Text("your message goes here"),
);
Make a custom Triangle
class ChatBubbleTriangle extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
var paint = Paint()..color = Colors.blue;
var path = Path();
path.lineTo(-10, 0);
path.lineTo(0, 10);
path.lineTo(10, 0);
canvas.drawPath(path, paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
Use both of them within a stack wrapping ChatBubbleTriangle with Positioned() widget