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.
Asked
Active
Viewed 1,828 times
2

Richard Onslow Roper
- 5,477
- 2
- 11
- 42
3 Answers
3
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).

Richard Onslow Roper
- 5,477
- 2
- 11
- 42
-
1Just want to translate the ` //This might seem crazy` It is actually very elegant. Basically it says: if lazy row X is updated (by detecting changes in the scrollable offset of the first item), we move Y to make it the same as X. https://developer.android.com/jetpack/compose/side-effects – Keenan Gebze Oct 21 '21 at 19:19
-
2You have recursion calls between states, one change calls another, another calls one etc until: 1. Whole this thing starts to lag. 2. Inertia is lost. Your solution have none at all. You need to block recursion calls. – Ivan Mitsura Nov 05 '21 at 22:56
-
I appreciate you sharing your thoughts here. Did you run the code on a device? It seems like recursion, but the blocks are executed very efficiently. I personally experienced no lag. As far as inertia is concerned, I'll check once, but I think even that was preserved here. – Richard Onslow Roper Nov 06 '21 at 01:40
-
This code works but if you have two lists, one with few items than other, then the smaller list will stop as soon as it reaches end. e.g.: lists are : val nums = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11) val chars = listOf('a', 'b', 'c'). So, I guess I am basically asking for how to scroll the smaller list beyond the first item until the end of longer list. Please let me know. – Jun 28 '22 at 18:24
3
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)
}
}
}
}

MrCurious
- 31
- 2
-
this looks to have more performance than the accepted answer. Also, I think it can become more clear by putting into a Box container. – Marco Antonio Jan 13 '23 at 20:36
-
@MrCurious i dont see userScrollEnabled = false in LazyRow. What is that? – anshul Jan 22 '23 at 20:52
-
@anshul You can find it here https://developer.android.com/reference/kotlin/androidx/compose/foundation/lazy/package-summary under the LazyRow section. – MrCurious Jan 24 '23 at 02:15
-
@MrCurious thanks for the reply. it is available with the latest version. But this solution doesn't seem to be working with a more complex use cases as i have here https://pastebin.com/FKMh0xhC – anshul Jan 26 '23 at 16:14
2
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.

Sunny
- 14,522
- 15
- 84
- 129