Reputation: 6817
For a LazyRow
, or Column
, how to I know whether the user has scrolled left or right ( or up or... you know). We do not need callbacks in compose for stuff like that, since mutableStateOf
objects always anyway trigger recompositions so I just wish to know a way to store it in a variable. Okay so there's lazyRowState.firstVisibleItemScrollOffset
, which can be used to mesaure it in a way, but I can't find a way to store its value first, and then subtract the current value to retrieve the direction (based on positive or negative change). Any ideas on how to do that, thanks
Upvotes: 15
Views: 8734
Reputation: 8719
There are now APIs for this in Compose.
lazyListState.lastScrolledBackward
& lazyListState.lastScrolledForward
Upvotes: 1
Reputation: 11
For LazyColumn with specifying the minimum detection step:
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.State
import androidx.compose.runtime.produceState
import kotlinx.coroutines.delay
import kotlin.math.absoluteValue
@Composable
fun LazyListState.verticalDebouncedScrollState(
minOffsetForDetection: Int = 10
): State<VerticalScrollDirection> = produceState(
initialValue = VerticalScrollDirection.None
) {
var previousScrollOffset = firstVisibleItemScrollOffset
var previousItemIndex = firstVisibleItemIndex
while (true) {
delay(300L)
val capturedItemIndex = firstVisibleItemIndex
val capturedScrollOffset = firstVisibleItemScrollOffset
val offsetDelta = capturedScrollOffset - previousScrollOffset
val itemIndexDelta = capturedItemIndex - previousItemIndex
value = when {
offsetDelta.absoluteValue < minOffsetForDetection && itemIndexDelta == 0 -> VerticalScrollDirection.None
itemIndexDelta > 0 -> VerticalScrollDirection.Up
itemIndexDelta == 0 && offsetDelta > 0 -> VerticalScrollDirection.Up
else -> VerticalScrollDirection.Down
}
previousScrollOffset = capturedScrollOffset
previousItemIndex = capturedItemIndex
}
}
enum class VerticalScrollDirection {
None, Up, Down
}
Upvotes: 1
Reputation: 735
Due to false positives when the firstVisibleItemIndex
is changed, I ended up with the following version:
@Composable
fun LazyListState.isScrollingUp(): Boolean {
var previousItemIndex by remember(this) { mutableIntStateOf(firstVisibleItemIndex) }
var previousScrollOffset by remember(this) { mutableIntStateOf(firstVisibleItemScrollOffset) }
var scrollingUp by remember(this) { mutableStateOf(true) }
return remember(this) {
derivedStateOf {
if (previousItemIndex == firstVisibleItemIndex) {
scrollingUp = firstVisibleItemScrollOffset - previousScrollOffset <= 0
} else {
previousItemIndex = firstVisibleItemIndex
}
previousScrollOffset = firstVisibleItemScrollOffset
scrollingUp
}
}.value
}
The scroll state is cached and preserved while firstVisibleItemIndex
is changed.
Upvotes: -1
Reputation: 2099
This extensions functions can be very handy for you:
fun LazyListState.isFirstItemVisible() = firstVisibleItemIndex == 0
@Composable
fun LazyListState.isScrollingDown(): Boolean {
val offset by remember(this) { mutableStateOf(firstVisibleItemScrollOffset) }
return remember(this) { derivedStateOf { (firstVisibleItemScrollOffset - offset) > 0 } }.value
}
@Composable
fun LazyListState.isScrollingUp(): Boolean {
val offset by remember(this) { mutableStateOf(firstVisibleItemScrollOffset) }
return remember(this) { derivedStateOf { (firstVisibleItemScrollOffset - offset) < 0 } }.value
}
Upvotes: 2
Reputation: 363439
Currently there is no built-in function to get this info from LazyListState
.
You can use something like:
@Composable
private fun LazyListState.isScrollingUp(): Boolean {
var previousIndex by remember(this) { mutableStateOf(firstVisibleItemIndex) }
var previousScrollOffset by remember(this) { mutableStateOf(firstVisibleItemScrollOffset) }
return remember(this) {
derivedStateOf {
if (previousIndex != firstVisibleItemIndex) {
previousIndex > firstVisibleItemIndex
} else {
previousScrollOffset >= firstVisibleItemScrollOffset
}.also {
previousIndex = firstVisibleItemIndex
previousScrollOffset = firstVisibleItemScrollOffset
}
}
}.value
}
Then just use listState.isScrollingUp()
to get the info about the scroll.
This snippet is used in a google codelab.
Upvotes: 41
Reputation: 6817
Got it
{ //Composable Scope
val lazyRowState = rememberLazyListState()
val pOffset = remember { lazyRowState.firstVisibleItemScrollOffset }
val direc = lazyRowState.firstVisibleItemScrollOffset - pOffset
val scrollingRight /*or Down*/ = direc > 0 // Tad'aa
}
Upvotes: 1