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

在Jetpack Compose Material中调整TabRow指示器宽度

自定义Jetpack Compose TabRow的指示器宽度

你提到TabRow默认指示器会占满选中Tab的整个宽度,想让它适配文本长度,且已经在ScrollableTabRow上实现了这个效果,下面针对TabRow的场景给出可行方案:

核心思路

TabRow的每个Tab默认会均分父布局宽度,直接用tabIndicatorOffset会让指示器跟着Tab的宽度走。要让指示器适配文本长度,核心是获取Tab内Text的实际尺寸,再调整指示器的位置和宽度。

修改后的代码实现

Column {
    val tabs = listOf("Tab 1", "Tab 2", "Tab 3")
    // 存储每个Tab文本的宽度和在Tab内的偏移量
    val textSizeStates = remember { mutableStateListOf<IntSize>() }
    val textOffsetStates = remember { mutableStateListOf<Offset>() }

    TabRow(
        selectedTabIndex = pagerState.currentPage,
        backgroundColor = MaterialTheme.colors.background,
        divider = { CustomDivider() },
        contentColor = MaterialTheme.colors.primary,
        indicator = { tabPositions ->
            val selectedTabPosition = tabPositions[pagerState.currentPage]
            val textSize = textSizeStates.getOrElse(pagerState.currentPage) { IntSize.Zero }
            val textOffset = textOffsetStates.getOrElse(pagerState.currentPage) { Offset.Zero }
            
            // 计算指示器的偏移:Tab的起始偏移 + 文本在Tab内的水平偏移
            val indicatorOffset = selectedTabPosition.left + textOffset.x
            Box(
                modifier = Modifier
                    .offset(x = indicatorOffset.dp)
                    .width(textSize.width.dp)
                    .height(2.dp)
                    .background(selectedTabIndicatorColor)
            )
        }
    ) {
        tabs.forEachIndexed { index, title ->
            CompositionLocalProvider(LocalRippleTheme provides CustomRippleTheme) {
                Tab(
                    text = {
                        Text(
                            title,
                            color = if (pagerState.currentPage == index) selectedTabTextColor else unselectedTabTextColor,
                            fontWeight = if (pagerState.currentPage == index) FontWeight.Bold else FontWeight.Normal,
                            // 获取文本的实际尺寸和在Tab内的位置
                            onTextLayout = { result ->
                                textSizeStates.apply {
                                    if (size > index) set(index, result.size)
                                    else add(result.size)
                                }
                                textOffsetStates.apply {
                                    if (size > index) set(index, result.getBoundingBox(0).topLeft)
                                    else add(result.getBoundingBox(0).topLeft)
                                }
                            }
                        )
                    },
                    selected = pagerState.currentPage == index,
                    selectedContentColor = Color.Gray,
                    onClick = { scope.launch { pagerState.animateScrollToPage(index) } }
                )
            }
        }
    }
}

关键说明

  • 通过Text的onTextLayout回调,获取文本的实际宽高(result.size)和在Tab内的偏移位置(result.getBoundingBox(0).topLeft)
  • 在指示器中,结合Tab的起始偏移(selectedTabPosition.left)和文本的水平偏移,计算出指示器的正确位置
  • 将指示器的宽度设置为文本的实际宽度,实现和文本长度匹配的效果

如果需要固定宽度的指示器,直接把width(textSize.width.dp)改成固定值(比如width(20.dp)),同时调整偏移让它居中:

// 固定宽度指示器的偏移计算:Tab中心 - 指示器宽度的一半
val fixedWidth = 20.dp
val indicatorOffset = selectedTabPosition.left + (selectedTabPosition.width - fixedWidth) / 2
Box(
    modifier = Modifier
        .offset(x = indicatorOffset)
        .width(fixedWidth)
        .height(2.dp)
        .background(selectedTabIndicatorColor)
)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 12:06:07