如何在Android Jetpack Compose中检测TextField光标位于哪一行

3
如何在Android Jetpack Compose中检测TextField光标位于哪一行。

enter image description here

2个回答

4

要访问所选文本位置,您需要使用 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,
            )
        }
    )
}


3

您可以得到换行字符'\n'或其他的索引,然后循环遍历它们以获取行数。

结果

enter image description here

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 })
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接