You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Jetpack Compose中实现图片悬停时的纯色动画遮罩?

问题:为Jetpack Compose图片组件添加悬停纯色遮罩

我有一个带圆角的方形图片组件,希望在悬停时覆盖一层纯色(rgba: 0, 0, 0, 100)。现有代码如下:

@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)
        )
    }
}

我尝试了以下代码,但出现错误:@Composable invocations can only happen from the context of a @Composable function(中文翻译:@Composable调用只能在@Composable函数的上下文中进行)

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)
    ) {}
}

解决方法

错误原因

你出错的核心问题是在onEvent回调(非Composable上下文)里调用了animateColorAsState这个Composable函数,违反了Jetpack Compose的调用规则——Composable函数只能在其他Composable函数或标注了@Composable的上下文中执行。

正确实现

下面是两种可行的实现方式,都能实现悬停时的纯色遮罩动画效果:

方式一:使用hoverable(推荐,更简洁)

@Composable
fun MyImage(imageUri: String) {
    // 维护悬停状态
    val isHovered = remember { mutableStateOf(false) }
    // 基于悬停状态创建颜色过渡动画
    val overlayColor by animateColorAsState(
        targetValue = if (isHovered.value) Color(0, 0, 0, 100) else Color.Transparent,
        animationSpec = tween(durationMillis = 200) // 可选:自定义动画时长
    )
    val imageShape = RoundedCornerShape((WindowSize.windowHeight / 10).dp)
    val imageAspectRatio = 1f

    Box(
        modifier = Modifier
            .hoverable(
                interactionSource = remember { MutableInteractionSource() },
                indication = null // 不需要默认悬停指示器可设为null
            ) { interaction ->
                // 根据交互类型更新悬停状态
                isHovered.value = interaction is HoverInteraction.Enter
            }
    ) {
        // 底层图片
        Image(
            painter = painterResource(imageUri),
            contentDescription = "自定义图片",
            contentScale = ContentScale.Crop,
            alignment = Alignment.TopStart,
            modifier = Modifier
                .fillMaxHeight()
                .aspectRatio(imageAspectRatio)
                .clip(imageShape)
                .align(Alignment.CenterStart)
        )
        // 悬停遮罩层
        Box(
            modifier = Modifier
                .fillMaxHeight()
                .aspectRatio(imageAspectRatio)
                .clip(imageShape)
                .background(overlayColor)
                .align(Alignment.CenterStart)
        )
    }
}

方式二:使用onPointerEvent

如果你习惯用onPointerEvent监听事件,也可以这样写:

@Composable
fun MyImage(imageUri: String) {
    val isHovered = remember { mutableStateOf(false) }
    val overlayColor by animateColorAsState(
        targetValue = if (isHovered.value) Color(0, 0, 0, 100) else Color.Transparent,
        animationSpec = tween(durationMillis = 200)
    )
    val imageShape = RoundedCornerShape((WindowSize.windowHeight / 10).dp)
    val imageAspectRatio = 1f

    Box(
        modifier = Modifier
            .onPointerEvent(PointerEventType.Enter) {
                isHovered.value = true
            }
            .onPointerEvent(PointerEventType.Exit) {
                isHovered.value = false
            }
    ) {
        // 底层图片(代码同上)
        Image(
            painter = painterResource(imageUri),
            contentDescription = "自定义图片",
            contentScale = ContentScale.Crop,
            alignment = Alignment.TopStart,
            modifier = Modifier
                .fillMaxHeight()
                .aspectRatio(imageAspectRatio)
                .clip(imageShape)
                .align(Alignment.CenterStart)
        )
        // 悬停遮罩层(代码同上)
        Box(
            modifier = Modifier
                .fillMaxHeight()
                .aspectRatio(imageAspectRatio)
                .clip(imageShape)
                .background(overlayColor)
                .align(Alignment.CenterStart)
        )
    }
}

关键要点

  • 始终在Composable函数内部声明animateColorAsState,基于isHovered这类状态变量的变化触发动画
  • 遮罩层必须和图片保持完全一致的尺寸、圆角和对齐方式,才能实现精准覆盖
  • hoverable是Jetpack Compose专门为悬停场景设计的Modifier,代码更简洁且符合Compose的交互设计规范

内容的提问来源于stack exchange,提问作者Lolmerkat

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 02:52:18