Compose TV scrolling with D-PAD

187 Views Asked by At

I have this code

LazyVerticalStaggeredGrid(
    columns = StaggeredGridCells.Fixed(2),
    modifier = modifier.fillMaxSize(),
    contentPadding = PaddingValues(
        top = 16.dp,
        end = 12.dp,
        bottom = 16.dp,
    ),
    horizontalArrangement = Arrangement.spacedBy(16.dp),
    verticalItemSpacing = 16.dp,
    userScrollEnabled = true,
) {
    item {
        data.moreInformationText?.let { morInformationText ->
            Text(
                text = morInformationText,
                style = MaterialTheme.typography.bodyMedium.copy(
                    color = Color(context.partnerTheme.themePrimaryTint0),
                ),
            )
        }
    }

The problem is that scrolling with mouse is working but since this is a TV the scrolling with the D-pad doesn't work.

The moreinformationText is really long so we need to scroll that.

I've tried to use simply a

Column {
        Text(
            text = data.moreInformationText!!,
            modifier = Modifier.verticalScroll(rememberScrollState())
        )
    }

Without any grid but still didn't work.

1

There are 1 best solutions below

3
On

On a TV, users primarily interact with D-PAD. Using D-PAD, you move focus from 1 item to another. Because your text is not focusable, D-PAD cannot move focus anywhere and hence nothing is scrolling. You probably want to add some focusable items on the screen like buttons, cards, etc. On a side note, displaying very long text on a TV is not a good UX. You should probably look for some alternative design.

If you still want to have long block of text, you can choose to do the following:

  1. Break down the text into smaller chunks and make each of the chunk a separate focusable Text component. Wrap it in a scrollable container.
LazyColumn {
  item {
    Text("Chunk 1", Modifider.focusable())
  }
  item {
    Text("Chunk 2", Modifider.focusable())
  }
  ...
}
  1. Make use of the onKeyEvent modifier to listen to key events on the long text block and using this, programmatically scroll the container as shown below.
@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun App() {
    val someHugeText = (1..10000).joinToString(" ")
    val scrollAmount = 100f
    val scrollState = rememberScrollState()
    val cs = rememberCoroutineScope()

    fun scroll(reverse: Boolean = false) {
        cs.launch {
            scrollState.scrollBy(if (reverse) -scrollAmount else scrollAmount)
        }
    }

    Box(
        modifier = Modifier
            .fillMaxSize()
            .verticalScroll(scrollState)
            .onKeyEvent {
                if (it.nativeKeyEvent.action == ACTION_DOWN) {
                    return@onKeyEvent KeyEventPropagation.ContinuePropagation
                }
                if (it.nativeKeyEvent.keyCode == KEYCODE_DPAD_DOWN) {
                    scroll(false)
                    return@onKeyEvent KeyEventPropagation.StopPropagation
                }
                if (it.nativeKeyEvent.keyCode == KEYCODE_DPAD_UP) {
                    scroll(true)
                    return@onKeyEvent KeyEventPropagation.StopPropagation
                }
                return@onKeyEvent KeyEventPropagation.ContinuePropagation
            }
            .background(Color.Yellow)
            .clickable {
                println("Pikachu: scrollState value=${scrollState.value} maxValue=${scrollState.maxValue}")
            }
        ,
    ) {
        Text(text = someHugeText)
    }
}

internal object KeyEventPropagation {
    const val StopPropagation = true
    const val ContinuePropagation = false
}