要访问所选文本位置,您需要使用 TextFieldValue
。
如果您有短行并且确定它们不会以除了 \n
符号之外的任何方式中断,您可以手动计算当前行。
对于更一般的解决方案,不依赖于数据格式,您可以使用 TextLayoutResult
值来获取此信息。
onTextLayout
仅适用于 BasicTextField
,但是使用 decorationBox
您可以轻松将其转换为 TextField
/OutlinedTextField
。
var textFieldValue by remember {
mutableStateOf(TextFieldValue(LoremIpsum().values.first()))
}
var textLayoutResult by remember {
mutableStateOf<TextLayoutResult?>(null)
}
val cursorLine by remember {
derivedStateOf {
textLayoutResult?.getLineForOffset(textFieldValue.selection.start)
}
}
val interactionSource = remember { MutableInteractionSource() }
Column {
Text("Line: ${cursorLine.toString()}")
BasicTextField(
value = textFieldValue,
onValueChange = { textFieldValue = it },
interactionSource = interactionSource,
onTextLayout = {
textLayoutResult = it
},
decorationBox = { innerTextField ->
TextFieldDefaults.TextFieldDecorationBox(
value = textFieldValue.text,
innerTextField = innerTextField,
enabled = true,
singleLine = false,
visualTransformation = VisualTransformation.None,
interactionSource = interactionSource,
)
}
)
}
您可以得到换行字符'\n'或其他的索引,然后循环遍历它们以获取行数。
结果
fun getLine(textFieldValue: TextFieldValue): Int {
val text = textFieldValue.text
val selection = textFieldValue.selection.start
val lineList = mutableListOf< Int>()
text.forEachIndexed { index: Int, c: Char ->
if (c == '\n') {
lineList.add(index)
}
}
if (lineList.isEmpty()) return 1
lineList.forEachIndexed { index, lineEndIndex ->
if (selection <= lineEndIndex){
return index + 1
}
}
return lineList.size + 1
}
fun getLineAndRowOfSelection(textFieldValue: TextFieldValue): Pair<Int, Int> {
val text = textFieldValue.text
val selection = textFieldValue.selection.start
val lineMap = linkedMapOf<Int, Int>()
var lineCount = 1
text.forEachIndexed { index: Int, c: Char ->
if (c == '\n') {
lineMap[index] = lineCount
lineCount++
}
}
if (lineMap.isEmpty()) {
return Pair(1, selection)
} else if (lineMap.keys.last() < selection) {
// Selection is in a row after last new line char
val key = lineMap.keys.last()
val lastLine = lineMap[key] ?: 0
return Pair(lastLine + 1, selection - key - 1)
} else {
// Selection is before last new line char
var previousLineIndex = -1
lineMap.forEach { (lineEndIndex, line) ->
if (selection <= lineEndIndex) {
// First line
return if (previousLineIndex == -1) {
Pair(line, selection)
} else {
Pair(line, selection - previousLineIndex - 1)
}
}
previousLineIndex = lineEndIndex
}
}
return Pair(-1, -1)
}
演示
@Preview
@Composable
private fun TextSelectionTest() {
Column(
modifier = Modifier.padding(top = 30.dp)
) {
var textFieldValue by remember {
mutableStateOf(TextFieldValue())
}
val lineAndRow = getLineAndRowOfSelection(textFieldValue)
val line = getLine(textFieldValue)
Text(
"Text ${textFieldValue.text}, " +
"selection: ${textFieldValue.selection}\n" +
"line and row: $lineAndRow\n" +
"line: $line"
)
TextField(value = textFieldValue, onValueChange = { textFieldValue = it })
}
}