1

I am trying to implement a TVLazyRow where when we focus on the row from some other composable above the row it must always focus on the first item first. Currently, when pressing down, the focus goes to whichever item in the row is directly below the composable above. How can I achieve this behaviour?

Here is my code for more context:

val tvListState = rememberTvLazyListState()
val coScope = rememberCoroutineScope()

TvLazyRow(
        horizontalArrangement = Arrangement.spacedBy(15.dp),
        state = tvListState,
        modifier = modifier
            .fillMaxHeight()
            .padding(end = 5.dp)
            .onFocusChanged {
                if (it.isFocused) {
                    coScope.launch {
                        tvListState.scrollToItem(0)
                    }
                }
            }, pivotOffsets = PivotOffsets(0f)
) { *items* }
vighnesh153
  • 4,354
  • 2
  • 13
  • 27
Rahul Rawat
  • 103
  • 6

1 Answers1

4

You can make use of the focusRestoration APIs which were released recently in the alpha version of compose foundation. Reference: saveFocusedChild() and restoreFocusedChild()

You can tap into the focusProperties and when the focus enters the container, you can check if there was a previously saved focused child. If yes, you transfer focus to that child, else, you transfer focus to the first child. While exiting the container, you can save the previously focused child.

You can create the following modifier factory which will abstract away this logic for you:

data class FocusRequesterModifiers(
    val parentModifier: Modifier,
    val childModifier: Modifier
)

@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun createFocusRestorationModifiers(): FocusRequesterModifiers {
    val focusRequester = remember { FocusRequester() }
    val childFocusRequester = remember { FocusRequester() }

    val parentModifier = Modifier
        .focusRequester(focusRequester)
        .focusProperties {
            exit = {
                focusRequester.saveFocusedChild()
                FocusRequester.Default
            }
            enter = {
                if (!focusRequester.restoreFocusedChild())
                    childFocusRequester
                else
                    FocusRequester.Cancel
            }
        }

    val childModifier = Modifier.focusRequester(childFocusRequester)

    return FocusRequesterModifiers(parentModifier, childModifier)
}

With the above factory in place, you can make use of it in your lazy containers like following:

val modifiers = createFocusRestorationModifiers()

TvLazyRow(
  modifier = Modifier.then(modifiers.parentModifier)
) {
  item { 
    Button(
      onClick = {}, 
      modifier = Modifier.then(modifiers.childModifier)
    ) { 
      Text("My Button 1") 
    } 
  }
  item { Button(onClick = {}) { Text("My Button 2") } }
  item { Button(onClick = {}) { Text("My Button 3") } }
  // ...
}

Notice, in the above example usage that we just need to assign the parent modifier to the TvLazyRow container and the child modifier to the first item. You can choose to assign it to the second or third item as well in case you desire that the second or third item should gain focus for the first time.

vighnesh153
  • 4,354
  • 2
  • 13
  • 27