Hamidreza Sahraei
Hamidreza Sahraei

Reputation: 627

Current scroll position value in pixels in LazyColumn Jetpack Compose

I want to have the total scroll position in px in LazyColumn. The column itself has a ScrollState which has a value (Described in the document as the current scroll position value in pixels), But LazyColumn ScrollState hasn't this field, I want the exact equivalent of Column ScrollState scroll position value for LazyColumn, How I can achieve that with the help of LazyScrollState?

Upvotes: 15

Views: 17756

Answers (7)

Jacek Kwiecień
Jacek Kwiecień

Reputation: 12649

I'm amazed this has to be complicated. But I think I managed to simplify it as much as possible.

@Composable
fun LazyColumnScrollObserver(
    listState: LazyListState,
    onScrollOffsetChangeded: (Int) -> Unit = {}
) {
    val firstVisibleItemIndex by remember { derivedStateOf { listState.firstVisibleItemIndex } }
    val scrollOffset by remember { derivedStateOf { listState.firstVisibleItemScrollOffset } }
    var map: Map<Int, Int> by remember { mutableStateOf(emptyMap()) }
    map = map.toMutableMap().apply { put(firstVisibleItemIndex, scrollOffset) }
    val totalOffset by remember { derivedStateOf { map.entries.sumOf { it.value }  } }
    onScrollOffsetChangeded(totalOffset)
}

You can use this composable to get the total vertical scroll offset of LazyColumn

Upvotes: 0

Jo&#227;o Ferreira
Jo&#227;o Ferreira

Reputation: 1

I could get the maximum amount scrolled if all the items have the same size with this implementation:

val scrollOffset by remember {
    derivedStateOf {
        with (density) {
            lazyListState.firstVisibleItemIndex * width + lazyListState.firstVisibleItemScrollOffset
        }
    }
}

If you do not have a predefined width, you can find the width value using the onGloballyPositioned modifier in one of your items.

Here is an example:

    LazyRow(
        state = lazyListState
    ) {
        items(count) { index ->
            YourComposable(
                modifier = Modifier
                    .onGloballyPositioned { 
                        width = it.size.width
                    }
            )
        }
    }

Upvotes: 0

Jos&#233; Braz
Jos&#233; Braz

Reputation: 665

It is not possible to know the exact scroll position, but we can try!

@Composable
fun rememberCurrentOffset(state: LazyListState): androidx.compose.runtime.State<Int> {
    val position = remember { derivedStateOf { state.firstVisibleItemIndex } }
    val itemOffset = remember { derivedStateOf { state.firstVisibleItemScrollOffset } }
    val lastPosition = rememberPrevious(position.value)
    val lastItemOffset = rememberPrevious(itemOffset.value)
    val currentOffset = remember { mutableStateOf(0) }

    LaunchedEffect(position.value, itemOffset.value) {
        if (lastPosition == null || position.value == 0) {
            currentOffset.value = itemOffset.value
        } else if (lastPosition == position.value) {
            currentOffset.value += (itemOffset.value - (lastItemOffset ?: 0))
        } else if (lastPosition > position.value) {
            currentOffset.value -= (lastItemOffset ?: 0)
        } else { // lastPosition.value < position.value
            currentOffset.value += itemOffset.value
        }
    }

    return currentOffset
}

Using the rememberPrevious from Get previous value of state in Composable - Jetpack Compose solution also.

How to use

val scroll = rememberLazyListState()
val offset = rememberCurrentOffset(scroll)

// do any thing with the offset state

LazyColumn(
    state = scroll,
    modifier = Modifier.fillMaxSize()
) {
    ....
}

Upvotes: 8

Castaldi
Castaldi

Reputation: 701

I have created this simple LazyListState extension to get the current scroll position. Please note that this assumes that all items in the list have the same height.

fun LazyListState.scrollPos(): Int {
    val itemLengthInPx = layoutInfo.visibleItemsInfo.firstOrNull()?.size ?: 0
    return (firstVisibleItemIndex * itemLengthInPx) + firstVisibleItemScrollOffset
}

Upvotes: 0

Stanley Ko
Stanley Ko

Reputation: 3497

You can get it by firstVisibleItemScrollOffset.
('androidx.activity:activity-compose:1.3.1')

val listState = rememberLazyListState()
val itemHeight = with(LocalDensity.current) { 80.dp.toPx() } // Your item height
val scrollPos = listState.firstVisibleItemIndex * itemHeight + listState.firstVisibleItemScrollOffset

Text(text = "scroll: $scrollPos")

LazyColumn(state = listState) {
    // Your items here
}

Also, you can set the scroll position to listState, like this:

LaunchedEffect(key1 = "Key") {
    delay(1000) // To show scroll explicitly, set the delay
    listState.scrollBy(itemHeight * 2)
}

Upvotes: 1

John
John

Reputation: 6065

The scroll amount can be obtained, no calculation required, in the items themselves, by attaching an onGloballyPosition{ it.positionInParent()} modifier to one or more items.

Then, the items can do what they need to do with their own scroll position, such as offsetting some screen-drawing y coordinate.

Or, if you need the scroll offset in the parent LazyColumn, you could have one item (perhaps an item with zero height, though I've not tested that) at the very top of your list of items, that reports back its position to the parent (perhaps by updating a mutableState that was passed to the item by the parent) whenever it moves.

I had the same need and onGloballyPosition{ it.positionInParent()} addressed it very nicely.

Upvotes: 3

user496854
user496854

Reputation: 6830

The reason it exists in Column is because it knows about all the items (rows) when it's being generated.

But LazyColumn only displays a portion of the total views at any given moment, and when you scroll, it continuously re-generates the views on the screen (plus a couple above and/or below the visible area), so it never actually calculates the total height of all the rows. If you wanted to get the scroll position, you would have to manually calculate it. If the height of each row is the same, it'll work pretty well. But if the heights are different, then you won't be able to get the exact scroll position (your calculations will fluctuate depending on the height of the rows that are currently displayed). But here's what you have to do to calculate it yourself:

Calculate the total size of all the items

val totalItems = lazyListState.layoutInfo.totalItemsCount
val itemLengthInPx = lazyListState.layoutInfo.visibleItemsInfo.firstOrNull()?.size ?: 0
val totalLengthInPx = totalItems * itemLengthInPx 

Calculate the current scroll position

val scrollPos = (lazyListState.firstVisibleItemIndex * itemLengthInPx) / totalLengthInPx

But as I mentioned earlier, this calculation depends on the height of each item (itemLengthInPx), and it works perfectly if it's the same for all the views. But you can see how it'll fluctuate if each view has a different height

Upvotes: 0

Related Questions