1

I am using compose Image as buttons, the images that I am displaying are all different kinds of shapes, and I am using .combinedClickable() on them like this:

            Image(
                bitmap = bitmap,
                contentDescription = "",
                modifier = Modifier.size(
                    (imageWidth * scaleX).pxToDp(),
                    (imageHeight * scaleY).pxToDp())
                    .aspectRatio(imageWidth / imageHeight)
                    .offset(location.x.pxToDp(), location.y.pxToDp())
                    .rotate(rotation)
                    .scale(image.scale)
                    .combinedClickable(
                        onClick = {
                            onClick.invoke()
                            appToOpen?.launch()
                        }, onLongClick = {
                            //appToOpen?.openSettings()
                            onLongClick()
                        }
                    ))

For the most part, the bitmaps are not rectangles, one is the shape of a sun for example, with a large transparent background. I would like to make it so the combinedClickable is only triggered when the non-transparent part of the image is clicked.

Is there a way to achieve that in compose?

1 Answer 1

5

in Jetpack Compose there is no automatic way to achieve that as far as I know. Here's my solution to achive the non transparent part clickability:

@Composable
fun BitmapButton(
    bitmap: ImageBitmap,
    modifier: Modifier = Modifier,
    onLongPress: ()->Unit = {},
    onClick: () -> Unit,
) {
    val androidBitmap = bitmap.asAndroidBitmap()
    var layoutSize by remember { mutableStateOf(IntSize.Zero) }
    var showClick by remember { mutableStateOf(false) }

    fun isTappingOnDrawnImage(tapLocation: Offset): Boolean {
        if (layoutSize.width == 0 || layoutSize.height == 0) return false
        val scaleX = androidBitmap.width.toFloat() / layoutSize.width
        val scaleY = androidBitmap.height.toFloat() / layoutSize.height

        val x = (tapLocation.x * scaleX).toInt().coerceIn(0, androidBitmap.width - 1)
        val y = (tapLocation.y * scaleY).toInt().coerceIn(0, androidBitmap.height - 1)

        val pixel = androidBitmap[x, y]
        val alpha = android.graphics.Color.alpha(pixel)

        return alpha > 0
    }

    Box(
        modifier = modifier
            .onGloballyPositioned { layoutSize = it.size }
            .pointerInput(Unit) {
                awaitEachGesture {
                    awaitFirstDown()
                    // ACTION_DOWN here
                    val tappedTime = System.currentTimeMillis()
                    var isValid = false
                    do {
                        val event: PointerEvent = awaitPointerEvent()

                        event.changes.forEach { pointerInputChange: PointerInputChange ->
                            if (isTappingOnDrawnImage(pointerInputChange.position)) {
                                pointerInputChange.consume()
                                isValid = true
                                if (tappedTime + 350 < System.currentTimeMillis()) {
                                    onLongPress()
                                    isValid = false
                                }
                                showClick = isValid
                            }
                        }
                    } while (event.changes.any { it.pressed })

                    // ACTION_UP is here
                    if (isValid) {
                        onClick()
                    }
                    showClick = false
                }
            }
    ) {

        Image(
            bitmap = bitmap,
            contentDescription = null,
            modifier = Modifier.fillMaxSize(),
            contentScale = ContentScale.FillBounds
        )
        if (showClick) {
            Icon(
                bitmap = bitmap,
                tint = TranslucentDark,
                contentDescription = "",
                modifier = Modifier.fillMaxSize())
        }
    }
}
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks so much! Worked great, updated the code to add a translucent image on top when clicking to give it the "button effect"
Wow, your code adds even more control, I just used stuffs that were already available. Thank you I learnt something new.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.