Home > Net >  How can I achieve the below layout in jetpack compose?
How can I achieve the below layout in jetpack compose?

Time:08-30

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,
                one rror = one rror
            )
        },
        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.

CodePudding user response:

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

CodePudding user response:

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)
        )
    }
}
  • Related