Kunal Kalwar
Kunal Kalwar

Reputation: 955

How can I achieve the below layout in jetpack compose?

This layout is made by me, the layout you are looking is a SVG image so I have just made the image to fill max size and added the above text and camera capture button below. But now I want to remove the image background and want to make the same layout programmatically.

enter image description here

Box(contentAlignment = Alignment.BottomCenter, modifier = Modifier.fillMaxSize()) {
    AndroidView({ previewView }, modifier = Modifier.fillMaxSize())

    Column(modifier = Modifier.fillMaxSize()) {

        Icon(
            painter = painterResource(id = R.drawable.ic_card_overlay),
            contentDescription = null
        )

        Image(
            modifier = Modifier.fillMaxSize(),
            painter = painterResource(id = R.drawable.ic_black_transparent),
            contentDescription = null,
            contentScale = ContentScale.FillWidth
        )
    }

    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(26.dp)
    ) {

        Row(
            modifier = Modifier
                .padding(bottom = 20.dp), verticalAlignment = Alignment.CenterVertically
        ) {

            Icon(
                modifier = Modifier.clickable {
                    onCloseCameraClick()
                },
                painter = painterResource(id = R.drawable.ic_baseline_arrow_back_ios_24),
                contentDescription = null,
                tint = Color.White
            )

            Text(
                text = "Passport",
                color = Color.White,
                fontSize = 20.sp
            )
        }

        Text(
            text = "Place your passport inside the frame and take a\npicture.\nMake sure it is not cut or has any glare.",
            color = Color.White,
            fontSize = 12.sp
        )
    }

    IconButton(
        modifier = Modifier.padding(bottom = 20.dp),
        onClick = {
            Log.d("takePhoto", "ON CLICK")
            takePhoto(
                imageCapture = imageCapture,
                outputDirectory = outputDirectory,
                executor = executor,
                onImageCaptured = onImageCaptured,
                onError = onError
            )
        },
        content = {
            Icon(
                painter = painterResource(id = R.drawable.ic_baseline_camera_24),
                contentDescription = stringResource(R.string.take_picture),
                tint = Color.White,
                modifier = Modifier
                    .fillMaxSize(0.2f)
            )
        }
    )
}

You can see I have used ic_card_overlay image which act like a background. I want to achieve the same black transparent background with the box in the middle which will not include the black transparent color. Thank you.

Upvotes: 3

Views: 3086

Answers (2)

Bennik2000
Bennik2000

Reputation: 1152

You can archieve this background layout using a custom Shape in combination with a Surface. With a custom implementation you can define what parts of the Surface are displayed and which parts are "cut out".

The cutoutPath defines the part which are highlighted. Here it is defined as a RoundRect with a dynamically calculated position and size. Adjust the topLeft and ``formulas as you need.

Using Path.combine(...) the outlinePath is combined with the cutoutPath. This is where the magic happens.

/**
 * This is a shape with cuts out a rectangle in the center
 */
class CutOutShape : Shape {
    override fun createOutline(
        size: Size,
        layoutDirection: LayoutDirection,
        density: Density
    ): Outline {
        val outlinePath = Path()
        outlinePath.addRect(Rect(Offset(0f, 0f), size))

        val cutoutHeight = size.height * 0.3f
        val cutoutWidth = size.width * 0.75f
        val center = Offset(size.width / 2f, size.height / 2f)

        val cutoutPath = Path()
        cutoutPath.addRoundRect(
            RoundRect(
                Rect(
                    topLeft = center - Offset(
                        cutoutWidth / 2f,
                        cutoutHeight / 2f
                    ),
                    bottomRight = center + Offset(
                        cutoutWidth / 2f,
                        cutoutHeight / 2f
                    )
                ),
                cornerRadius = CornerRadius(16f, 16f)
            )
        )

        val finalPath = Path.combine(
            PathOperation.Difference,
            outlinePath,
            cutoutPath
        )

        return Outline.Generic(finalPath)
    }
}

The shape can be used like this:

Surface(
    shape = CutOutShape(),
    color = Color.Black.copy(alpha = 0.45f)
) { }

This results in the following screen:

enter image description here

Box {
    AndroidView({ previewView }, modifier = Modifier.fillMaxSize())

    Surface(
        shape = CutOutShape(),
        color = Color.Black.copy(alpha = 0.45f),
        modifier = Modifier.fillMaxSize()
    ) { }

    Column(
        modifier = Modifier
            .padding(top = 54.dp, start = 32.dp, end = 32.dp, bottom = 54.dp)
    ) {
        Row(
            Modifier.fillMaxWidth(),
            verticalAlignment = Alignment.CenterVertically
        ) {
            IconButton(onClick = { /*TODO*/ }) {
                Icon(
                    imageVector = Icons.TwoTone.ArrowBack,
                    contentDescription = null,
                    tint = Color.White
                )
            }

            Text(
                "Passport",
                color = Color.White,
                fontSize = 20.sp
            )
        }

        Text(
            "Place your passport inside the frame and take a picture.\nMake sure it is not cut or has any glare.",
            color = Color.White,
            fontSize = 12.sp
        )

        Spacer(modifier = Modifier.weight(1f))

        Icon(
            imageVector = Icons.TwoTone.Camera,
            contentDescription = null,
            tint = Color.White,
            modifier = Modifier
                .size(48.dp)
                .align(Alignment.CenterHorizontally)
        )
    }
}

Upvotes: 4

Thracian
Thracian

Reputation: 66516

You can achieve this with using BlendMode.Clear

@Composable
fun TransparentClipLayout(
    modifier: Modifier,
    width: Dp,
    height: Dp,
    offsetY: Dp
) {

    val offsetInPx: Float
    val widthInPx: Float
    val heightInPx: Float

    with(LocalDensity.current) {
        offsetInPx = offsetY.toPx()
        widthInPx = width.toPx()
        heightInPx = height.toPx()
    }

    Canvas(modifier = modifier) {

        val canvasWidth = size.width

        with(drawContext.canvas.nativeCanvas) {
            val checkPoint = saveLayer(null, null)

            // Destination
            drawRect(Color(0x77000000))

            // Source
            drawRoundRect(
                topLeft = Offset(
                    x = (canvasWidth - widthInPx) / 2,
                    y = offsetInPx
                ),
                size = Size(widthInPx, heightInPx),
                cornerRadius = CornerRadius(30f,30f),
                color = Color.Transparent,
                blendMode = BlendMode.Clear
            )
            restoreToCount(checkPoint)
        }

    }
}

You can customize corner radius size too. This is only for demonstration

Usage

Column {

    Box(modifier = Modifier.fillMaxSize()) {
        Image(
            modifier =Modifier.fillMaxSize(),
            painter = painterResource(id = R.drawable.landscape1),
            contentDescription = null,
            contentScale = ContentScale.Crop
        )
        TransparentClipLayout(
            modifier = Modifier.fillMaxSize(),
            width = 300.dp,
            height = 200.dp,
            offsetY = 150.dp
        )
    }
}

Result

enter image description here

Upvotes: 8

Related Questions