I am trying to center a Text widget in a custom shape made using Custom Painter i tried laying a Stack widget but it failed i googled and found about TextPainter and tried to use it I dont know if its the right solution but i could not acheive what i wanted
I want to achieve the button with green arrow i currently have the button with yellow arrow
Any Idea?
class CustomShape1 extends CustomPainter{
#override
void paint(Canvas canvas, Size size) {
TextPainter textPainter = TextPainter(
text: TextSpan(
text: 'Foo\nBar',
style: TextStyle(
color: Colors.white,
fontSize: 30,
),
),
textDirection: TextDirection.ltr,
textAlign: TextAlign.center
);
Paint fillPaint = Paint();
fillPaint.color = const Color(0xFF830B2C);
fillPaint.style = PaintingStyle.fill;
Path path = Path();
path.moveTo(size.width*0.5000125,size.height*0.2996000);
path.cubicTo(size.width*0.6875031,size.height*0.2990750,
size.width*0.6871875,size.height*0.2979000,size.width*0.7500000,size.height*0.2989000);
path.quadraticBezierTo(size.width*0.7645125,size.height*0.5243200,
size.width*0.9373000,size.height*0.6009200);
path.lineTo(size.width*0.9373000,size.height*0.6395600);
path.lineTo(size.width*0.5000500,size.height*0.6401600);
path.quadraticBezierTo(size.width*0.5000406,size.height*0.5550200,
size.width*0.5000125,size.height*0.2996000);
path.close();
canvas.drawPath(path, fillPaint);
textPainter.layout(
minWidth: 0,
maxWidth: size.width,
);
final offset = Offset(50 - (textPainter.width / 2), 100 - (textPainter.height / 2));
textPainter.paint(canvas,offset);
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return false;
}
}
After inspecting your canvas, you can see that it is not drawn correctly, so you do not see the text on the shape.
final offset = Offset(size.width / 2 + 50, size.height / 2); //text offset
either remake the shape, or try to adjust to this offset, this will give you the visibility of the text on the shape for a start
Related
I made a custom shape of a crooked square for music artwork images, now I'm trying to make the image fill it completely whilst not leaking out of it.
I tried using a stack, but it is bugging the painted square and the image still would leak and not fill it completely.
SizedBox(
width: 50,
height: 50,
child: CustomPaint(
painter: ImperfectRectangleBorder(strokeColor: Colors.white),
child: QueryArtworkWidget(//from on_audio_query package
artworkBorder: BorderRadius.zero,
artworkFit: BoxFit.fill,
id: item.data![index].id,
type: ArtworkType.AUDIO,
),
),
),
class ImperfectRectangleBorder extends CustomPainter {
final Color strokeColor;
ImperfectRectangleBorder({this.strokeColor = Colors.white});
#override
void paint(Canvas canvas, Size size) {
var paint = Paint()
..color = strokeColor
..strokeWidth = 4.0
..style = PaintingStyle.stroke;
var path = Path();
path.moveTo(0, -5);
path.lineTo(size.width * (0.2), size.height + 10);
path.lineTo(size.width, size.height - 5);
path.lineTo(size.height * (0.8), -1);
path.close();
canvas.drawPath(path, paint);
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return false;
}
}
I need to achieve this in flutter:
I tried making a Row and setting a background to the Text to handle the ractangle, then a ClipPath to make the triangle. The problem with this is that the edge is not precise (you can see a thin line between the rectangle and the triangle in the image below), also the ClipPath has fixed size so if I want to change the text size at some point I'll have to fine-tune the triangle again.
here's my code:
class TriangleClipper extends CustomClipper<Path> {
#override
Path getClip(Size size) {
final path = Path();
path.lineTo(0.0, size.height);
path.lineTo(size.width / 3, size.height);
path.close();
return path;
}
#override
bool shouldReclip(TriangleClipper oldClipper) => false;
}
Row(
children: [
Container(
color: Colors.orange,
child: Padding(
padding: const EdgeInsets.all(4.0),
child: Text(
"a label",
),
),
),
ClipPath(
clipper: TriangleClipper(),
child: Container(
color: Colors.orange,
height: 23,
width: 20,
),
)
],
)
I though using double.infinity instead of fixed size in ClipPath's child would solve it but doing that makes the triangle disappear (probably it becomes too big that I can't see it or goes behind something).
What is the correct way to approache this problem? I guess I could draw the trapezoid using a ClipPath, but how to make it so the width of it adapts to the lenght of the Text?
One way to achieve this is like so.
class TriangleClipper extends CustomClipper<Path> {
#override
Path getClip(Size size) {
final w = size.width;
final h = size.height;
final triangleWidth = 10;
final path = Path();
path.lineTo(0, 0);
path.lineTo(0, h);
path.lineTo(w, h);
path.lineTo(w - triangleWidth, 0);
path.close();
return path;
}
#override
bool shouldReclip(TriangleClipper oldClipper) => false;
}
and use it like so.
ClipPath(
clipper: TriangleClipper(),
child: Container(
padding: EdgeInsets.fromLTRB(4, 4, 14, 4), // 4 + triangleWidth for right padding
color: Colors.orange,
child: Text('A label'),
),
),
You can try the following code:
import 'dart:ui' as ui;
child: CustomPaint(
size: Size(WIDTH,(WIDTH*0.625).toDouble()), //You can Replace [WIDTH] with your desired width for Custom Paint and height will be calculated automatically
painter: RPSCustomPainter(),
),
class RPSCustomPainter extends CustomPainter{
#override
void paint(Canvas canvas, Size size) {
Paint paint_0 = new Paint()
..color = Color.fromARGB(255, 33, 150, 243)
..style = PaintingStyle.fill
..strokeWidth = 1;
paint_0.shader = ui.Gradient.linear(Offset(size.width*0.29,size.height*0.28),Offset(size.width*0.29,size.height*0.28),[Color(0xff7c1010),Color(0xffffffff)],[0.00,1.00]);
Path path_0 = Path();
path_0.moveTo(size.width*0.2937500,size.height*0.2780000);
canvas.drawPath(path_0, paint_0);
Paint paint_1 = new Paint()
..color = Color.fromARGB(255, 33, 150, 243)
..style = PaintingStyle.fill
..strokeWidth = 1;
Path path_1 = Path();
path_1.moveTo(size.width*0.3750000,size.height*0.4000000);
path_1.lineTo(size.width*0.5000000,size.height*0.4000000);
path_1.lineTo(size.width*0.5375000,size.height*0.5000000);
path_1.lineTo(size.width*0.3750000,size.height*0.5000000);
path_1.lineTo(size.width*0.3750000,size.height*0.4000000);
path_1.close();
canvas.drawPath(path_1, paint_1);
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
}
You can try it by yourself: https://shapemaker.web.app/#/
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,
)
),
),
)
),
I've tried to center items in a Canvas using the CustomPainter in Flutter, ranging from TextPaints to even plain circles.
Normally, when centering a custom paint, I'd set the Offset to Offset(size.width/2, size.height/2). But it's not working and my item is drawn towards the bottom right of the canvas.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(home: Container(
child: LayoutBuilder(builder: (context, constraints) {
return Container(
width: constraints.widthConstraints().maxWidth,
height: constraints.widthConstraints().maxHeight,
child: CustomPaint(
painter: FollowPainter(),
),
);
}),
));
}
}
class FollowPainter extends CustomPainter {
static final fill = Paint()..color = Colors.black;
#override
void paint(Canvas canvas, Size size) {
TextSpan span = new TextSpan(
style: new TextStyle(
color: Colors.black, fontSize: 50, fontWeight: FontWeight.w700),
text: 'Hello World');
TextPainter tp = new TextPainter(
text: span,
textAlign: TextAlign.left,
textDirection: TextDirection.ltr);
tp.layout();
tp.paint(canvas, new Offset(size.width / 2, size.height / 2));
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
// TODO: implement shouldRepaint
throw UnimplementedError();
}
I suspect that the problem could be the fact that I'm starting the drawing from that Offset, instead of drawing from the center.
So how can I center a drawn widget in the CustomPainter?
I suspect that the problem could be the fact that I'm starting the drawing from that Offset, instead of drawing from the center.
You're right, just subtract half of the width of the text from width and half of its height from height.
tp.paint(canvas, new Offset(size.width / 2 - tp.width / 2, size.height / 2 - tp.height / 2));
I need make custom widget (in Flutter) which contains text on specific position. When I draw text by TextPainter, I set TextAlign to center. Text is still drawn with left align. What am I doing wrong?
TextSpan span = TextSpan(style: TextStyle(color: Colors.white, fontSize: textSize), text: 'T');
TextPainter tp = TextPainter(text: span, textAlign: TextAlign.center, textDirection: TextDirection.ltr);
tp.layout();
tp.paint(canvas, Offset(pos.x, pos.y));
To simply center the text on the canvas, calculate its offset:
void _paintText(Canvas canvas, Size size) {
final textSpan = TextSpan(
text: 'n/a',
);
final textPainter = TextPainter(
text: textSpan,
textDirection: TextDirection.ltr,
);
textPainter.layout();
textPainter.paint(
canvas,
Offset(
// Do calculations here:
(size.width - textPainter.width) * 0.5,
(size.height - textPainter.height) * 0.5,
),
);
}
for the X position make sure layout is set to 0 for min an max width
textPainter.layout(minWidth: 0, maxWidth: 0);
for the Y position you have to substract the height of the text itself
final position = Offset(xPOS, yPOS - (textPainter.height / 2));
here is an example code
void _drawTextAt(String text, Offset position, Canvas canvas) {
final textStyle = TextStyle(
color: Colors.black,
fontSize: 30,
);
final textSpan = TextSpan(
text: 'O',
style: textStyle,
);
final textPainter = TextPainter(
text: textSpan,
textDirection: TextDirection.ltr,
textAlign: TextAlign.center);
textPainter.layout(minWidth: 0, maxWidth: 0);
Offset drawPosition =
Offset(position.dx, position.dy - (textPainter.height / 2));
textPainter.paint(canvas, drawPosition);
}
To use this function call e.g.
_drawTextAt('A', Offset(20, 20), canvas);
double width = 100, height = 200, r = 63;
canvas.drawCircle(Offset(width, height), r, Paint()..color = Colors.purple);
ParagraphBuilder paragraphBuilder =
ParagraphBuilder(ParagraphStyle(textAlign: TextAlign.center))
..addText('Hello');
ParagraphConstraints paragraphConstraints =
ParagraphConstraints(width: r * 2);
Paragraph paragraph = paragraphBuilder.build()
..layout(paragraphConstraints);
canvas.drawParagraph(
paragraph, Offset(width - r, height - paragraph.height / 2));
By using Alignment.inscribe, you can align the text in any way. :)
extension CanvasExt on Canvas {
void drawTextInRect(
Rect rect,
Alignment alignment,
TextPainter painter,
) {
final offsetRect = alignment.inscribe(painter.size, rect);
painter.paint(this, offsetRect.topLeft);
}
}
I used drawParagraph and it works well
final ui.ParagraphBuilder paragraphBuilder = ui.ParagraphBuilder(
ui.ParagraphStyle(textDirection: ui.TextDirection.ltr),
)
..addText('Your Text');
final ui.Paragraph paragraph = paragraphBuilder.build()
..layout(ui.ParagraphConstraints(width: textSize.width));
canvas.drawParagraph(paragraph, textOffset);
you can change the ParagraphStyle the way you like.