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* }
The focus restoration API is now improved and accepts a fallback focus requester which will be used when visiting a unvisited list. Because of this new change, we now don't need the modifier factory that I created earlier.
val firstItemToGainFocusFr = remember { FocusRequester() }
TvLazyRow(
modifier = Modifier.focusRestorer { firstItemToGainFocusFr }
) {
item {
Button(
onClick = {},
modifier = Modifier.focusRequester(firstItemToGainFocusFr)
) {
Text("My Button 1")
}
}
item { Button(onClick = {}) { Text("My Button 2") } }
item { Button(onClick = {}) { Text("My Button 3") } }
// ...
}
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.