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

如何在Jetpack Compose中实现文本自动滚动效果?

动态文本自动滚动实现方案

需求说明

从对象列表动态加载文本,文本长度存在差异:

  • 短文本直接适配Text视图,保持静止
  • 长文本超出视图范围时,需满足:
    1. 文本更新后延迟500毫秒开始滚动
    2. 以匀速向左滚动,直到显示最后一个字符后停止
    3. 下次文本更新时,自动回到初始位置,重复上述滚动逻辑

当前代码仅支持手动拖动滚动,原代码如下:

Column(
            modifier = Modifier
                .height(25.dp)
                .fillMaxSize()
                .background(color = slideCaptionBg, shape = RoundedCornerShape(16.dp))
                .clickable { columnClicked.value = true },
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.Center,
        ) {
            val textState = remember { Animatable(0f) }
            val scrollState = rememberScrollState()

            LaunchedEffect(Unit) {
                while (true) {
                    val targetValue = if (textWidth < 800) (textWidth - 800).toFloat() else 0F
                    textState.animateTo(targetValue, animationSpec = tween(800))
                    delay(500L)
                    textState.animateTo(0F, animationSpec = tween(800))
                    delay(4000L)
                }
            }

            Row(
                modifier = Modifier
                    .fillMaxSize()
                    .padding(end = 8.dp, start = 8.dp)
                    .width(IntrinsicSize.Min)
                    .horizontalScroll(scrollState)
                    .background(Color.Transparent),
                horizontalArrangement = Arrangement.Center,
                verticalAlignment = Alignment.CenterVertically
            ) {

                Text(
                    text = stringResource(
                        R.string.song_art_slide_caption, // %s by %s  <-- 带变量的字符串资源
                        newArtSlide.track,
                        newArtSlide.artist
                    ),
                    modifier = Modifier
                        .background(Color.Transparent),
                    color = Color.White,
                    fontSize = 16.sp,
                    maxLines = 1,
                    overflow = TextOverflow.Ellipsis,
                    softWrap = false,
                    style = MaterialTheme.typography.titleMedium,
                    textAlign = TextAlign.Center,
                )
            }

            if (columnClicked.value) {
                UtilFunctions.showSnackBar(
                    coroutineScope = coroutineScope,
                    snackState = snackState,
                    message = "Show Song If Signed in!"
                )
                columnClicked.value = false
            }
        }

修改后的实现代码

Column(
    modifier = Modifier
        .height(25.dp)
        .fillMaxSize()
        .background(color = slideCaptionBg, shape = RoundedCornerShape(16.dp))
        .clickable { columnClicked.value = true },
    horizontalAlignment = Alignment.CenterHorizontally,
    verticalArrangement = Arrangement.Center,
) {
    val scrollState = rememberScrollState()
    // 存储当前显示的文本,用于监听内容变化
    val currentText = remember {
        mutableStateOf(
            stringResource(
                R.string.song_art_slide_caption,
                newArtSlide.track,
                newArtSlide.artist
            )
        )
    }
    // 用于测量文本实际渲染宽度
    val textMeasurer = rememberTextMeasurer()
    // 存储容器可用宽度(减去左右padding后)
    val containerWidth = remember { mutableStateOf(0) }

    // 监听文本更新和容器宽度变化,触发滚动逻辑
    LaunchedEffect(currentText.value, containerWidth.value) {
        // 每次文本更新,先重置滚动位置到初始状态
        scrollState.scrollTo(0)
        
        // 计算文本实际宽度
        val textLayoutResult = textMeasurer.measure(
            text = currentText.value,
            style = MaterialTheme.typography.titleMedium.copy(
                fontSize = 16.sp,
                color = Color.White
            )
        )
        val textWidth = textLayoutResult.size.width
        val availableWidth = containerWidth.value - 16.dp.toPx() // 减去左右各8dp的padding

        // 仅当文本宽度超过容器可用宽度时执行滚动
        if (textWidth > availableWidth) {
            delay(500L) // 延迟500毫秒开始滚动
            
            // 计算需要滚动的总距离
            val scrollDistance = textWidth - availableWidth
            // 设置滚动速度:100dp/秒,转换为px/毫秒单位
            val scrollSpeed = 100.dp.toPx() / 1000
            // 根据滚动距离和速度计算时长,保证匀速滚动
            val scrollDuration = (scrollDistance / scrollSpeed).toLong()

            // 平滑滚动到目标位置,匀速滚动保证可读性
            scrollState.animateScrollTo(
                targetOffset = scrollDistance.toInt(),
                animationSpec = tween(
                    durationMillis = scrollDuration.toInt(),
                    easing = LinearEasing
                )
            )
        }
    }

    Row(
        modifier = Modifier
            .fillMaxSize()
            .padding(horizontal = 8.dp)
            // 监听容器尺寸变化,记录可用宽度
            .onSizeChanged { size ->
                containerWidth.value = size.width
            }
            .horizontalScroll(scrollState)
            .background(Color.Transparent),
        horizontalArrangement = Arrangement.Center,
        verticalAlignment = Alignment.CenterVertically
    ) {
        Text(
            text = currentText.value,
            modifier = Modifier.background(Color.Transparent),
            color = Color.White,
            fontSize = 16.sp,
            maxLines = 1,
            overflow = TextOverflow.Ellipsis,
            softWrap = false,
            style = MaterialTheme.typography.titleMedium,
            textAlign = TextAlign.Center,
        )
    }

    if (columnClicked.value) {
        UtilFunctions.showSnackBar(
            coroutineScope = coroutineScope,
            snackState = snackState,
            message = "Show Song If Signed in!"
        )
        columnClicked.value = false
    }
}

关键修改点说明

  1. 准确判断滚动条件:通过rememberTextMeasurer()测量文本实际宽度,结合onSizeChanged获取容器可用宽度,只有文本超出容器时才触发滚动
  2. 文本更新监听:用LaunchedEffect监听文本内容变化,每次更新先重置滚动位置到初始状态
  3. 匀速滚动控制:根据滚动距离动态计算动画时长,搭配LinearEasing实现匀速滚动,保证阅读舒适度
  4. 延迟触发逻辑:长文本更新后延迟500毫秒再开始滚动,符合需求要求

内容的提问来源于stack exchange,提问作者Klem Lloyd Mwenya

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 18:23:14