kotlinjetbrains-compose

How can I animate a solid color over an Image?


I have this square image component with rounded corners that I want to overlay a solid color over on hover:

@Composable
fun MyImage(imageUri: String) {
    var likeOverlayColor = animateColorAsState(targetValue = Color(0, 0, 0, 100))
    val imageShape = RoundedCornerShape((WindowSize.windowHeight / 10).dp)
    val imageAspectRatio = 1f

    Box {
        Image(
            painter = painterResource(imageUri),
            contentDescription = null,
            contentScale = ContentScale.Crop,
            alignment = Alignment.TopStart,
            modifier = Modifier
                .fillMaxHeight()
                .aspectRatio(imageAspectRatio)
                .clip(imageShape)
                .align(Alignment.CenterStart)
        )
    }
}

How would I now overlay a solid color (rgb: 0, 0, 0, 100) over it on hover?

I have tried to use

var imageOverlayColor = remember { mutableStateOf(Color(0, 0, 0, 0)) }

Box (
    Modifier.onPointerEvent(
        eventType = PointerEventType.Enter,
        pass = PointerEventPass.Main,
        onEvent = {
            imageOverlayColor = animateColorAsState(targetValue = Color(0, 0, 0, 100))
        }
    )
) { 
    Image (...) {

    }

    Surface (
        modifier = Modifier
            .fillMaxHeight()
            .aspectRatio(imageAspectRatio)
            .clip(imageShape)
            .align(Alignment.CenterStart)
    ) {}
}

but got this error: @Composable invocations can only happen from the context of a @Composable function


Solution

  • The problem is here that you are calling the @Composable function animateColorAsState inside the onEvent handler context, which is not a composable function.

    To use animateColorAsState correctly, you should set your color outside of the event handler. Use for example a variable showOverlay to save the state of the overlay. Then you can specify different colors for your imageOverlayColor.

    See the example code:

    @Composable
    fun MyImage(imageUri: String) {
        val imageShape = RoundedCornerShape((WindowSize.windowHeight / 10).dp)
        val imageAspectRatio = 1f
    
        // boolean variable for overlay visibility
        var showOverlay by remember { mutableStateOf(false) }
        // Specify different color depending on showOverlay value
        val imageOverlayColor = animateColorAsState(if (showOverlay) Color(0, 0, 0, 0) else Color(0, 0, 0, 100))
    
        Box (
            Modifier.onPointerEvent(
                eventType = PointerEventType.Enter,
                pass = PointerEventPass.Main,
                onEvent = {
                    // toggle overlay visibility
                    showOverlay = !showOverlay
                }
            )
        ) {
            Image (...) {
    
            }
    
            Surface (
                modifier = Modifier
                    .fillMaxHeight()
                    .aspectRatio(imageAspectRatio)
                    .clip(imageShape)
                    .align(Alignment.CenterStart)
            ) {}
        }
    }
    
    

    And if you just want to change the opacity of the element, consider using just a value overlayOpacity and use animateFloatAsState.