Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the simplest way to set the focus order in Jetpack Compose?

I have a column of TextFields, something like:

Column {
    TextField(
        value = ...,
        onValueChange = { ... },
        keyboardOptions = KeyboardOptions(imeAction = ImeAction.next),
    )
    TextField(
        value = ...,
        onValueChange = { ... },
        keyboardOptions = KeyboardOptions(imeAction = ImeAction.next),
    )
    .
    .
    .
}

I would like to have the focus on each TextField move to the next when the user press Tab, or the next button on the keyboard. Currently pressing Tab inserts a tab into the TextField. Pressing the next button does nothing. I can create a FocusRequester for each TextField and set the keyboardActions onNext to request focus on the next field for each one. This is a little tedious and it doesn't address the Tab behavior.

like image 617
Sean Avatar asked Mar 26 '21 13:03

Sean


People also ask

What is LazyColumn in jetpack compose?

A LazyColumn is a vertically scrolling list that only composes and lays out the currently visible items. It's similar to a Recyclerview in the classic Android View system.

What is mutableStateOf in jetpack compose?

mutableStateOf creates an observable MutableState<T> , which is an observable type integrated with the compose runtime. interface MutableState<T> : State<T> { override var value: T. } Any changes to value will schedule recomposition of any composable functions that read value .

Does jetpack compose use views?

Jetpack Compose is designed to work with the established View-based UI approach. If you're building a new app, the best option might be to implement your entire UI with Compose. But if you're modifying an existing app, you might not want to fully migrate your app all at once.

Is jetpack compose reactive?

Jetpack Compose is a modern toolkit designed to simplify UI development. It combines a reactive programming model with the conciseness and ease of use of the Kotlin programming language.


Video Answer


5 Answers

I recently found this article: https://medium.com/google-developer-experts/focus-in-jetpack-compose-6584252257fe

It explains a different way to handle focus that is quite a bit simpler.

val focusManager = LocalFocusManager.current
TextField(
    modifier = Modifier
        .onPreviewKeyEvent {
            if (it.key == Key.Tab && it.nativeKeyEvent.action == ACTION_DOWN){
                focusManager.moveFocus(FocusDirection.Down)
                true
            } else {
                false
            }
        },
    value = text,
    onValueChange = { it -> text = it },
    keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
    keyboardActions = KeyboardActions(
        onNext = { focusManager.moveFocus(FocusDirection.Down) }
    )
)
like image 191
Sean Avatar answered Nov 12 '22 21:11

Sean


Not sure if it's the easier way, but you can create a FocusRequester object for each field and request the focus following the order that you want.

@Composable
fun FocusRequestScreen() {
    // Create FocusRequesters... (you can use createRefs function)
    val focusRequesters = List(3) { FocusRequester() }

    Column {
        TextFieldWithFocusRequesters(focusRequesters[0], focusRequesters[1])
        TextFieldWithFocusRequesters(focusRequesters[1], focusRequesters[2])
        TextFieldWithFocusRequesters(focusRequesters[2], focusRequesters[0])
    }
}

@Composable
private fun TextFieldWithFocusRequesters(
    focusRequester: FocusRequester,
    nextFocusRequester: FocusRequester
) {
    var state by rememberSaveable {
        mutableStateOf("Focus Transition Test")
    }
    TextField(
        value = state,
        onValueChange = { text -> state = text },
        // Here it is what you want...
        modifier = Modifier
            .focusOrder(focusRequester) {
                nextFocusRequester.requestFocus()
            }
        ,
        keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next)
    )
}

I get this code from here. It didn't solve the tab issue though... :(

like image 22
nglauber Avatar answered Nov 12 '22 22:11

nglauber


I'm currently on compose_version = '1.0.2'.

Focus is not moved when you press the next button because, although the default keyboard action is to move focus to the next one, compose seems doesn't know which one should be the next one. Creating FocusRequester s for each item and set their focus order via Modifier.focusOrder() {} could work (btw, no need to set the keyboardActions's onNext to request focus if you are choosing this way), but since your TextFields are in the same Column, you can just set keyboardActions to tell compose move the focus to the one in the down direction. Something like:

        Column {
            val focusManager = LocalFocusManager.current
            TextField(
                value = "", onValueChange = {},
                keyboardOptions = KeyboardOptions( imeAction = ImeAction.Next ),
                keyboardActions = KeyboardActions(
                    onNext = { focusManager.moveFocus(FocusDirection.Down) }
                )
            )
            TextField(
                value = "", onValueChange = {},
                keyboardOptions = KeyboardOptions( imeAction = ImeAction.Next ),
                keyboardActions = KeyboardActions(
                    onNext = { focusManager.moveFocus(FocusDirection.Down) }
                )
            )
            TextField(
                value = "", onValueChange = {},
                keyboardOptions = KeyboardOptions( imeAction = ImeAction.Next ),
                keyboardActions = KeyboardActions(
                    onNext = { focusManager.moveFocus(FocusDirection.Down) }
                )
            )
        }

After you did this, the next button on the IME keyboard should works.

For the Tab key, since TextField doesn't deal with Tab key automately, so you might want to use focusManager inside Modifier.onKeyEvent{} to move focus in the same way as the above example did.

like image 20
Gary Wang Avatar answered Nov 12 '22 21:11

Gary Wang


About the order you can check the @nglauber answer.

To use the Tab key you can use the onKeyEvent modifier.

TextField(
    modifier = Modifier
        .focusRequester(focusRequester)
        .onKeyEvent {
            if (it.key.keyCode == Key.Tab.keyCode){
                focusRequesterNext.requestFocus()
                true //true -> consumed
            } else false },
    value = text,
    onValueChange = { it -> text = it },
    keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
    keyboardActions = KeyboardActions(
        onNext = {focusRequesterNext.requestFocus()}
        )
)
like image 27
Gabriele Mariotti Avatar answered Nov 12 '22 21:11

Gabriele Mariotti


Set singleLine , please try

OutlinedTextField(
...
singleLine = true,
)

Simple example

@Composable
fun Test() {
    val focusManager = LocalFocusManager.current
    var text1 by remember {
        mutableStateOf("")
    }
    var text2 by remember {
        mutableStateOf("")
    }
    Column() {
        OutlinedTextField(value = text1, onValueChange = {
            text1 = it
        },
            keyboardOptions = KeyboardOptions(
                imeAction = ImeAction.Next,
                keyboardType = KeyboardType.Text
            ),
            keyboardActions = KeyboardActions(
                onNext ={
                    focusManager.moveFocus(FocusDirection.Down)
                }
            ),
            singleLine = true
            )
        OutlinedTextField(value = text2, onValueChange = {
            text2 = it
        },
            keyboardOptions = KeyboardOptions(
                imeAction = ImeAction.Next,
                keyboardType = KeyboardType.Text
            ),
            keyboardActions = KeyboardActions(
                onNext ={
                    focusManager.moveFocus(FocusDirection.Down)
                }
            ),
            singleLine = true
        )
    }
}
like image 20
Yshh Avatar answered Nov 12 '22 20:11

Yshh