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

Jetpack Compose动画水瓶首次点击水位无变化问题求助

问题根源与修复方案

问题分析

你的代码存在两个核心问题导致首次点击水位无明显变化:

  1. 水位逻辑完全反向:当前代码用已饮水量占总量的比例绘制「已喝掉的水」,而非剩余水量。首次点击仅喝掉总量的8%左右(200/2450),对应蓝色区域仅增加少量,视觉上难以察觉;后续点击累积已饮水量,蓝色区域逐步变大,你误以为是正常减少,但实际逻辑与需求相反。

  2. 总量赋值方式不规范:直接在Composable根节点赋值totalWaterAmount = weight*35,虽结果正确,但不符合Compose状态管理规范,可能引发不必要的重组。

修复步骤

1. 修正剩余水量计算与绘制逻辑

在WaterBottle组件中,将已饮比例改为剩余水量比例,并调整Canvas绘制路径:

@Composable
fun WaterBottle(
    modifier: Modifier,
    totalWaterAmount: Int,
    unit: String,
    usedWaterAmount: Int,
    waterColor: Color = Color(0xFF1F97F8),
    bottleColor: Color = Color.White,
    capColor: Color = Color(0xFF01355F)
) {
    // 计算剩余水量比例,而非已饮比例
    val remainingPercentage by animateFloatAsState(
        targetValue = if (totalWaterAmount > 0) (totalWaterAmount - usedWaterAmount).toFloat() / totalWaterAmount.toFloat() else 1f,
        animationSpec = tween(1000), label = ""
    )

    val usedWaterAnimation by animateIntAsState(
        targetValue = usedWaterAmount, animationSpec = tween(1000), label = ""
    )

    Box(
        modifier = modifier
            .clip(RoundedCornerShape(10.dp))
            .background(Color(0xFFFDF7BA))
            .padding(10.dp)
    ) {
        Canvas(modifier = Modifier.fillMaxSize()) {
            val width = size.width
            val height = size.height

            val capWidth = size.width * 0.55f
            val capHeight = size.height * 0.08f

            val bottleBodyPath = Path().apply {
                moveTo(width * 0.3f, height * 0.1f)
                lineTo(
                    width * 0.3f, height * 0.2f
                )
                quadraticBezierTo(
                    0f, height * 0.3f, 0f, height * 0.4f
                )
                lineTo(
                    0f, height * 0.95f
                )
                quadraticBezierTo(
                    0f, height, width * 0.05f, height
                )
                lineTo(
                    width * 0.95f, height
                )
                quadraticBezierTo(
                    width, height, width, height * 0.95f
                )
                lineTo(
                    width, height * 0.4f
                )
                quadraticBezierTo(
                    width, height * 0.3f, width * 0.7f, height * 0.2f
                )
                lineTo(
                    width * 0.7f, height * 0.1f
                )
                close()
            }
            clipPath(
                bottleBodyPath
            ) {
                drawRect(
                    color = bottleColor, size = size
                )
                // 计算剩余水的高度,从顶部往下延伸
                val remainingWaterHeight = remainingPercentage * size.height
                val waterPath = Path().apply {
                    moveTo(0f, 0f)
                    lineTo(size.width, 0f)
                    lineTo(size.width, remainingWaterHeight)
                    lineTo(0f, remainingWaterHeight)
                    close()
                }
                drawPath(
                    waterPath, waterColor
                )
            }
            drawRoundRect(
                color = capColor,
                size = Size(capWidth, capHeight),
                topLeft = Offset(size.width / 2 - capWidth / 2f, 0f),
                cornerRadius = CornerRadius(45f, 45f)
            )
        }

        val fontSize = with(LocalConfiguration.current) { screenWidthDp * 0.03 }.sp

        val text = buildAnnotatedString {
            withStyle(
                SpanStyle(
                    color = Color.Black,
                    fontSize = fontSize
                )
            ) {
                append("Drunk Water:\n")
            }
            withStyle(
                style = SpanStyle(
                    color = Color.Black,
                    fontSize = fontSize * 2
                )
            ) {
                append(usedWaterAnimation.toString())
            }
            withStyle(
                style = SpanStyle(
                    color = Color.Black,
                    fontSize = fontSize
                )
            ) {
                append(" ")
                append(unit)
            }
        }
        Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
            Text(text = text)
        }
    }
}

2. 规范总量状态管理

在WaterScreen中,用derivedStateOf计算每日需水量,避免重复赋值:

@Composable
fun WaterScreen() {
    val context = LocalContext.current
    var usedWaterAmount by remember { mutableStateOf(0) }

    val weight = 70
    // 用derivedStateOf计算固定总量,无需可变状态
    val totalWaterAmount by remember { derivedStateOf { weight * 35 } }

    val configuration = LocalConfiguration.current
    val screenWidth = configuration.screenWidthDp.dp

    Column(
        modifier = Modifier
            .width(screenWidth * 0.4f),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Top
    ) {
        Spacer(modifier = Modifier.height(25.dp))
        Column(
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            WaterBottle(
                modifier = Modifier
                    .width(screenWidth * 0.3f)
                    .height(screenWidth * 0.6f),
                totalWaterAmount = totalWaterAmount,
                unit = "ml",
                usedWaterAmount = usedWaterAmount
            )
            Spacer(modifier = Modifier.height(10.dp))
            Text(text = "Total:$totalWaterAmount ml")
            Button(onClick = {
                if (usedWaterAmount < totalWaterAmount) usedWaterAmount += 200 else Toast.makeText(
                    context, "Bottle is Empty", Toast.LENGTH_LONG
                ).show()
            }) {
                Text(text = "Drink")
            }
        }
    }
}

修复效果

修正后,首次点击按钮时,剩余水量从100%降至约92%,水位明显下降;后续每次点击都会直观显示水位逐步降低,完全符合你的需求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 21:22:31