i have this code in flutter , iam using a custom paint to draw a "road like" and the path is done but when i draw circles inside it this happens :
and here is what i want :
and here is my code :
import 'dart:math' as math;
import 'package:flutter/material.dart';
//Add this CustomPaint widget to the Widget Tree
//Copy this CustomPainter code to the Bottom of the File
extension DrawOnPathExtension on Canvas {
void drawOnPath(
Path path, {
required double spacing,
required int steps,
required Color defaultColor,
required Color stepCompletedColor,
double offsetFromPath = 10.0, // Adjust for desired offset from the path
}) {
final paint = Paint()..style = PaintingStyle.fill;
final pathMetrics = path.computeMetrics();
double distance = 0.0; // Start at the beginning of the path
int shapeCounter = 0;
for (final metric in pathMetrics) {
while (distance < metric.length) {
final tangent = metric.getTangentForOffset(distance);
if (tangent == null) continue;
// Calculate the normal (perpendicular) vector, consistently on one side
final normal =
Offset(-tangent.vector.dy, tangent.vector.dx).normalize();
// Offset the position by the normal vector and specified offset
final offsetPosition = tangent.position + normal * offsetFromPath;
// Determine color based on steps completed
paint.color =
shapeCounter < steps * 5 ? defaultColor : stepCompletedColor;
// Draw circle at the offset position
drawCircle(this, offsetPosition, paint,
radius: 5.0); // Adjust radius as needed
distance += spacing; // Move to the next position along the path
shapeCounter++;
}
distance = 0.0; // Reset distance for next path metric
}
}
void drawCircle(Canvas canvas, Offset position, Paint paint,
{double? radius}) {
canvas.drawCircle(position, radius!, paint);
}
}
extension on Offset {
// Normalize the Offset to get a unit vector
Offset normalize() {
final length = math.sqrt(dx * dx + dy * dy);
return Offset(dx / length, dy / length);
}
}
class RPSCustomPainter1 extends CustomPainter {
final int stepNumber;
RPSCustomPainter1({required this.stepNumber});
@override
void paint(Canvas canvas, Size size) {
Path path_0 = Path();
path_0.moveTo(size.width * 0.7180468, size.height * 0.02709575);
path_0.lineTo(size.width * 0.7585899, size.height * 0.04322462);
path_0.lineTo(size.width * 0.5463654, size.height * 0.1534690);
path_0.lineTo(size.width * 0.5066828, size.height * 0.1674390);
path_0.lineTo(size.width * 0.4452413, size.height * 0.1719870);
path_0.lineTo(size.width * 0.3967298, size.height * 0.1617205);
path_0.lineTo(size.width * 0.2607467, size.height * 0.09137139);
path_0.lineTo(size.width * 0.1801492, size.height * 0.04966274);
path_0.lineTo(size.width * 0.1535241, size.height * 0.03829289);
path_0.lineTo(size.width * 0.1110580, size.height * 0.03719908);
path_0.lineTo(size.width * 0.07781390, size.height * 0.04915422);
path_0.lineTo(size.width * 0.06558509, size.height * 0.06854533);
path_0.lineTo(size.width * 0.08100125, size.height * 0.09115071);
path_0.lineTo(size.width * 0.3775313, size.height * 0.2422786);
path_0.lineTo(size.width * 0.5260088, size.height * 0.3199390);
path_0.lineTo(size.width * 0.5832749, size.height * 0.3077632);
path_0.lineTo(size.width * 0.6205457, size.height * 0.2884680);
path_0.lineTo(size.width * 0.7098659, size.height * 0.2422786);
path_0.lineTo(size.width * 0.7729755, size.height * 0.2284525);
path_0.lineTo(size.width * 0.8304328, size.height * 0.2393905);
path_0.lineTo(size.width * 0.8658337, size.height * 0.2554810);
path_0.lineTo(size.width * 0.9350524, size.height * 0.2889861);
path_0.lineTo(size.width * 0.9755636, size.height * 0.3107759);
path_0.lineTo(size.width * 0.9982788, size.height * 0.3450582);
path_0.lineTo(size.width * 0.9873037, size.height * 0.3732573);
path_0.lineTo(size.width * 0.9598287, size.height * 0.3941836);
path_0.lineTo(size.width * 0.9035826, size.height * 0.4244648);
path_0.lineTo(size.width * 0.7605979, size.height * 0.4929238);
path_0.lineTo(size.width * 0.6050551, size.height * 0.5751610);
path_0.lineTo(size.width * 0.5392469, size.height * 0.6110743);
path_0.lineTo(size.width * 0.4607637, size.height * 0.6302256);
path_0.lineTo(size.width * 0.3966235, size.height * 0.6215039);
path_0.lineTo(size.width * 0.3524150, size.height * 0.6004145);
path_0.lineTo(size.width * 0.2721787, size.height * 0.5561536);
path_0.lineTo(size.width * 0.2467648, size.height * 0.5403798);
path_0.lineTo(size.width * 0.2365122, size.height * 0.5156923);
path_0.lineTo(size.width * 0.2399545, size.height * 0.4979611);
path_0.lineTo(size.width * 0.2555832, size.height * 0.4740029);
path_0.lineTo(size.width * 0.2765772, size.height * 0.4647535);
path_0.lineTo(size.width * 0.2883704, size.height * 0.4458325);
path_0.lineTo(size.width * 0.2851406, size.height * 0.4353166);
path_0.lineTo(size.width * 0.2691613, size.height * 0.4266237);
path_0.lineTo(size.width * 0.1527698, size.height * 0.3681721);
path_0.lineTo(size.width * 0.1179639, size.height * 0.3632883);
path_0.lineTo(size.width * 0.09614118, size.height * 0.3655431);
path_0.lineTo(size.width * 0.07278851, size.height * 0.3788031);
path_0.lineTo(size.width * 0.06388517, size.height * 0.3943659);
path_0.lineTo(size.width * 0.07820701, size.height * 0.4161270);
path_0.lineTo(size.width * 0.1041414, size.height * 0.4326012);
path_0.lineTo(size.width * 0.1138416, size.height * 0.4567322);
path_0.lineTo(size.width * 0.1047683, size.height * 0.4849218);
path_0.lineTo(size.width * 0.08567604, size.height * 0.4976637);
path_0.lineTo(size.width * 0.06644568, size.height * 0.5117584);
path_0.lineTo(size.width * 0.06388517, size.height * 0.5238287);
path_0.lineTo(size.width * 0.07097172, size.height * 0.5405237);
path_0.lineTo(size.width * 0.3899620, size.height * 0.7089222);
path_0.lineTo(size.width * 0.5029536, size.height * 0.7667981);
path_0.lineTo(size.width * 0.5419349, size.height * 0.7749921);
path_0.lineTo(size.width * 0.5645120, size.height * 0.7719985);
path_0.lineTo(size.width * 0.6117592, size.height * 0.7522140);
path_0.lineTo(size.width * 0.6923780, size.height * 0.7080011);
path_0.lineTo(size.width * 0.7431206, size.height * 0.6833137);
path_0.lineTo(size.width * 0.8080151, size.height * 0.6485037);
path_0.lineTo(size.width * 0.8541361, size.height * 0.6376711);
path_0.lineTo(size.width * 0.8930962, size.height * 0.6366445);
path_0.lineTo(size.width * 0.9610293, size.height * 0.6549898);
path_0.lineTo(size.width * 0.9933278, size.height * 0.6862209);
path_0.lineTo(size.width * 0.9846051, size.height * 0.7166077);
path_0.lineTo(size.width * 0.9506279, size.height * 0.7450563);
path_0.lineTo(size.width * 0.8039672, size.height * 0.8161442);
path_0.lineTo(size.width * 0.7521408, size.height * 0.8452837);
path_0.lineTo(size.width * 0.7301375, size.height * 0.8622089);
path_0.lineTo(size.width * 0.7290750, size.height * 0.8811587);
path_0.lineTo(size.width * 0.7547226, size.height * 0.8975274);
path_0.lineTo(size.width * 0.7695332, size.height * 0.9127160);
path_0.lineTo(size.width * 0.7793927, size.height * 0.9243449);
path_0.lineTo(size.width * 0.7789465, size.height * 0.9451848);
path_0.lineTo(size.width * 0.7551476, size.height * 0.9706687);
path_0.lineTo(size.width * 0.7082404, size.height * 0.9895033);
path_0.lineTo(size.width * 0.6569877, size.height * 0.9948572);
path_0.lineTo(size.width * 0.6282378, size.height * 0.9920267);
path_0.lineTo(size.width * 0.5725016, size.height * 0.9744298);
path_0.lineTo(size.width * 0.5310873, size.height * 0.9512775);
path_0.lineTo(size.width * 0.4816515, size.height * 0.9247095);
path_0.lineTo(size.width * 0.4392491, size.height * 0.9106531);
path_0.lineTo(size.width * 0.3966235, size.height * 0.9176190);
path_0.lineTo(size.width * 0.3565054, size.height * 0.9344099);
path_0.lineTo(size.width * 0.3246850, size.height * 0.9391881);
path_0.lineTo(size.width * 0.2660696, size.height * 0.9344099);
path_0.lineTo(size.width * 0.2456280, size.height * 0.9260816);
path_0.lineTo(size.width * 0.1933873, size.height * 0.9024783);
path_0.lineTo(size.width * 0.1445146, size.height * 0.8758240);
path_0.lineTo(size.width * 0.1218737, size.height * 0.8622089);
path_0.lineTo(size.width * 0.1673254, size.height * 0.8428274);
path_0.lineTo(size.width * 0.2722318, size.height * 0.9009815);
path_0.lineTo(size.width * 0.3126793, size.height * 0.9096649);
path_0.lineTo(size.width * 0.3505557, size.height * 0.9011063);
path_0.lineTo(size.width * 0.3743227, size.height * 0.8880286);
path_0.lineTo(size.width * 0.4257134, size.height * 0.8826651);
path_0.lineTo(size.width * 0.4815771, size.height * 0.8886042);
path_0.lineTo(size.width * 0.5185823, size.height * 0.9014133);
path_0.lineTo(size.width * 0.5595397, size.height * 0.9229153);
path_0.lineTo(size.width * 0.6098468, size.height * 0.9499439);
path_0.lineTo(size.width * 0.6647543, size.height * 0.9672241);
path_0.lineTo(size.width * 0.7005801, size.height * 0.9561229);
path_0.lineTo(size.width * 0.7179512, size.height * 0.9415292);
path_0.lineTo(size.width * 0.7179512, size.height * 0.9280293);
path_0.lineTo(size.width * 0.7068911, size.height * 0.9127160);
path_0.lineTo(size.width * 0.6867045, size.height * 0.8975274);
path_0.lineTo(size.width * 0.6740613, size.height * 0.8811587);
path_0.lineTo(size.width * 0.6740613, size.height * 0.8651929);
path_0.lineTo(size.width * 0.6790973, size.height * 0.8498892);
path_0.lineTo(size.width * 0.6906037, size.height * 0.8335492);
path_0.lineTo(size.width * 0.8937549, size.height * 0.7272099);
path_0.lineTo(size.width * 0.9333312, size.height * 0.7059382);
path_0.lineTo(size.width * 0.9358705, size.height * 0.6904426);
path_0.lineTo(size.width * 0.9233973, size.height * 0.6779502);
path_0.lineTo(size.width * 0.9100635, size.height * 0.6667722);
path_0.lineTo(size.width * 0.8955292, size.height * 0.6680003);
path_0.lineTo(size.width * 0.8606489, size.height * 0.6648341);
path_0.lineTo(size.width * 0.8164722, size.height * 0.6822486);
path_0.lineTo(size.width * 0.7452668, size.height * 0.7216737);
path_0.lineTo(size.width * 0.6578271, size.height * 0.7673066);
path_0.lineTo(size.width * 0.5956312, size.height * 0.7950644);
path_0.lineTo(size.width * 0.5511995, size.height * 0.8046592);
path_0.lineTo(size.width * 0.4933703, size.height * 0.7980100);
path_0.lineTo(size.width * 0.4545909, size.height * 0.7847692);
path_0.lineTo(size.width * 0.03892820, size.height * 0.5665064);
path_0.lineTo(size.width * 0.008340239, size.height * 0.5365131);
path_0.lineTo(size.width * 0.003527337, size.height * 0.5134759);
path_0.lineTo(size.width * 0.01826353, size.height * 0.4914078);
path_0.lineTo(size.width * 0.04395359, size.height * 0.4764591);
path_0.lineTo(size.width * 0.05453560, size.height * 0.4598985);
path_0.lineTo(size.width * 0.05588492, size.height * 0.4498047);
path_0.lineTo(size.width * 0.03163979, size.height * 0.4302409);
path_0.lineTo(size.width * 0.01340813, size.height * 0.4089884);
path_0.lineTo(size.width * 0.008340239, size.height * 0.3902881);
path_0.lineTo(size.width * 0.02125964, size.height * 0.3624440);
path_0.lineTo(size.width * 0.04919147, size.height * 0.3438013);
path_0.lineTo(size.width * 0.08431610, size.height * 0.3354250);
path_0.lineTo(size.width * 0.1433352, size.height * 0.3320572);
path_0.lineTo(size.width * 0.1968190, size.height * 0.3464974);
path_0.lineTo(size.width * 0.2771828, size.height * 0.3903361);
path_0.lineTo(size.width * 0.3188096, size.height * 0.4118381);
path_0.lineTo(size.width * 0.3344808, size.height * 0.4280629);
path_0.lineTo(size.width * 0.3424385, size.height * 0.4426662);
path_0.lineTo(size.width * 0.3355751, size.height * 0.4748568);
path_0.lineTo(size.width * 0.3045834, size.height * 0.4914078);
path_0.lineTo(size.width * 0.2930027, size.height * 0.5031039);
path_0.lineTo(size.width * 0.2930027, size.height * 0.5199236);
path_0.lineTo(size.width * 0.3055609, size.height * 0.5349395);
path_0.lineTo(size.width * 0.3848516, size.height * 0.5768400);
path_0.lineTo(size.width * 0.4280402, size.height * 0.5960393);
path_0.lineTo(size.width * 0.4721532, size.height * 0.6004145);
path_0.lineTo(size.width * 0.4980876, size.height * 0.5913186);
path_0.lineTo(size.width * 0.9008627, size.height * 0.3799353);
path_0.lineTo(size.width * 0.9333100, size.height * 0.3623480);
path_0.lineTo(size.width * 0.9405559, size.height * 0.3441083);
path_0.lineTo(size.width * 0.9300695, size.height * 0.3320572);
path_0.lineTo(size.width * 0.8011411, size.height * 0.2642315);
path_0.lineTo(size.width * 0.7637322, size.height * 0.2617081);
path_0.lineTo(size.width * 0.7242409, size.height * 0.2749969);
path_0.lineTo(size.width * 0.6447908, size.height * 0.3163985);
path_0.lineTo(size.width * 0.6063088, size.height * 0.3347917);
path_0.lineTo(size.width * 0.5646183, size.height * 0.3454132);
path_0.lineTo(size.width * 0.5051741, size.height * 0.3411435);
path_0.lineTo(size.width * 0.4420539, size.height * 0.3163985);
path_0.lineTo(size.width * 0.04462294, size.height * 0.1069150);
path_0.lineTo(size.width * 0.02193961, size.height * 0.08686183);
path_0.lineTo(size.width * 0.01177196, size.height * 0.06529269);
path_0.lineTo(size.width * 0.02783621, size.height * 0.03075137);
path_0.lineTo(size.width * 0.06909118, size.height * 0.01136985);
path_0.lineTo(size.width * 0.1378318, size.height * 0.003444537);
path_0.lineTo(size.width * 0.1783961, size.height * 0.01136985);
path_0.lineTo(size.width * 0.3515544, size.height * 0.09681164);
path_0.lineTo(size.width * 0.4009477, size.height * 0.1242528);
path_0.lineTo(size.width * 0.4404709, size.height * 0.1401994);
path_0.lineTo(size.width * 0.4821083, size.height * 0.1407367);
path_0.lineTo(size.width * 0.5315229, size.height * 0.1189277);
path_0.lineTo(size.width * 0.6837296, size.height * 0.03577905);
path_0.lineTo(size.width * 0.7055524, size.height * 0.02407338);
path_0.lineTo(size.width * 0.7180468, size.height * 0.02709575);
path_0.close();
Paint paint_0_fill = Paint()..style = PaintingStyle.fill;
paint_0_fill.color = Color(0xffffc629).withOpacity(1.0);
canvas.drawPath(path_0, paint_0_fill);
// Set your colors and spacing here
final Color defaultColor = Colors.blue;
final Color stepCompletedColor = Colors.blue.shade100;
final double spacing = 20.0; // Adjust spacing based on your requirement
// Use the drawOnPath method
canvas.drawOnPath(
path_0,
spacing: spacing,
steps: stepNumber,
defaultColor: defaultColor,
stepCompletedColor: stepCompletedColor,
);
}
void drawCircle(Canvas canvas, Offset position, Paint paint) {
canvas.drawCircle(position, 10.0, paint); // Circle radius of 10
}
// Simplified heart drawing function, you'll want to replace this with your actual heart drawing logic
void drawHeart(Canvas canvas, Offset position, Paint paint) {
Path heartPath = Path();
// This is a placeholder, implement your heart drawing logic here
heartPath.addOval(Rect.fromCircle(center: position, radius: 10));
canvas.drawPath(heartPath, paint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}
my code depends on a steps that is coming from the database as this form :
"steps": [
{
"id": 1,
"scholarship_type_id": 1,
"sort": 1,
"name": "Start",
"name_ar": "البداية",
"amount": 0,
"created_at": "2023-12-18T15:35:29.000000Z",
"updated_at": "2023-12-18T15:45:56.000000Z",
"deleted_at": null
},
{
"id": 2,
"scholarship_type_id": 1,
"sort": 2,
"name": "Seat",
"name_ar": "مقعد الدراسة",
"amount": 100,
"created_at": "2023-12-18T15:35:56.000000Z",
"updated_at": "2023-12-18T15:45:56.000000Z",
"deleted_at": null
}
]
what iam looking to is that : every step has its own heart shape and i want this heart to be in another color if he didnot reach the step yet , how should i do this ?
and also a simple question if i have too many paths how to handle it , and can i use a normal svg and place my widgets dynamically on it ?
Let's call the circles as the steps
and the heart-shaped positions as the milestones
.
To find out what the problem is, and if you are doing some parts on purpose or not, let’s walk through the code.
The first problem is the loop. To view what is the problem, we can remove the position normalizing and use tangent.position
instead. The result is:
I think the solution is obvious, change the while
loop:
double maxDistance = metric.length / 2;
while (distance < maxDistance){
// Your codes here
}
And the result is:
Now we have fixed the distance
, lets put back the normalizing and see how it works:
The next, in the while loop, you are normalizing the position, I think there are some minor problem in here: The road width, and overlapping steps
(caused by positions and vectors).
I mentioned that because I guess that causes a problem:
steps
are near the road edges instead of being exactly in the middle.steps
are on the same spot as the previous one (like the first few steps
).But how to fix it? Can I cheat?
Instead of creating the full path, can I create one side of the road? Or is the list should be on this exact positions?
Let us cheat a little bit. Remove the closing part of the path
, so in your road path list, comment out the lines where creating the right edge:
/*
path_0.lineTo(size.width * 0.1673254, size.height * 0.8428274);
path_0.lineTo(size.width * 0.2722318, size.height * 0.9009815);
.
.
.
path_0.lineTo(size.width * 0.7180468, size.height * 0.02709575);
path_0.close();
*/
Now, instead of using this path
as the edge of the road, lets use it as the middle of the road. This part of the code should look like this:
// Paint paint0Fill = Paint()..style = PaintingStyle.fill; //Removed this line
Paint paint0Fill = Paint(); // added this line
paint0Fill.strokeWidth = 20; // and added this line
paint0Fill.color = const Color(0xffffc629).withOpacity(1.0);
canvas.drawPath(path_0, paint0Fill);
Since you removed the other side of the road in path, now you can use the full distance
:
double maxDistance = metric.length;
while (distance < maxDistance){
// Your codes here
}
It's better, I think. But I don't like the first step. I prefer to remove it (but it is your choice).
I removed the first moveTo
and started the moveTo
from the second element.
// path_0.moveTo(size.width * 0.7180468, size.height * 0.02709575);
// path_0.lineTo(size.width * 0.7585899, size.height * 0.04322462);
path_0.moveTo(size.width * 0.7585899, size.height * 0.04322462);
Let's go back to the while loop. Your code uses:
paint.color = shapeCounter < steps * 5 ? defaultColor : stepCompletedColor;
But why are you multiplying the steps
? I think it is more calculatable if you don't do that and use it like:
paint.color = shapeCounter < steps ? defaultColor : stepCompletedColor;
You have to take more steps
and of course it is your choice.
So far, the drawOnPath
looks like this:
void drawOnPath(
Path path, {
required double spacing,
required int steps,
required Color defaultColor,
required Color stepCompletedColor,
double offsetFromPath =
10.0,
}) {
final paint = Paint()..style = PaintingStyle.fill;
final pathMetrics = path.computeMetrics();
double distance = 0.0;
int shapeCounter = 0;
for (final metric in pathMetrics) {
double maxDistance = metric.length;
while (distance < maxDistance) {
final tangent = metric.getTangentForOffset(distance);
if (tangent == null) continue;
paint.color = shapeCounter < steps ? defaultColor : stepCompletedColor;
drawCircle(this, tangent.position, paint, radius: 5);
distance += spacing;
shapeCounter++;
}
distance = 0.0;
}
}
And the paint
is:
@override
void paint(Canvas canvas, Size size) {
Path path_0 = Path();
path_0.moveTo(size.width * 0.7585899, size.height * 0.04322462);
path_0.lineTo(size.width * 0.5463654, size.height * 0.1534690);
path_0.lineTo(size.width * 0.5066828, size.height * 0.1674390);
path_0.lineTo(size.width * 0.4452413, size.height * 0.1719870);
path_0.lineTo(size.width * 0.3967298, size.height * 0.1617205);
path_0.lineTo(size.width * 0.2607467, size.height * 0.09137139);
path_0.lineTo(size.width * 0.1801492, size.height * 0.04966274);
path_0.lineTo(size.width * 0.1535241, size.height * 0.03829289);
path_0.lineTo(size.width * 0.1110580, size.height * 0.03719908);
path_0.lineTo(size.width * 0.07781390, size.height * 0.04915422);
path_0.lineTo(size.width * 0.06558509, size.height * 0.06854533);
path_0.lineTo(size.width * 0.08100125, size.height * 0.09115071);
path_0.lineTo(size.width * 0.3775313, size.height * 0.2422786);
path_0.lineTo(size.width * 0.5260088, size.height * 0.3199390);
path_0.lineTo(size.width * 0.5832749, size.height * 0.3077632);
path_0.lineTo(size.width * 0.6205457, size.height * 0.2884680);
path_0.lineTo(size.width * 0.7098659, size.height * 0.2422786);
path_0.lineTo(size.width * 0.7729755, size.height * 0.2284525);
path_0.lineTo(size.width * 0.8304328, size.height * 0.2393905);
path_0.lineTo(size.width * 0.8658337, size.height * 0.2554810);
path_0.lineTo(size.width * 0.9350524, size.height * 0.2889861);
path_0.lineTo(size.width * 0.9755636, size.height * 0.3107759);
path_0.lineTo(size.width * 0.9982788, size.height * 0.3450582);
path_0.lineTo(size.width * 0.9873037, size.height * 0.3732573);
path_0.lineTo(size.width * 0.9598287, size.height * 0.3941836);
path_0.lineTo(size.width * 0.9035826, size.height * 0.4244648);
path_0.lineTo(size.width * 0.7605979, size.height * 0.4929238);
path_0.lineTo(size.width * 0.6050551, size.height * 0.5751610);
path_0.lineTo(size.width * 0.5392469, size.height * 0.6110743);
path_0.lineTo(size.width * 0.4607637, size.height * 0.6302256);
path_0.lineTo(size.width * 0.3966235, size.height * 0.6215039);
path_0.lineTo(size.width * 0.3524150, size.height * 0.6004145);
path_0.lineTo(size.width * 0.2721787, size.height * 0.5561536);
path_0.lineTo(size.width * 0.2467648, size.height * 0.5403798);
path_0.lineTo(size.width * 0.2365122, size.height * 0.5156923);
path_0.lineTo(size.width * 0.2399545, size.height * 0.4979611);
path_0.lineTo(size.width * 0.2555832, size.height * 0.4740029);
path_0.lineTo(size.width * 0.2765772, size.height * 0.4647535);
path_0.lineTo(size.width * 0.2883704, size.height * 0.4458325);
path_0.lineTo(size.width * 0.2851406, size.height * 0.4353166);
path_0.lineTo(size.width * 0.2691613, size.height * 0.4266237);
path_0.lineTo(size.width * 0.1527698, size.height * 0.3681721);
path_0.lineTo(size.width * 0.1179639, size.height * 0.3632883);
path_0.lineTo(size.width * 0.09614118, size.height * 0.3655431);
path_0.lineTo(size.width * 0.07278851, size.height * 0.3788031);
path_0.lineTo(size.width * 0.06388517, size.height * 0.3943659);
path_0.lineTo(size.width * 0.07820701, size.height * 0.4161270);
path_0.lineTo(size.width * 0.1041414, size.height * 0.4326012);
path_0.lineTo(size.width * 0.1138416, size.height * 0.4567322);
path_0.lineTo(size.width * 0.1047683, size.height * 0.4849218);
path_0.lineTo(size.width * 0.08567604, size.height * 0.4976637);
path_0.lineTo(size.width * 0.06644568, size.height * 0.5117584);
path_0.lineTo(size.width * 0.06388517, size.height * 0.5238287);
path_0.lineTo(size.width * 0.07097172, size.height * 0.5405237);
path_0.lineTo(size.width * 0.3899620, size.height * 0.7089222);
path_0.lineTo(size.width * 0.5029536, size.height * 0.7667981);
path_0.lineTo(size.width * 0.5419349, size.height * 0.7749921);
path_0.lineTo(size.width * 0.5645120, size.height * 0.7719985);
path_0.lineTo(size.width * 0.6117592, size.height * 0.7522140);
path_0.lineTo(size.width * 0.6923780, size.height * 0.7080011);
path_0.lineTo(size.width * 0.7431206, size.height * 0.6833137);
path_0.lineTo(size.width * 0.8080151, size.height * 0.6485037);
path_0.lineTo(size.width * 0.8541361, size.height * 0.6376711);
path_0.lineTo(size.width * 0.8930962, size.height * 0.6366445);
path_0.lineTo(size.width * 0.9610293, size.height * 0.6549898);
path_0.lineTo(size.width * 0.9933278, size.height * 0.6862209);
path_0.lineTo(size.width * 0.9846051, size.height * 0.7166077);
path_0.lineTo(size.width * 0.9506279, size.height * 0.7450563);
path_0.lineTo(size.width * 0.8039672, size.height * 0.8161442);
path_0.lineTo(size.width * 0.7521408, size.height * 0.8452837);
path_0.lineTo(size.width * 0.7301375, size.height * 0.8622089);
path_0.lineTo(size.width * 0.7290750, size.height * 0.8811587);
path_0.lineTo(size.width * 0.7547226, size.height * 0.8975274);
path_0.lineTo(size.width * 0.7695332, size.height * 0.9127160);
path_0.lineTo(size.width * 0.7793927, size.height * 0.9243449);
path_0.lineTo(size.width * 0.7789465, size.height * 0.9451848);
path_0.lineTo(size.width * 0.7551476, size.height * 0.9706687);
path_0.lineTo(size.width * 0.7082404, size.height * 0.9895033);
path_0.lineTo(size.width * 0.6569877, size.height * 0.9948572);
path_0.lineTo(size.width * 0.6282378, size.height * 0.9920267);
path_0.lineTo(size.width * 0.5725016, size.height * 0.9744298);
path_0.lineTo(size.width * 0.5310873, size.height * 0.9512775);
path_0.lineTo(size.width * 0.4816515, size.height * 0.9247095);
path_0.lineTo(size.width * 0.4392491, size.height * 0.9106531);
path_0.lineTo(size.width * 0.3966235, size.height * 0.9176190);
path_0.lineTo(size.width * 0.3565054, size.height * 0.9344099);
path_0.lineTo(size.width * 0.3246850, size.height * 0.9391881);
path_0.lineTo(size.width * 0.2660696, size.height * 0.9344099);
path_0.lineTo(size.width * 0.2456280, size.height * 0.9260816);
path_0.lineTo(size.width * 0.1933873, size.height * 0.9024783);
path_0.lineTo(size.width * 0.1445146, size.height * 0.8758240);
path_0.lineTo(size.width * 0.1218737, size.height * 0.8622089);
Paint paint0Fill = Paint();
paint0Fill.strokeWidth = 20;
paint0Fill.color = const Color(0xffffc629).withOpacity(1.0);
canvas.drawPath(path_0, paint0Fill);
const Color defaultColor = Colors.blue;
final Color stepCompletedColor = Colors.blue.shade100;
const double spacing = 20.0;
canvas.drawOnPath(
path_0,
spacing: spacing,
steps: stepNumber,
defaultColor: defaultColor,
stepCompletedColor: stepCompletedColor,
);
}
Last part is drawing milestones
. You have too many options, but the problem is you didn't mentioned where you want to put those!
Suppose you want to add milestones
every 10 steps. Now, you can add a condition to check if it is the place you want to put the heart shape or not.
Define a list List milestones = [];
before your for loop
and then, in the while loop
add:
if (shapeCounter % 10 == 0) {
milestones.add({
'position': tangent.position,
'color': paint.color,
'paint': paint
});
}
After the while loop
finished:
for (var milestone in milestones) {
drawHeart(this, milestone['position'], milestone['color'], milestone['paint']);
}
Because when you draw next element, it shows on top previous elements, and it leads to having parts of the circles on top of the hearts. We don't want that, right? So, a quick solution is drawing all the circles and after that, drawing all the hearts.
Drawing the heart-shape
is like:
void drawHeart(Canvas canvas, Offset position, Color color, Paint paint) {
Paint paint = Paint();
paint.color = color;
double width = 30;
double height = 30;
position -= Offset(0.7 * width, 0.7 * height);
Path path = Path();
path.moveTo(position.dx + 0.5 * width, position.dy + height * 0.35);
path.cubicTo(
position.dx + 0.2 * width,
position.dy + height * 0.1,
position.dx + -0.25 * width,
position.dy + height * 0.6,
position.dx + 0.5 * width,
position.dy + height);
path.moveTo(position.dx + 0.5 * width, position.dy + height * 0.35);
path.cubicTo(
position.dx + 0.8 * width,
position.dy + height * 0.1,
position.dx + 1.25 * width,
position.dy + height * 0.6,
position.dx + 0.5 * width,
position.dy + height);
canvas.drawPath(path, paint);
}
The final code looks like this:
void drawOnPath(
Path path, {
required double spacing,
required int steps,
required Color defaultColor,
required Color stepCompletedColor,
double offsetFromPath = 10.0,
}) {
final paint = Paint()..style = PaintingStyle.fill;
final pathMetrics = path.computeMetrics();
double distance = 0.0;
int shapeCounter = 0;
List milestones = [];
for (final metric in pathMetrics) {
double maxDistance = metric.length;
while (distance < maxDistance) {
final tangent = metric.getTangentForOffset(distance);
if (tangent == null) continue;
paint.color = shapeCounter < steps ? defaultColor : stepCompletedColor;
drawCircle(this, tangent.position, paint, radius: 5);
distance += spacing;
shapeCounter++;
if (shapeCounter % 10 == 0) {
milestones.add({
'position': tangent.position,
'color': paint.color,
'paint': paint
});
}
}
distance = 0.0;
for (var milestone in milestones) {
drawHeart(
this, milestone['position'], milestone['color'], milestone['paint']);
}
}
}
And the result is:
The last point doesn't match the (shapeCounter % 10 == 0)
condition but if you want, you can add it by adding the last position to the list. :)
The final part, if you want to have different steps
number in each milestone
, you must change the condition where you put the heart-shapes
.
Suppose you have a list like List<int> stepsInMilestones = [5, 12, 4, 14, 9, 4, 5, 12, 7, 14, 9, 4];
which defines the counts of steps
in milestones
.
First change the class:
class RPSCustomPainter1 extends CustomPainter {
final int stepNumber;
final List<int> stepsInMilestones; // Added this and update the class
RPSCustomPainter1(
{required this.stepNumber, required this.stepsInMilestones});
// Your old codes here
canvas.drawOnPath(path_0,
spacing: spacing,
steps: stepNumber,
defaultColor: defaultColor,
stepCompletedColor: stepCompletedColor,
stepsInMilestones: stepsInMilestones, // Added this line
);
}
Pass this to RPSCustomPainter1
like:
RPSCustomPainter1(stepNumber: 30, stepsInMilestones: stepsInMilestones)
And update the drawOnPath
:
void drawOnPath(Path path,
{required double spacing,
required int steps,
required Color defaultColor,
required Color stepCompletedColor,
double offsetFromPath = 10.0,
required List<int> stepsInMilestones, // Added this line
}) {
// Your code here
}
Now, You must change the condition, and check if it reached your desired step
for that particular milestone
instead of checking the 10th steps
for every milestones
.
First, add these two variables before the for loop
:
int milestoneCounter = 0;
int totalSteps = stepsInMilestones[milestoneCounter];
Remove the previous condition from the loop:
// if (shapeCounter % 10 == 0) {
// milestones.add({
// 'position': tangent.position,
// 'color': paint.color,
// 'paint': paint
// });
// }
And add the new one:
if (shapeCounter == totalSteps) {
// Add this milestone position
milestones.add({
'position': tangent.position,
'color': paint.color,
'paint': paint
});
milestoneCounter += 1; // Move to the next milestone
if (milestoneCounter >= stepsInMilestones.length - 1) {
// Added this condition to prevent index out of range error.
// I added steps randomly and there is a chance that
// I needed to put more values in stepsInMilestones.
milestoneCounter = stepsInMilestones.length - 1;
}
totalSteps += stepsInMilestones[
milestoneCounter]; // This is the next milestone step
}
The final drawOnPath
is:
void drawOnPath(Path path,
{required double spacing,
required int steps,
required Color defaultColor,
required Color stepCompletedColor,
double offsetFromPath = 10.0,
required List<int> stepsInMilestones}) {
final paint = Paint()..style = PaintingStyle.fill;
final pathMetrics = path.computeMetrics();
double distance = 0.0;
int shapeCounter = 0;
List milestones = [];
int milestoneCounter = 0;
int totalSteps = stepsInMilestones[milestoneCounter];
for (final metric in pathMetrics) {
double maxDistance = metric.length;
while (distance < maxDistance) {
final tangent = metric.getTangentForOffset(distance);
if (tangent == null) continue;
paint.color = shapeCounter < steps ? defaultColor : stepCompletedColor;
drawCircle(this, tangent.position, paint, radius: 5);
distance += spacing;
shapeCounter++;
if (shapeCounter == totalSteps) {
milestones.add({
'position': tangent.position,
'color': paint.color,
'paint': paint
});
milestoneCounter += 1; // Move to the next milestone
if (milestoneCounter >= stepsInMilestones.length - 1) {
// Added this condition to prevent index out of range error.
// I added steps randomly and there is a chance that
// I needed to put more values in stepsInMilestones.
milestoneCounter = stepsInMilestones.length - 1;
}
totalSteps += stepsInMilestones[
milestoneCounter]; // This is the next milestone step
}
}
distance = 0.0;
for (var milestone in milestones) {
drawHeart(
this, milestone['position'], milestone['color'], milestone['paint']);
}
}
}
And the result is: