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

如何在Jetpack Compose的BasicTextField中添加对齐的行号?

Jetpack Compose代码编辑器行号与文本垂直对齐解决方案

你的核心问题出在行号与输入框文本的基线未对齐,以及文本样式不统一导致的视觉错位。以下是针对性的修正方案:

关键问题分析

  • 行号文本与输入框文本的字体参数(字号、行高、字体族)不一致,导致基线错位
  • 直接使用lineTop作为偏移,未考虑行号自身的基线偏移(文本的垂直对齐应基于基线而非顶部)
  • 行号布局方式不合理,叠加的Text容易出现位置偏差

修正方案1:基于基线对齐的简洁实现

var text by remember { mutableStateOf("") }
var textLayoutResult by remember { mutableStateOf<TextLayoutResult?>(null) }

// 统一行号与输入框的文本样式
val codeTextStyle = TextStyle(
    fontSize = 14.sp,
    lineHeight = 20.sp,
    fontFamily = FontFamily.Monospace
)

BasicTextField(
    modifier = Modifier.fillMaxSize(),
    value = text,
    onValueChange = { text = it },
    textStyle = codeTextStyle,
    onTextLayout = { result ->
        textLayoutResult = result
    },
    decorationBox = { innerTextField ->
        Row(verticalAlignment = Alignment.Top) {
            // 行号区域
            textLayoutResult?.let { result ->
                Box(
                    modifier = Modifier
                        .padding(horizontal = 8.dp)
                        .widthIn(min = 32.dp),
                    contentAlignment = Alignment.TopEnd
                ) {
                    Column {
                        repeat(result.lineCount) { lineIndex ->
                            val lineTop = result.getLineTop(lineIndex)
                            val lineBaseline = result.getLineBaseline(lineIndex)
                            // 计算行号基线偏移:让行号基线与输入框行基线对齐
                            val baselineOffset = with(LocalDensity.current) {
                                (lineBaseline - lineTop).toDp()
                            }

                            Text(
                                text = "${lineIndex + 1}",
                                style = codeTextStyle.copy(color = Color.Gray),
                                modifier = Modifier
                                    .padding(top = with(LocalDensity.current) { lineTop.toDp() })
                                    .offset(y = baselineOffset)
                            )
                        }
                    }
                }
            }
            // 输入框内容区域
            Box(modifier = Modifier.weight(1f)) {
                innerTextField()
            }
        }
    }
)

修正方案2:自定义Layout精确控制(推荐)

通过自定义Layout直接基于输入框的基线位置布局行号,性能更优且对齐精度更高:

// 自定义行号布局组件
@Composable
fun LineNumbers(
    lineCount: Int,
    textLayoutResult: TextLayoutResult,
    style: TextStyle,
    modifier: Modifier = Modifier
) {
    Layout(
        content = {
            repeat(lineCount) { index ->
                Text(text = "${index + 1}", style = style.copy(color = Color.Gray))
            }
        },
        modifier = modifier
    ) { measurables, constraints ->
        val placeables = measurables.map { it.measure(constraints) }
        val maxWidth = placeables.maxOf { it.width }

        layout(maxWidth, constraints.maxHeight) {
            repeat(lineCount) { lineIndex ->
                val placeable = placeables[lineIndex]
                val lineBaseline = textLayoutResult.getLineBaseline(lineIndex)
                // 计算行号Y位置:让行号基线与输入框行基线完全对齐
                val yPosition = lineBaseline - placeable.baseline
                placeable.placeRelative(
                    x = maxWidth - placeable.width, // 行号右对齐
                    y = with(LocalDensity.current) { yPosition.toIntPx() }
                )
            }
        }
    }
}

// 使用示例
var text by remember { mutableStateOf("") }
var textLayoutResult by remember { mutableStateOf<TextLayoutResult?>(null) }
val codeTextStyle = TextStyle(
    fontSize = 14.sp,
    lineHeight = 20.sp,
    fontFamily = FontFamily.Monospace
)

BasicTextField(
    modifier = Modifier.fillMaxSize(),
    value = text,
    onValueChange = { text = it },
    textStyle = codeTextStyle,
    onTextLayout = { result ->
        textLayoutResult = result
    },
    decorationBox = { innerTextField ->
        Row(verticalAlignment = Alignment.Top) {
            textLayoutResult?.let { result ->
                LineNumbers(
                    lineCount = result.lineCount,
                    textLayoutResult = result,
                    style = codeTextStyle,
                    modifier = Modifier.padding(horizontal = 8.dp)
                )
            }
            Box(modifier = Modifier.weight(1f)) {
                innerTextField()
            }
        }
    }
)

注意事项

  • 必须保证行号与输入框的TextStyle完全一致,包括fontSize、lineHeight、fontFamily,否则视觉上仍会错位
  • 如果输入框设置了内边距,需要在行号的位置计算中同步叠加对应偏移量
  • 行号区域设置固定最小宽度,避免行号位数变化导致布局跳动

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 05:03:12