Cloudcraftz
Back to Insights
Flutter UI

Guide to drawing path using CustomPaint in Flutter

by Cloudcraftz | Jun 15, 2023 | Flutter UI

Guide to drawing path using CustomPaint in Flutter

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.