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,
)
),
),
)
),
Related
I need to create a Circular progress bar with intervals on the outer circle. The progress bar fills the empty spaces when it is tapped.
Circle with gaps at intervals
Completed circle
You can use CustomPainter or ClipPath with Stack
Stack(
alignment: Alignment.center,
children: [
CustomPaint(
size: const Size(100, 100),
painter: LoaderPaint(percentage: _sliderValue),
),
/// image widget with size, or may wrap stack with sizedBox
],
)
Pain Class
class LoaderPaint extends CustomPainter {
final double percentage;
LoaderPaint({
required this.percentage,
});
deg2Rand(double deg) => deg * pi / 180;
#override
void paint(Canvas canvas, Size size) {
final midOffset = Offset(size.width / 2, size.height / 2);
Paint paint = Paint()..color = Colors.black;
canvas.drawArc(
Rect.fromCenter(
center: midOffset,
width: size.width * .9,
height: size.height * .9,
),
deg2Rand(-90),
deg2Rand(360 * percentage),
true,
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 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(),
),
),
),
);
}
}
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 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.