Scroll Two Lazy Scrollers Together

2.5k Views Asked by At

This question has been asked before, but in different forms, regarding some specific use cases, and so far there has been no answer. I finally got it working, so I am sharing this here, but this should not be marked as a duplicate since all the previous questions specify specific stuff, like Columns with scrollable Modifiers, or LazyRows, but this will resolve all the issues in general, I mean all the lazy-scrollers, and hopefully even scrollable containers in general. I'll post the answer so this is just a piece of knowledge that I wished to share with the community, also, any improvements are welcome, of course.

3

There are 3 best solutions below

4
On BEST ANSWER

This is the full working solution:-

@Composable
fun DUME() {

    val stateRowX = rememberLazyListState() // State for the first Row, X
    val stateRowY = rememberLazyListState() // State for the second Row, Y

    Column { // Placing two Lazy Rows one above the other for the example

        LazyRow(state = stateRowY) {
            items(LoremIpsum(10).values.toList()) {
                Text(it)
            }
        }

        LazyRow(state = stateRowX) {
            items(LoremIpsum(10).values.toList()) {
                Text(text = it)
            }
        }

    }

    //This might seem crazy

    LaunchedEffect(stateRowX.firstVisibleItemScrollOffset) {
        stateRowY.scrollToItem(
            stateRowX.firstVisibleItemIndex,
            stateRowX.firstVisibleItemScrollOffset
        )
    }

    LaunchedEffect(stateRowY.firstVisibleItemScrollOffset) {
        stateRowX.scrollToItem(
            stateRowY.firstVisibleItemIndex,
            stateRowY.firstVisibleItemScrollOffset
        )
    }
}

The items import here is : androidx.compose.foundation.lazy.items, this accepts a list instead of a number (the size).

0
On

The above answer by Richard is good but it creates lags when scrolling because of the loop it creates as described by Ivan. The solution is simple for that problem

LaunchedEffect(stateRowX.firstVisibleItemScrollOffset) {
    if (!stateRowY.isScrollInProgress) {
        stateRowY.scrollToItem(
            stateRowX.firstVisibleItemIndex,
            stateRowX.firstVisibleItemScrollOffset
        )
    }
}

LaunchedEffect(stateRowY.firstVisibleItemScrollOffset) {
    if (!stateRowX.isScrollInProgress) {
        stateRowX.scrollToItem(
            stateRowY.firstVisibleItemIndex,
            stateRowY.firstVisibleItemScrollOffset
        )
    }
}

Now it will not lag while scrolling.

4
On

Another working approach, which can also be applied to more than two lazy scrollers.

@Composable
fun DUME() {

    val stateRowX = rememberLazyListState() // State for the first Row, X
    val stateRowY = rememberLazyListState() // State for the second Row, Y
    val scope = rememberCoroutineScope()
    val scrollState = rememberScrollableState { delta ->
        scope.launch {
            stateRowX.scrollBy(-delta)
            stateRowY.scrollBy(-delta)
        }
        delta
    }

    Column(
        modifier = Modifier.scrollable(scrollState, Orientation.Horizontal, flingBehavior = ScrollableDefaults.flingBehavior())
    ) { 

        LazyRow(
            state = stateRowX,
            userScrollEnabled = false
        ) {
            items(LoremIpsum(10).values.toList()) {
                Text(text = it)
            }
        }

        LazyRow(
            state = stateRowY,
            userScrollEnabled = false
        ) {
            items(LoremIpsum(10).values.toList()) {
                Text(text = it)
            }
        }

    }
}