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 ?
2
Answers
Let’s call the circles as the
steps
and the heart-shaped positions as themilestone
.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: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 fewsteps
).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: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:Since you removed the other side of the road in path, now you can use the full
distance
: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 themoveTo
from the second element.Let’s go back to the while loop. Your code uses:
But why are you multiplying the
steps
? I think it is more calculatable if you don’t do that and use it like:You have to take more
steps
and of course it is your choice.So far, the
drawOnPath
looks like this:And the
paint
is: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 yourfor loop
and then, in thewhile loop
add:After the
while loop
finished:Drawing the
heart-shape
is like:The final code looks like this:
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 eachmilestone
, you must change the condition where you put theheart-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 ofsteps
inmilestones
.First change the class:
Pass this to
RPSCustomPainter1
like:And update the
drawOnPath
:Now, You must change the condition, and check if it reached your desired
step
for that particularmilestone
instead of checking the 10thsteps
for everymilestones
.First, add these two variables before the
for loop
:Remove the previous condition from the loop:
And add the new one:
The final
drawOnPath
is:And the result is:
There is somewhat different approach which does not use CustomPainter.
This is the result
Here you break your svg into layers. And with help of xml parser, you draw the road path. Please make relevant adjustment.
The assets I used
path_dot.svg
path_favorite.svg
path.svg
I had to create a sample design in Figma as your svg was not available.
Inside the
favoritesSteps
array, you need to provide the steps between each favorites.