Guide to drawing path using CustomPaint in Flutter
by Cloudcraftz | Jun 15, 2023 | Flutter UI

This guide walks you through creating scrollable dashed paths using Flutter's CustomPaint widget — a common need when building level-select screens or journey maps.
Step 1: Creating a GridView using GridView.builder()
Create a class LevelsScreen that extends StatefulWidget. In the build method, return Scaffold and add a GridView.builder() inside the body. Create a list for the grid items:
List<String> levels = [
'Start',
'Level-1',
'Level-2',
'Level-3',
'Level-4',
'Level-5',
];
Set itemCount to the list's length. The itemBuilder returns circular containers displaying text from the list, alternating left/right alignment:
GridView.builder(
itemCount: levels.length,
reverse: true,
shrinkWrap: true,
physics: const BouncingScrollPhysics(),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 1,
childAspectRatio: 2,
),
itemBuilder: (BuildContext context, int index) {
return Row(
mainAxisAlignment: (index % 2 == 0)
? MainAxisAlignment.start
: MainAxisAlignment.end,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 25),
child: Container(
height: 125,
width: 125,
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.circular(62.5),
),
child: Center(
child: Container(
height: 110,
width: 110,
decoration: BoxDecoration(
color: (index == 0) ? Colors.green : Colors.amberAccent,
borderRadius: BorderRadius.circular(55),
),
child: Center(
child: Text(
levels[index],
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
),
),
),
),
),
],
);
},
),
Step 2: Create a DashLinePainter class
Create a class DashLinePainter extending CustomPainter. Define Paint with color, strokeWidth, and style, then build the Path in the paint method:
class DashLinePainter extends CustomPainter {
final Paint _paint = Paint()
..color = Colors.black
..strokeWidth = 4.0
..style = PaintingStyle.stroke
..strokeJoin = StrokeJoin.round;
@override
void paint(Canvas canvas, Size size) {
var path = Path()
..moveTo(size.width / 4.5, size.height / 1.09)
..lineTo(size.width / 4.5, size.height / 1.28)
..arcToPoint(
Offset(size.width / 3.2, size.height / 1.33),
radius: const Radius.circular(50),
clockwise: true,
)
..lineTo(size.width / 1.3, size.height / 1.33)
..lineTo(size.width / 1.3, size.height / 1.62)
..arcToPoint(
Offset(size.width / 1.5, size.height / 1.72),
radius: const Radius.circular(50),
clockwise: false,
)
..lineTo(size.width / 4.5, size.height / 1.72);
// … (continue for full path)
// Convert solid path to dashes
Path dashPath = Path();
double dashWidth = 10.0;
double dashSpace = 5.0;
double distance = 0.0;
for (PathMetric pathMetric in path.computeMetrics()) {
while (distance < pathMetric.length) {
dashPath.addPath(
pathMetric.extractPath(distance, distance + dashWidth),
Offset.zero,
);
distance += dashWidth;
distance += dashSpace;
}
}
canvas.drawPath(dashPath, _paint);
}
@override
bool shouldRepaint(DashLinePainter oldDelegate) => true;
}
Step 3: Wrap GridView.builder() with CustomPaint
To show the path between grid children, wrap GridView.builder with CustomPaint and pass the painter:
CustomPaint(
painter: DashLinePainter(),
child: GridView.builder(/* ... */),
)
Step 4: Make the path scrollable
Wrap CustomPaint with SingleChildScrollView so the path scrolls along with the grid:
SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: CustomPaint(
painter: DashLinePainter(),
child: GridView.builder(/* ... */),
),
),
Conclusion
This pattern demonstrates how to use CustomPaint alongside GridView to draw scrollable dashed paths. The key insight is that CustomPaint must be the parent of the scrollable content — not the other way around — so the Canvas receives the full content size and can draw across the entire scrollable area.