Custom National Card/QR Scanner Cutout with Jetpack Compose
How to code a beautiful custom National Card/QR scanner cutout from scratch
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:
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!