androidandroid-jetpack-composeandroid-jetpack-compose-material3android-jetpack-compose-listandroid-jetpack-compose-tv

Jetpack Compose TvLazyColumn PivotOffset for items with different sizes


I am using TvLazyColumn composable with 7 items, and inside of each item has a title and a TvLazyRow with a list of video thumbnail images which are focusable.

Out of the 7 items, 6 have identical header style (1 row header text) so the padding between the header and the TvLazyRow is identical. However, one item has a different header style (multiple-row header: LIVE badge, title row, information row).

When I scroll up and down the TvLazyColumn using D-pad, I want the top of each column item to be aligning with the top of TvLazyColumn, regardless of the focused thumnail image's vertical position. This means I want the column pivot to be set on the top of the title of each column items, and not on the thumbnail images.

If all 7 items have the identical 1 row text header style, I can simply adjust PivotOffset to achieve this. But I can't do so because the one item has different header style. Could someone please help me achieving this desired behavior?

From here:

enter image description here

If I press DOWN on my D-Pad

This is what I want:

enter image description here

But this is what I get instead - Notice the LIVE badge goes out of the bound:

enter image description here

Here is the code snippets:

            TvLazyColumn(
                modifier = Modifier
                    .fillMaxWidth()
                    .fillMaxHeight(0.5f)
                    .border(width = 2.dp, color = Color.Red)
                    .align(Alignment.BottomStart),
                pivotOffsets = PivotOffsets(parentFraction = 0.3f, childFraction = 0.2f),
                contentPadding = PaddingValues(bottom = 100.dp),
            ) {
                .
                .
                .

                if (homeViewState.userHistoryList.isNotEmpty()) {
                    item {
                        WTvMediaPostRow(
                            postList = homeViewState.userHistoryList,
                            title = stringResource(id = ...),
                            onPostClick = onItemClick,
                            onItemFocus = onItemFocus,
                        )
                    }
                }

                if (homeViewState.liveList.isNotEmpty()) {
                    item {
                        WTvLivePostRow(
                            postList = homeViewState.liveList,
                            title = stringResource(id = ...),
                            showLiveBadge = true,
                            onPostClick = onItemClick,
                            onItemFocus = onItemFocus,
                        )
                    }
                }

                .
                .
                .
            }

I tried setting different pivotOffsets value for the one item with different header style and it sort of works - the offset gets applied ONLY AFTER I start scrolling the TvLazyRow inside the column item. I want the offset to be applied as soon as the thumbnail gets focused.

enter image description here

            var currentFocusRowType by remember { mutableStateOf(WTvHomeRowType.EMPTY) }

            TvLazyColumn(
                modifier = Modifier
                    .fillMaxWidth()
                    .fillMaxHeight(0.5f)
                    .border(width = 2.dp, color = Color.Red)
                    .align(Alignment.BottomStart),
                pivotOffsets = if (currentFocusRowType == WTvHomeRowType.ON_LIVE) {
                    PivotOffsets(parentFraction = 0.5f, childFraction = 0.2f)
                } else {
                    PivotOffsets(parentFraction = 0.3f, childFraction = 0.2f)
                },
            ) {
                .
                .
                .

                if (homeViewState.liveList.isNotEmpty()) {
                    item {
                        WTvLivePostRow(
                            postList = homeViewState.liveList,
                            title = stringResource(id = ...),
                            showLiveBadge = true,
                            onPostClick = onItemClick,
                            onItemFocus = { index, post ->
                                currentFocusRowType = WTvHomeRowType.ON_LIVE
                                onItemFocus.invoke(index, post)
                            },
                        )
                    }
                }

                .
                .
                .
             }

I tried setting different pivotOffsets value for the one item with different header style and it sort of works - the offset gets applied ONLY AFTER I start scrolling the TvLazyRow inside the column item. I want the offset to be applied as soon as the thumbnail gets focused.


Solution

  • In case anyone is facing the same issue, I was able to achieve the desired outcome by setting userScrollEnabled = false and use rememberTvLazyListState().scrollToItem() to manually scroll to each category when there's up & down D-pad input.

    Here is the code snippet:

                val tvLazyListState = rememberTvLazyListState()
                val homeCategorySize = remember { mutableIntStateOf(7) }
                val currentIndex = remember { mutableIntStateOf(0) }
    
                TvLazyColumn(
                    modifier = Modifier
                        .fillMaxWidth()
                        .fillMaxHeight(0.5f)
                        .align(Alignment.BottomStart)
                        .handleDPadKeyEvents(
                            onDown = {
                                coroutineScope.launch {
                                    currentIndex.intValue = (++currentIndex.intValue).coerceIn(0, homeCategorySize.intValue - 1)
                                    tvLazyListState.scrollToItem(currentIndex.intValue)
                                }
                                false
                            },
                            onUp = {
                                coroutineScope.launch {
                                    currentIndex.intValue = (--currentIndex.intValue).coerceIn(0, homeCategorySize.intValue - 1)
                                    tvLazyListState.scrollToItem(currentIndex.intValue)
                                }
                                false
                            },
                        ),
                    state = tvLazyListState,
                    userScrollEnabled = false,
                    contentPadding = PaddingValues(bottom = 100.dp),
                ) {
                   ...
                }
    

    .handleDPadKeyEvents is an extension function I created using Modifier.onPreviewKeyEvents to handle certain key inputs differently.

    The above method scrolls the TvLazyColumn to bring the view of the complete children as I wanted. Hope this helps to anyone facing similar issues.