How to draw an arc with Flutter CustomPainter.

To draw an arc we need to use drawArc function inside the paint method. Define the rectangle, start angle, sweepAngle, useCenter, and the Paint object as the parameters.

There are a lot of shapes we can draw using the Flutter CustomPainter. One of them is an arc. This article will show and explain how we can use CustomPainter to draw a simple arc. Let's check it out!

Preparation

Before we begin, let's create a new Flutter project:

flutter create my_simple_arc

Now, let's update the main.dart to be like this:

import 'package:flutter/material.dart';
import 'my_simple_arc.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        appBar: AppBar(
          title: const Text('My Simple Arc'),
        ),
        body: MySimpleArc(),
      ),
    );
  }
}
main.dart

Notice that we assign a widget called MySimpleArc to the body of our scaffold. Let's create that widget now.

Creating the CustomPaint Widget

In the same directory with the main.dart let's create a new file my_simple_arc.dart :

import 'package:flutter/material.dart';

class MySimpleArc extends StatelessWidget {
  const MySimpleArc({super.key});

  @override
  Widget build(BuildContext context) {
    return CustomPaint(
      size: Size(500, 500),
      painter: MySimplePainter(),
    );
  }
}

class MySimplePainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    // TODO: implement paint
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true;
  }
}
my_simple_arc.dart

In the above file, we create two classes MySimpleArc and MySimplePainter . The class MySimpleArc is where we build the CustomPaint object. The CustomPaint widget accepts six parameters, but, in this case, we set only the size and painter parameters for the widget.

Painting the arc to the canvas

To draw an arc, we need to define the rectangle, start angle, sweep angle, useCenter, and the Paint object.

To draw it into the canvas, we need to write the code inside the paint method. Just like painting on a real canvas, the first thing we need to do is to choose our paintbrush. We can define our paint like so:

final strokeWidth = size.width / 30.0;
    
final paint = Paint()
    ..color = Colors.black12
    ..strokeWidth = strokeWidth
    ..style = PaintingStyle.stroke
    ..strokeCap = StrokeCap.round;
my_simple_arc.dart

In the code above, we define the stroke width by dividing the width of the canvas, which we defined in the CustomPaint widget with 30.0 (you can change this to any number) just to ensure that the stroke width is proportionally set relative to the canvas width.

We also defined the color of the paint, the stroke width, style, and strokeCap. Let's discuss the style and stroke cap later.  For now, let's just draw the arc:

Offset circleCenter = Offset(size.width / 2, size.height / 2);
double circleRadius = (size.width - strokeWidth) / 3;
Rect rectangle =
Rect.fromCircle(center: circleCenter, radius: circleRadius);

double startAngle = 0;
double sweepAngle = pi;
bool useCenter = false;

canvas.drawArc(rectangle, startAngle, sweepAngle, useCenter, paint);
my_simple_arc.dart

As you can see in the code above, to draw an arc, we need to first define a rectangle. The rectangle is used to define the bounding within which the arc is drawn. The Rect specifies the size and position of the oval that contains the arc. As you can see in the above code, the Rect object has two parameters, the center, and the radius.

Defining the circle center

To define the circle center, we simply create an offset. For the horizontal coordinate, we use half of the canvas width, and for the vertical coordinate, we use half of the canvas height. Thus, the circle center will be positioned in the center of the canvas.

Offset circleCenter = Offset(size.width / 2, size.height / 2)

Defining the circle radius

For the circle radius, we set it as (size.width - strokeWidth) / 3 ensuring it is proportionally adjusted to the canvas width minus the stroke width. If you want it to adjust the canvas height instead, you can use (size.height - strokeWidth) / 3 or another calculation.

Defining circle radius

Defining the start angle

The start angle is the starting point of the arc. Before we discuss how startAngle works, Let's see how our arc looks on the simulator.

Flutter simple arc with CustomPainter

