Search code examples
androidandroid-jetpack-composeandroid-jetpackandroid-compose-textfield

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.

enter image description here


Solution

  • You can get indexes of new line characters '\n' or others and then loop through each of them to get line count.

    Result

    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
    }
    

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