This article is part of day 3 of KINTO Technologies Advent Calendar 2024 .🎅🎄 I am Somi, a Flutter application developer at KINTO Technologies (hereinafter, KTC). Flutter is an appealing framework that enables you to construct a diverse range of UIs independently of the platform. In particular, with CustomPaint, you can easily produce intricate designs that would be difficult to achieve with the basic widgets alone. Recently, when implementing a QR code recognition screen, an issue arose with creating a border for the recognition area. We tried to use the existing libraries, but there was a limit to how well they could achieve the curve design we wanted. Therefore, we solved the problem by drawing the border directly using CustomPaint and Path . In this article, I will detail what steps we took to complete the border for the QR code recognition screen using CustomPaint and Path. The Goal of the Border Design The border we implemented this time is a curved, translucent white one around the four corners of the QR code recognition area. Using the CustomPainter class , we defined a path on the Canvas with Path, then drew the border using a combination of curves and straight lines. Preparations for Drawing the Border Using CustomPainter First, we define a class for drawing the border, namely, _OverlayPainter . This class extends CustomPainter, and will be responsible for drawing the border on the Canvas. The following is sample code for drawing a border using an already defined _OverlayPainter. Later, I will explain in detail about the specifics of implementing it. class QrScanPageContent extends StatelessWidget { const QrScanPageContent({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("QR Code Scanner"), // Screen title ), body: CustomPaint( size: Size.infinite, // Draw it to fit the size of the whole screen painter: _OverlayPainter( squareSize: 200.0, // Size of the border area borderRadius: 20.0, // Roundness of the border’s corners borderThickness: 8.0, // Border thickness ), ), ); } } Setting up the background and recognition area We will create in specific detail the _OverlayPainter I mentioned above. First, we draw the background color and the QR code recognition area. We draw the background as a semi-transparent rectangle using the drawRect method, and the QR code recognition area as a rounded rectangle using the drawRRect method. For drawing each, we set the style (color and transparency) using the Paint class. In the next section, I will explain how to draw the border in detail. class _OverlayPainter extends CustomPainter { final double squareSize; final double borderRadius; final double borderThickness; _OverlayPainter({ required this.squareSize, required this.borderRadius, required this.borderThickness, }); @override void paint(Canvas canvas, Size size) { final centerX = size.width / 2; final centerY = size.height / 2; // Draw the background final backgroundPaint = Paint()..color = Colors.grey.withOpacity(0.5); canvas.drawRect( Rect.fromLTWH(0, 0, size.width, size.height), backgroundPaint); // Draw the recognition area final rect = RRect.fromRectAndRadius( Rect.fromCenter( center: Offset(centerX, centerY), width: squareSize, height: squareSize, ), Radius.circular(borderRadius), ); final innerPaint = Paint()..color = Colors.lightBlue.withOpacity(0.1); canvas.drawRRect(rect, innerPaint); // Here, we set the frame’s style and draw the frame. } @override bool shouldRepaint(covariant CustomPainter oldDelegate) => false; } The shouldRepaint method decides whether this CustomPainter requires redrawing. In this example, the background color and the size of the recognition area are fixed, so redrawing is unnecessary. Consequently, this method always returns false. However, if you want to draw dynamically or change the size or shape, you need to set this method to true. Setting the border style Next, to get ready to draw the border, we set the line style. Before drawing the border, we define the style using a Paint object. The Paint class provides tools for setting things like the color, thickness, and shape of a line. Here, we set the border to translucent white and define the line shape to be rounded. By setting the border to a translucent white color, we have ensured that the important recognition area can be spotted at a glance. final borderPaint = Paint() ..color = Colors.white.withOpacity(0.5) // Set the border color and transparency ..style = PaintingStyle.stroke // Set the outer border style ..strokeWidth = borderThickness // Line thickness ..strokeCap = StrokeCap.round; // Set the ends of the line to be rounded Calculating the coordinates and sizes To draw the border, we first need to calculate the coordinates and size of each corner. This will enable us to define the start and end points of each corner precisely. The following is an example calculation: const double cornerLength = 55; // Length of each corner double halfSquareSize = squareSize / 2; // Size of half of the recognition area double left = centerX - halfSquareSize; // Left boundary double right = centerX + halfSquareSize; // Right boundary Double top = centerY - halfSquareSize; // Top boundary double bottom = centerY + halfSquareSize; // Bottom boundary Coordinate system : In Flutter’s Canvas coordinate system, the top left is (0, 0). This means that the top side is calculated using centerY - halfSquareSize , and the bottom side is defined as centerY + halfSquareSize . cornerLength : Define what length of straight line to draw at each corner. halfSquareSize : Calculate the size of half of the QR code recognition area. left, right, top, bottom : Define the boundary coordinates of the recognition area with respect to the coordinates of the center. Pictorially, the above formulas look like the following figure. Drawing the Border First, we start to draw from the top left corner. To draw the top left corner, we define a path using the Path class. Path is a handy class that lets you specify a variety of shapes like lines, curves, and arcs, then draw them on the Canvas. 1. Draw a straight line from right to left We move the start point to the top end of the corner, then draw a straight line going left. Path topLeftPath = Path(); // Define a new path topLeftPath.moveTo(left + cornerLength, top); topLeftPath.lineTo(left + borderRadius, top); The above code results in drawing a line like the one below. 2. Draw a corner curve We add a curve that starts from an end point of the straight line. Using the arcToPoint method, we draw a curve from the start point to a specified end point. This enables us to create a natural join from the straight line to the curve. In the code below, we set the curve’s end point with Offset and its radius with Radius, giving us a rounded corner for the QR code area. topLeftPath.arcToPoint( Offset(left, top + borderRadius), // End point of the curve radius: Radius.circular(borderRadius), // Radius of the curve clockwise: false, // Draw the curve counterclockwise ); The code above generates a curve with a rounded corner, as shown below. 3. Draw a vertical straight line We extend a line downward from the endpoint of the curve. topLeftPath.lineTo(left, top + cornerLength); The code above adds a vertical line, as shown below. 4. Draw the path on the Canvas We use the defined Path to draw on the Canvas with borderPaint. canvas.drawPath(topLeftPath, borderPaint); Processing the remaining corners The following is a code example demonstrates how to draw the other three corners after the top left one: // Bottom left corner final bottomLeftPath = Path() ..moveTo(left + cornerLength, bottom) ..lineTo(left + borderRadius, bottom) ..arcToPoint( Offset(left, bottom - borderRadius), radius: Radius.circular(borderRadius), clockwise: true, ) ..lineTo(left, bottom - cornerLength); canvas.drawPath(bottomLeftPath, borderPaint); // Bottom right corner final bottomRightPath = Path() ..moveTo(right - cornerLength, bottom) ..lineTo(right - borderRadius, bottom) ..arcToPoint( Offset(right, bottom - borderRadius), radius: Radius.circular(borderRadius), clockwise: false, ) ..lineTo(right, bottom - cornerLength); canvas.drawPath(bottomRightPath, borderPaint); // Top right corner final topRightPath = Path() ..moveTo(right - cornerLength, top) ..lineTo(right - borderRadius, top) ..arcToPoint( Offset(right, top + borderRadius), radius: Radius.circular(borderRadius), clockwise: true, ) ..lineTo(right, top + cornerLength); canvas.drawPath(topRightPath, borderPaint); Summary Besides precise designs like the QR code border, using CustomPaint and the Path class enables you to create even more complex UI designs as well. Implementing the border for the QR code recognition screen ourselves reaffirmed how flexible Flutter is, and power of its Canvas functionality. However, when using CustomPainter, keep in mind that more complex your drawing logic can impact performance. If frequent redrawing is necessary, consider optimizing the processing and utilizing other existing widgets. I hope this article can serve as a useful reference for implementing UI design using CustomPaint and Path.