英文:
How to detect TextField cursor lies on which line in Android Jetpack compose
问题
How to detect TextField cursor lies on which line in Android Jetpack Compose.
答案1
得分: 4
To have access to selected text position, you need TextFieldValue
.
如果要访问所选文本的位置,您需要使用 TextFieldValue
。
If you have short lines and you are sure that they will not break in any way other than \n
symbol, you can manually calculate the current line.
如果您的文本行较短,并且您确信它们除了 \n
符号之外不会以任何方式中断,您可以手动计算当前行。
For a more general solution, which does not depend on the format of your data, you can use the TextLayoutResult
value to get this information.
对于一种更通用的解决方案,不依赖于数据格式,您可以使用 TextLayoutResult
值来获取这些信息。
onTextLayout
is only available with BasicTextField
, but using a decorationBox
you can easily turn it into a TextField
/OutlinedTextField
.
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,
)
}
)
}
<img src="https://i.stack.imgur.com/xAtnP.gif" width="300"></img>
英文:
To have access to selected text position, you need TextFieldValue
.
If you have short lines and you are sure that they will not break in any way other than \n
symbol, you can manually calculate the current line.
For a more general solution, which does not depend on the format of your data, you can use the TextLayoutResult
value to get this information.
onTextLayout
is only available with BasicTextField
, but using a decorationBox
you can easily turn it into a 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,
)
}
)
}
<img src="https://i.stack.imgur.com/xAtnP.gif" width="300"></img>
答案2
得分: 2
以下是代码部分的翻译:
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)
}
请注意,这只是代码部分的翻译,不包括注释和示例部分。如果您需要任何其他信息,请告诉我。
英文:
You can get indexes of new line characters '\n' or others and then loop through each of them to get line count.
Result
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
}
If you wish to get not only line but row too you can do it by using a map
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)
}
Demo
@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 })
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论