Custom National Card/QR Scanner Cutout with Jetpack Compose

How to code a beautiful custom National Card/QR scanner cutout from scratch

Lukas Kristianto
4 min readJul 19, 2024

In this tutorial, we will learn how to create a custom QR scanner cutout using Jetpack Compose. We will replicate the functionality of a typical QR scanner cutout view, but using the modern, declarative UI framework of Jetpack Compose.

Introduction

Jetpack Compose simplifies UI development on Android by using a declarative approach. Instead of defining UI components in XML and linking them to code, Compose allows you to describe your UI in Kotlin code directly. This tutorial will guide you through creating a custom QR scanner cutout with rounded corners and a semi-transparent background.

The final product should look something like this:

Final Capture

Prerequisites

  • Basic knowledge of Jetpack Compose
  • Android Studio 4.0 or later
  • Kotlin programming basics

Step-by-Step Guide

Step 1: Setting Up Your Project

First, ensure that your project is set up to use Jetpack Compose. Add the necessary dependencies to your build.gradle file:

dependencies {
implementation "androidx.compose.ui:ui:1.0.0"
implementation "androidx.compose.material:material:1.0.0"
implementation "androidx.compose.ui:ui-tooling:1.0.0"
}

Step 2: Creating the Composable Function

Now, let’s create the composable function that will represent our custom QR scanner cutout.

First, we have to create a semi-transparent background. We’ll achieve this by drawing a path around the whole screen.

private fun createBackgroundPath(size: Size): androidx.compose.ui.graphics.Path {
return androidx.compose.ui.graphics.Path().apply {
lineTo(size.width, 0f)
lineTo(size.width, size.height)
lineTo(0f, size.height)
lineTo(0f, 0f)
fillType = androidx.compose.ui.graphics.PathFillType.EvenOdd
}
}


@Composable
fun CustomRectangleWithCornersCutout(
modifier: Modifier = Modifier,
verticalOffset: Float = 0.5f,
horizontalOffset: Float = 0.5f
) {
val qrScannerWidth = 340.toPx()
val qrScannerHeight = 220.toPx()

Canvas(modifier = modifier.fillMaxSize()) {
val canvasWidth = size.width
val canvasHeight = size.height

val xAxisLeftEdge = canvasWidth * horizontalOffset - qrScannerWidth / 2f
val xAxisRightEdge = canvasWidth * horizontalOffset + qrScannerWidth / 2f
val yAxisTopEdge = canvasHeight * verticalOffset - qrScannerHeight / 2f
val yAxisBottomEdge = canvasHeight * verticalOffset + qrScannerHeight / 2f

val backgroundShape = createBackgroundPath(size)
val qrScannerShape =
createQrPath(xAxisLeftEdge, yAxisTopEdge, xAxisRightEdge, yAxisBottomEdge)
val qrScannerCornersShape = createCutoutCornersPath(
xAxisLeftEdge, yAxisTopEdge, xAxisRightEdge, yAxisBottomEdge, edgeLength.toFloat()
)

backgroundShape.addPath(qrScannerShape) // even odd rule


drawPath(
path = backgroundShape,
color = ComposeColor(0f, 0f, 0f, 0.8f), // semi-transparent black
)

drawPath(
path = qrScannerShape,
color = ComposeColor.Transparent
)

drawPath(
path = qrScannerCornersShape,
color = ComposeColor.White,
style = androidx.compose.ui.graphics.drawscope.Stroke(width = frameStrokeWidth.toFloat())
)
}
}

Step 3: Using the Custom QR Scanner Cutout in Your UI

We have to create the rectangle in the middle of the screen. We’ll define rectangle height and width and then calculate the positions of all the edges based on the rectangle dimensions and canvas total height and width. Then we can use those edge positions to draw a rectangular path.

The next step is to add the QR scanner path to the background path. This will ensure that the rectangle in the middle stays transparent.

private fun createQrPath(
xAxisLeftEdge: Float,
yAxisTopEdge: Float,
xAxisRightEdge: Float,
yAxisBottomEdge: Float
): androidx.compose.ui.graphics.Path {
return androidx.compose.ui.graphics.Path().apply {
moveTo(xAxisLeftEdge, yAxisTopEdge)
lineTo(xAxisRightEdge, yAxisTopEdge)
lineTo(xAxisRightEdge, yAxisBottomEdge)
lineTo(xAxisLeftEdge, yAxisBottomEdge)
lineTo(xAxisLeftEdge, yAxisTopEdge)
fillType = PathFillType.EvenOdd

}
}

This works because we added PathFillType.EvenOdd to the background Path. Android documentation states:

EVEN_ODD
Specifies that “inside” is computed by an odd number of edge crossings.

Step 4 Create QR Code Outline

private fun createCutoutCornersPath(
xAxisLeftEdge: Float,
yAxisTopEdge: Float,
xAxisRightEdge: Float,
yAxisBottomEdge: Float,
edgeLength: Float
): androidx.compose.ui.graphics.Path {
return androidx.compose.ui.graphics.Path().apply {
// Top-left corner
moveTo(xAxisLeftEdge, yAxisTopEdge + edgeLength)
lineTo(xAxisLeftEdge, yAxisTopEdge)
lineTo(xAxisLeftEdge + edgeLength, yAxisTopEdge)

// Top-right corner
moveTo(xAxisRightEdge - edgeLength, yAxisTopEdge)
lineTo(xAxisRightEdge, yAxisTopEdge)
lineTo(xAxisRightEdge, yAxisTopEdge + edgeLength)

// Bottom-right corner
moveTo(xAxisRightEdge, yAxisBottomEdge - edgeLength)
lineTo(xAxisRightEdge, yAxisBottomEdge)
lineTo(xAxisRightEdge - edgeLength, yAxisBottomEdge)

// Bottom-left corner
moveTo(xAxisLeftEdge + edgeLength, yAxisBottomEdge)
lineTo(xAxisLeftEdge, yAxisBottomEdge)
lineTo(xAxisLeftEdge, yAxisBottomEdge - edgeLength)
}
}

Preview Your Result

@Preview
@Composable
private fun PreviewCorner() {
MaterialTheme {
Box {
Box(Modifier.fillMaxSize().background(androidx.compose.ui.graphics.Color.Red)){

}
CustomRectangleWithCornersCutout()
}
}

}

Here the result from Jetpack Compose Preview

You can integrate with CameraX with Jetpack Compose.

@Composable
fun CameraCaptureScreen(
onCallback: (File) -> Unit
) {
val imageCapture = remember { ImageCapture.Builder().build() }

Box(modifier = Modifier.fillMaxSize()) {
CameraPreviewView(imageCapture)
Overlay()
CaptureImageButton(
Modifier
.align(Alignment.BottomCenter)
.padding(bottom = 48.dp), imageCapture
) {
onCallback(it)
}
}
}

Conclusion

In this article, we have learned how to create a custom QR scanner cutout using Jetpack Compose. This approach leverages the power of Compose’s Canvas to create custom drawing components with ease. You can further customize the cutout shape, colors, and other properties to fit your design needs. Happy coding!

--

--

Lukas Kristianto
Lukas Kristianto

Written by Lukas Kristianto

Senior Software Engineer Android and Artificial Intelligence

No responses yet