Since the startAngle value is set to 0, the start angle would be on the topmost right of the circle (3 o'clock position). Positive angles are measured in the clockwise direction from the rightmost point. If you set the startAngle bigger than 0, for example, you set it to pi / 2,  the start angle will move towards the clockwise direction like so:

double startAngle = pi / 2;
startAngle moves in the clockwise direction

The use of π (pi) here is because the angles for drawing arcs in Flutter's Canvas API is measured in radians. Radians are a unit of measurement for angles, and π is a mathematical constant that represents the ratio of the circumference of a circle to its diameter. To use the pi constant in our code, we need to make sure we imported the math package.

import 'dart:math';

Since π represents half the circumference of a circle (180 degrees), the value pi / 2 that we assign to the startAngle variable represents 90 degrees. This means we move the startAngle 90 degrees towards the clockwise direction from the topmost right of the circle (3 o'clock position). Now, the start angle is on the topmost bottom of the circle.

Now, what if we set the startAngle to a negative value? instead of moving the start angle 90 degrees in the clockwise direction, the negative value will move it counter-clockwise.

double startAngle = -pi / 2;
start angle moves the counter-clockwise direction

Now, the start angle is at the top.

Defining the sweep angle

The sweep angle works the same way as the start angle, except it defines the ending point of the arc. In the example above, we assign pi to the sweepAngle variable.

double sweepAngle = pi;

As mentioned before, π is a radian representation of half the circumference of a circle (180 degrees). Thus, the sweep angle is similar to the 180-degree clockwise direction from the start angle.

double startAngle = 0;
double sweepAngle = pi;

Similar to the start angle, assigning a negative value to the sweep angle makes the direction to be counter-clockwise.

double startAngle = 0;
double sweepAngle = -pi;

Defining the useCenter parameter.

The useCenter parameter in Flutter's drawArc method determines whether the arc should be connected to the center of the circle or not.

In the example above, we set it to false. So, the arc is drawn without connecting it to the center of the circle. This means that only the curved part of the arc will be drawn, and the endpoint will be the end of the arc.

When useCenter is set to true, the arc is drawn from the starting point to the ending point, and then a line is drawn from the ending point back to the center of the circle. This creates a closed shape, resembling a slice of pie.

bool useCenter = true;

Using a different painting style

In the example above, we use PaintingStyle.stroke as the style parameter of the Paint object. The style only draws the outline of the shape or path using the specified stroke width and color. It does not fill the interior. There are two more possible values of the PaintingStyle enum, PaintingStyle.fill.

Let's see how it works:

..style = PaintingStyle.fill

As you can see, this style fills the interior of the shape or path with the color that we specify using the color parameter. It creates a solid, filled-in appearance.

Using different strokeCap

The last thing we want to try is using a different strokeCap. The "stroke cap" refers to the style applied to the endpoints of a stroke (outline) when drawing a shape or path with a Paint object in Flutter. There are three types of stroke caps.

  1. StrokeCap.round: This style adds a half-circle shape at each endpoint of the stroke. It creates rounded line endings.
  2. StrokeCap.square : This style extends the stroke beyond the endpoint by half of the stroke width and adds a square shape. It creates a squared-off appearance.
  3. StrokeCap.butt: Notice that this type of stroke cap has a similar appearance to a square stroke cap. Unlike a squared stroke cap, this cap has no extension. The square-like shape at the end of the line is the endpoint itself.
Different types of stroke cap

In the arc that we created, we always use StrokeCap.round as the value of the strokeCap parameter. Let's try to edit the stroke cap to square, make sure you use PaintingStyle.stoke along with StrokeCap.square.

..style = PaintingStyle.stroke
..strokeCap = StrokeCap.square;

Since, the StrokeCap.butt has a similar appearance to the square one. We will skip the example.

Conclusion

In this article, we learned how to create an arc, using Flutter custom painter. You should now understand how the drawArc function works, and what is start angle and sweep angle are. We also discussed the useCenter, PaintingStyle, and strokeCap parameters. Hopefully, this article will be useful for your Flutter development journey, thanks for reading!