Using Paths and Custom Strokes in Flutter

Flutter provides a rich set of tools for drawing custom shapes and graphics directly onto the screen. Using Path objects, combined with custom strokes, you can create intricate designs, enhance UI elements, and build unique visual experiences within your Flutter applications.

What is a Path in Flutter?

In Flutter, a Path represents a geometric path composed of lines, curves, and other shapes. Paths can be stroked (drawn as lines) or filled (filled with color). You define the shape of the path by chaining various methods like moveTo, lineTo, quadraticBezierTo, cubicTo, and addArc to construct the desired form.

What are Custom Strokes?

A stroke defines how the outline of a path should be drawn. Custom strokes allow you to control the visual characteristics of the path’s outline, such as color, width, line style (dashed, dotted), and line caps (the shape at the ends of lines).

Why Use Paths and Custom Strokes?

  • Custom UI Elements: Create unique buttons, indicators, and other UI components.
  • Data Visualization: Draw graphs, charts, and other data visualizations with precision.
  • Artistic Designs: Implement complex artistic effects, backgrounds, and animations.

How to Use Paths and Custom Strokes in Flutter

Let’s walk through creating paths and applying custom strokes using Flutter.

Step 1: Creating a Custom Painter

To draw paths, you’ll need to create a custom painter. Custom painters are widgets that define how to paint on a canvas. You extend the CustomPainter class and override the paint method.


import 'package:flutter/material.dart';

class PathPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    // Path drawing logic goes here
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return false;
  }
}

Step 2: Drawing a Path

Inside the paint method, create a Path object and define its shape using various path commands.


  @override
  void paint(Canvas canvas, Size size) {
    final path = Path();
    path.moveTo(size.width * 0.1, size.height * 0.5);
    path.quadraticBezierTo(
      size.width * 0.25, size.height * 0.1,
      size.width * 0.5, size.height * 0.5,
    );
    path.quadraticBezierTo(
      size.width * 0.75, size.height * 0.9,
      size.width * 0.9, size.height * 0.5,
    );
  }

Step 3: Applying Custom Strokes

Create a Paint object and configure its properties to define the custom stroke. Set properties like color, stroke width, stroke cap, and stroke join.


  @override
  void paint(Canvas canvas, Size size) {
    final path = Path();
    path.moveTo(size.width * 0.1, size.height * 0.5);
    path.quadraticBezierTo(
      size.width * 0.25, size.height * 0.1,
      size.width * 0.5, size.height * 0.5,
    );
    path.quadraticBezierTo(
      size.width * 0.75, size.height * 0.9,
      size.width * 0.9, size.height * 0.5,
    );

    final paint = Paint()
      ..color = Colors.blue
      ..style = PaintingStyle.stroke
      ..strokeWidth = 5
      ..strokeCap = StrokeCap.round; // Optional
    
    canvas.drawPath(path, paint);
  }

Step 4: Using the Custom Painter in a Widget

Use the CustomPaint widget to display your custom drawing. Provide your PathPainter as the painter for the CustomPaint widget.


import 'package:flutter/material.dart';

class PathPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final path = Path();
    path.moveTo(size.width * 0.1, size.height * 0.5);
    path.quadraticBezierTo(
      size.width * 0.25, size.height * 0.1,
      size.width * 0.5, size.height * 0.5,
    );
    path.quadraticBezierTo(
      size.width * 0.75, size.height * 0.9,
      size.width * 0.9, size.height * 0.5,
    );

    final paint = Paint()
      ..color = Colors.blue
      ..style = PaintingStyle.stroke
      ..strokeWidth = 5
      ..strokeCap = StrokeCap.round;

    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return false;
  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Custom Path with Stroke'),
        ),
        body: Center(
          child: CustomPaint(
            size: const Size(300, 200),
            painter: PathPainter(),
          ),
        ),
      ),
    );
  }
}
void main() {
  runApp(MyApp());
}

Advanced Customization

Dashed Strokes

Flutter doesn’t provide a direct way to create dashed strokes out-of-the-box. You need to implement a custom solution, like using the PathDash class or manually segmenting the path.


import 'package:flutter/material.dart';

class DashedPathPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final path = Path();
    path.moveTo(size.width * 0.1, size.height * 0.5);
    path.quadraticBezierTo(
      size.width * 0.25, size.height * 0.1,
      size.width * 0.5, size.height * 0.5,
    );
    path.quadraticBezierTo(
      size.width * 0.75, size.height * 0.9,
      size.width * 0.9, size.height * 0.5,
    );

    final paint = Paint()
      ..color = Colors.blue
      ..style = PaintingStyle.stroke
      ..strokeWidth = 5
      ..strokeCap = StrokeCap.round;

    // Implement dashed line manually
    const dashWidth = 10;
    const dashSpace = 5;
    double distance = 0;

    for (PathMetric pathMetric in path.computeMetrics()) {
      while (distance < pathMetric.length) {
        canvas.drawPath(
          pathMetric.extractPath(distance, distance + dashWidth),
          paint,
        );
        distance += dashWidth + dashSpace;
      }
    }
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return false;
  }
}
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Custom Path with Stroke'),
        ),
        body: Center(
          child: CustomPaint(
            size: const Size(300, 200),
            painter: DashedPathPainter(),
          ),
        ),
      ),
    );
  }
}
void main() {
  runApp(MyApp());
}

Filling Paths

In addition to stroking, you can fill paths with a color or a gradient. Set the style property of the Paint object to PaintingStyle.fill and provide a color or shader.


import 'package:flutter/material.dart';

class FilledPathPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final path = Path();
    path.moveTo(size.width * 0.1, size.height * 0.5);
    path.quadraticBezierTo(
      size.width * 0.25, size.height * 0.1,
      size.width * 0.5, size.height * 0.5,
    );
    path.quadraticBezierTo(
      size.width * 0.75, size.height * 0.9,
      size.width * 0.9, size.height * 0.5,
    );

    final paint = Paint()
      ..color = Colors.green
      ..style = PaintingStyle.fill;

    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return false;
  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Custom Path with Stroke'),
        ),
        body: Center(
          child: CustomPaint(
            size: const Size(300, 200),
            painter: FilledPathPainter(),
          ),
        ),
      ),
    );
  }
}
void main() {
  runApp(MyApp());
}

Conclusion

Using paths and custom strokes in Flutter offers a powerful way to create custom UIs, data visualizations, and artistic designs. Whether you need a simple custom shape or a complex dashed line, Flutter's Path and Paint classes provide the flexibility you need. By mastering these techniques, you can enhance your Flutter applications with unique and engaging visuals.