Flutter Development: Designing a QR Code Border with CustomPaint and Path

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});
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,
});
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.
}
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 ascenterY + 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.
関連記事 | Related Posts

Jetpack Compose in myroute Android App

Flutter開発効率化:GitHub ActionsとFirebase Hostingを用いたWebプレビュー自動化の方法をstep-by-stepでご紹介

A Kotlin Engineer’s Journey Building a Web Application with Flutter in Just One Month

myroute Android AppでのJetpack Compose

GitHub Copilotとプログラミングして分かったAIとの付き合い方(モバイルエンジニア編)

KotlinエンジニアがFlutterに入門して1ヶ月でWebアプリケーションを作った話
We are hiring!
【プロジェクトマネージャー】モバイルアプリ開発G/大阪
モバイルアプリ開発GについてKINTOテクノロジーズにおける、モバイルアプリ開発のスペシャリストが集まっているグループです。KINTOやmy routeなどのサービスを開発・運用しているグループと協調しながら品質の高いモバイルアプリを開発し、サービスの発展に貢献する事を目標としています。
【Toyota Woven City決済プラットフォームフロントエンドエンジニア(Web/Mobile)】/Toyota Woven City Payment Solution開発G/東京
Toyota Woven City Payment Solution開発グループについて我々のグループはトヨタグループが取り組むToyota Woven Cityプロジェクトの一部として、街の中で利用される決済システムの構築を行います。Toyota Woven Cityは未来の生活を実験するためのテストコースとしての街です。