LazyNearestItemsRange.kt
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.compose.foundation.lazy.layout
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.snapshotFlow
import androidx.compose.runtime.snapshots.Snapshot
/**
* Calculate and memoize range of indexes which contains at least [extraItemCount] items near
* the first visible item. It is optimized to return the same range for small changes in the
* firstVisibleItem value so we do not regenerate the map on each scroll.
*
* @param firstVisibleItemIndex Provider of the first item index currently visible on screen.
* @param slidingWindowSize Number items user can scroll up to this number of items until we have to
* regenerate item mapping.
* @param extraItemCount The minimum amount of items near the first visible item we want
* to have mapping for.
* @return range of indexes with items near current the first visible position.
*/
@ExperimentalFoundationApi
@Composable
fun rememberLazyNearestItemsRangeState(
firstVisibleItemIndex: () -> Int,
slidingWindowSize: () -> Int,
extraItemCount: () -> Int
): State<IntRange> {
val state = remember(firstVisibleItemIndex, slidingWindowSize, extraItemCount) {
Snapshot.withoutReadObservation {
mutableStateOf(
calculateNearestItemsRange(
firstVisibleItemIndex(),
slidingWindowSize(),
extraItemCount()
)
)
}
}
LaunchedEffect(state) {
snapshotFlow {
calculateNearestItemsRange(
firstVisibleItemIndex(),
slidingWindowSize(),
extraItemCount()
)
}.collect {
state.value = it
}
}
return state
}
/**
* Returns a range of indexes which contains at least [extraItemCount] items near
* the first visible item. It is optimized to return the same range for small changes in the
* firstVisibleItem value so we do not regenerate the map on each scroll.
*/
private fun calculateNearestItemsRange(
firstVisibleItem: Int,
slidingWindowSize: Int,
extraItemCount: Int
): IntRange {
val slidingWindowStart = slidingWindowSize * (firstVisibleItem / slidingWindowSize)
val start = maxOf(slidingWindowStart - extraItemCount, 0)
val end = slidingWindowStart + slidingWindowSize + extraItemCount
return start until end
}