14

How can I set the cursor in a random position on a TextField when it it get focus? The equivalent of editText.setSelection(position) with the classic android view system.

This is the code I am using to have an edit text automatically receive the focus when it is added to the screen. I would like to be able to move the cursor from the default position which is 0

val (getText, setText) = remember { mutableStateOf("hello") }
AutofocusEditText(
    text = getText,
    setText = setText
)
    
...

@Composable
private fun AutofocusEditText(
    text: String,
    setText : (String) -> Unit
) {
    val focusState = remember { mutableStateOf(FocusState.Inactive) }
    val focusRequester = FocusRequester()
    val focusModifier = Modifier.focus()
    Row(
        modifier = Modifier.focusObserver { newFocusValue -> focusState.value = newFocusValue }
    ) {
        val focusRequesterModifier =
            Modifier.focusRequester(focusRequester)

        TextField(
            value = text,
            modifier = focusModifier.then(focusRequesterModifier),
            backgroundColor = Color.Transparent,
            onValueChange = setText,
            keyboardOptions = KeyboardOptions.Default.copy(
                imeAction = ImeAction.Done
            ),
            onImeActionPerformed = { action, softKeyboardController ->
                if (action == ImeAction.Done) {
                    softKeyboardController?.hideSoftwareKeyboard()
                }
            }
        )
    }
    onActive {
        focusRequester.requestFocus()
    }
}
Nicola De Fiorenze
  • 1,958
  • 1
  • 17
  • 27

3 Answers3

19

You have to use the TextFieldValue version of TextField.

@Composable
fun TextField(
    value: TextFieldValue,
    onValueChange: (TextFieldValue) -> Unit,
    /* ... */) {/* Impl */}

Code examples: EDIT: Compose Version 1.1.1 (13.04.22)

enum class CursorSelectionBehaviour {
    START, END, SELECT_ALL
}

@Composable
fun AutofocusTextFieldExample(
    initValue: String,
    behaviour: CursorSelectionBehaviour = CursorSelectionBehaviour.END
) {
    val direction = LocalLayoutDirection.current
    var tfv by remember {
        val selection = when (behaviour) {
            CursorSelectionBehaviour.START -> {
                if (direction == Ltr) TextRange.Zero else TextRange(initValue.length)
            }
            CursorSelectionBehaviour.END -> {
                if (direction == Ltr) TextRange(initValue.length) else TextRange.Zero
            }
            CursorSelectionBehaviour.SELECT_ALL -> TextRange(0, initValue.length)
        }
        val textFieldValue = TextFieldValue(text = initValue, selection = selection)
        mutableStateOf(textFieldValue)
    }
    val focusRequester = remember { FocusRequester.Default }
    TextField(
        modifier = Modifier.focusRequester(focusRequester),
        value = tfv,
        onValueChange = { tfv = it }
    )
    LaunchedEffect(Unit) {
        focusRequester.requestFocus()
    }
}

OLD (04.01.21):

    @Composable
    fun AutoFocusingText() {
        val textState = remember { mutableStateOf(TextFieldValue()) }
        val focusState = remember { mutableStateOf(FocusState.Inactive) }
        val focusRequester = FocusRequester()
        val focusModifier = Modifier.focus()
        Row(
            modifier = Modifier.focusObserver { focusState.value = it }
        ) {
            val focusRequesterModifier = Modifier.focusRequester(focusRequester)
            TextField(
                modifier = focusModifier.then(focusRequesterModifier),
                value = textState.value,
                onValueChange = { value: TextFieldValue ->
                    textState.value = value
                }
            )
        }
        onActive {
            focusRequester.requestFocus()
        }
    }

If you have a non-empty string as an initial value you have to change the selection manually. Replace the empty TextFieldValue with: TextFieldValue(text = value, selection = TextRange(value.length, value.length))

When you want to extract the value like it is in your code. You either add the current selection as a parameter or extract it combined with the TextFieldValue. Otherwise, if the user edits in the middle of the text the cursor jumps back to the end on the next onValueChanged.

2jan222
  • 1,732
  • 2
  • 16
  • 29
  • 44
    Such an enormous pain to do such a simple thing. Cursor at the end should be the **default** behavior, imo. At the very minimum, `TextField` should have an optional constructor parameter `autoFocus: AutoFocus = AutoFocus.Start` so that developers won't be forced to write their own solution. – kc_dev Dec 24 '21 at 01:31
  • @kc_dev Sorry, if this is not the case, but somebody might consider my comment useful. You can also use the textStyle constructor parameter of the TextField to change the position of the cursor. The cursor will follow the textAlign parameter of the textStyle. TextField(... textStyle = TextStyle( ... textAlign = TextAlign.Start )) – Inliner Apr 13 '22 at 09:18
  • 1
    The cursor will follow to the side of the alignment, but it does not move to the correct position in the text. I updated the code, if you want to have the cursor at the end of already existing text selection must be used. Alignment will just align the text alongside the cursor not the cursor in a text. – 2jan222 Apr 13 '22 at 10:54
  • So if I want to use one of the function overloads that don't use `TextFieldValue` then what? If there are no other options then the other overloads are implicitly useless. Compose has been stable for nearly 2 years, how can this be acceptable? – Mark Apr 23 '23 at 14:59
0

When the input is not updated just by the user and is also changed from outside of the component, this is a way to update the content while letting the user play with the cursor:

private fun InputField(input:String) {
 
  val inputLength = input.length
  val textFieldValue = TextFieldValue(input, TextRange(inputLength))
  var textFieldValueRembered by remember { mutableStateOf(textFieldValue) }

  //a way to detect a reformation of text while letting the user control the cursor location
  if (input != textFieldValue.text)
    textFieldValueRembered = textFieldValue

  TextField(
   textFieldValue = textFieldValueRembered,
    onValueChange = {
      onValueChange(it.text)
      phoneInputField = it
    }
  )
0

There is another way to control the cursor position without using LaunchedEffect to call focusRequester.requestFocus(). You can use call it in onGloballyPositioned. You can also show the keyboard in onGloballyPositioned if needed.


    val focusRequester = remember { FocusRequester() }
    val keyboard = LocalSoftwareKeyboardController.current

    val textFieldValue = TextFieldValue(text = initValue, selection = TextRange(initValue.length)) //place cursor at the end of the text
    var tfv by remember { mutableStateOf(textFieldValue) }

    TextField(
        modifier = Modifier
            .focusRequester(focusRequester)
            .onGloballyPositioned {
                focusRequester.requestFocus() // IMPORTANT
                keyboard?.show()
            },
        value = tfv,
        onValueChange = {
            tfv = it
        }
    )
VIN
  • 6,385
  • 7
  • 38
  • 77