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

Compose实现选中时长时白色高亮框的平滑移动动画问题

Jetpack Compose 高亮框动画修复方案

问题分析

你当前遇到两个核心问题:

  • 白色高亮框绘制在文本上方,导致被遮挡的文本(比如初始的"5 min")无法显示
  • 高亮框的偏移动画未正确关联选中项的位置,动画无法正常触发

修复方案

1. 调整层级解决遮挡问题

Compose 中组件的绘制顺序是代码编写的顺序,后写的组件会覆盖先写的。把高亮框放在文本 Row 的前面,就能让文本显示在高亮框上方。

2. 动态获取选中项的位置和宽度

使用 onGloballyPositioned 监听每个文本项的布局信息,记录它们的位置(x坐标)和宽度,这样高亮框就能精准匹配选中项的位置和尺寸。

3. 绑定动画到选中项的位置

用 animateDpAsState 根据选中项的位置动态计算目标偏移量,实现平滑移动效果。

完整修复代码

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.layout.boundsInParent

@Composable
fun DurationSelector() {
    var selectedIndex by remember { mutableIntStateOf(0) }
    val items = listOf("5 min", "15 min", "30 min", "1 hour")
    
    // 保存每个item的布局边界(位置和宽度)
    val itemBounds = remember { mutableStateListOf<Rect>() }
    // 初始化边界列表,避免索引越界
    LaunchedEffect(items.size) {
        itemBounds.clear()
        repeat(items.size) { itemBounds.add(Rect.Zero) }
    }

    // 根据选中项计算目标偏移量和宽度
    val targetOffset: Dp by animateDpAsState(
        targetValue = if (itemBounds.isNotEmpty()) itemBounds[selectedIndex].left.dp else 0.dp,
        animationSpec = tween(durationMillis = 500),
        label = "Highlight offset animation"
    )
    val targetWidth: Dp by animateDpAsState(
        targetValue = if (itemBounds.isNotEmpty()) itemBounds[selectedIndex].width.dp else 0.dp,
        animationSpec = tween(durationMillis = 500),
        label = "Highlight width animation"
    )

    Box(
        modifier = Modifier
            .fillMaxWidth()
            .height(48.dp)
            .background(
                color = Color(0xff8138FF).copy(0.08f),
                shape = RoundedCornerShape(12.dp),
            )
            .padding(horizontal = 4.dp, vertical = 4.dp),
    ) {
        // 高亮框放在前面,避免遮挡文本
        Box(
            modifier = Modifier
                .width(targetWidth)
                .height(40.dp) // 减去上下padding的4dp,和父容器内边距匹配
                .background(color = Color.White, shape = RoundedCornerShape(12.dp))
                .offset(x = targetOffset, y = 0.dp)
        )

        Row(
            modifier = Modifier
                .fillMaxSize(),
            horizontalArrangement = Arrangement.SpaceBetween,
            verticalAlignment = Alignment.CenterVertically,
        ) {
            items.forEachIndexed { index, item ->
                Text(
                    fontWeight = FontWeight.W600,
                    fontSize = 16.sp,
                    text = item,
                    color = if (selectedIndex == index) Color.Black else Color.DarkGray,
                    modifier = Modifier
                        .clickable { selectedIndex = index }
                        .padding(horizontal = 16.dp)
                        .onGloballyPositioned { layoutCoordinates ->
                            // 记录当前item在父容器中的边界
                            itemBounds[index] = layoutCoordinates.boundsInParent()
                        }
                )
            }
        }
    }
}

关键细节说明

  • 层级调整:高亮框Box写在Row前面,确保文本绘制在高亮框之上
  • 布局信息获取:onGloballyPositioned 获取每个Text在父容器中的相对位置,存入itemBounds列表
  • 动画绑定:animateDpAsState 监听选中index的变化,自动计算并动画过渡到目标偏移和宽度
  • 尺寸匹配:高亮框的高度设置为40dp(父容器高度48dp减去上下各4dp的padding),宽度随选中文本动态调整,更贴合设计

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 10:48:14