Search code examples
androidandroid-jetpack-composediscrete-mathematicsandroid-jetpack-compose-list

Unexplained Snapping Behaviour In A Programmatically Scrolled "Lazy List""


I stumbled upon this issue while trying to solve a case. To summarise, the patient needed complete control over the 'speed' of the scroll. Now, Android's sensible defaults have the item-scroll follow the user's finger at all times, but apparently an un-shared condition/requirement of this patient lead them to want the user scrolling further than the item actually scrolls. So, I suggested, as originally proposed by David in his solution, to disable the user's direct control over the lazy list, and manually scroll it by observing events from a touch detection callback, like the ones provided by the pointerInput(...) Modifier. Hence, the implementation:

@Composable
fun LazyStack(sdd: Int = 1) { // Scroll-Distance-Divisor
    val lazyStackState = rememberLazyListState()
    val lazyStackScope = rememberCoroutineScope()
    LazyColumn(
        modifier = Modifier.pointerInput(Unit) {
            detectVerticalDragGestures { _, dragAmount ->
                lazyStackScope.launch {
                    lazyStackState.scrollBy(-dragAmount / sdd)
                }
            }
        },
        state = lazyStackState,
        userScrollEnabled = false
    ) {
        items((0..500).map { LoremIpsum(randomInt(5)) }) {
            Text(text = it.values.joinToString(" "))
        }
    }
}

Now, the treatment actually worked, except a .. tiny side-effect, apparently.

You see, the list actually scrolls perfectly as intended for the most part. For example, if I pass the sdd parameter as 2, the scroll distance is reduced to half its original value. But, the side-effect is "snapping".

Apparently while scrolling smoothly through the list, there are certain "points" on both sides of which, the list "snaps", WHILE THE USER HAS THIER INPUT POINTER IN USE (a finger, a mouse, or any other pointers, are actively "holding" the list). Now, this is a point, which, if you cross in either direction, the list snaps, and apparently by the exact same value every single time.

The list above uses Lorem Ipsum as a convention, but perhaps a numbered list would help the diagnose the problem.

So, a synopsis of the symptoms:

There seem to be certain "points" in the list, occurring at inconsistent intervals/positions throughout the list/screen which tend to make the list instantly scroll through a lot of pixels. Once discovered, the points are consistently present and can be used to re-produce the symptom unless recomposed. The value of the "snap" is apparently not returned to be a massive float when continuously logging the value returned by the scrollBy method. This makes the issue untraceable by regular diagnostic techniques, which is why we, the diagnosticians are here.

You have the history, you have the symptoms. Let the differential diagnosis begin.


Solution

  • The snapping behavior is caused by incorrectly used randomInt(5), within a composable. List generation should be inside the viewmodel then there is no regeneration of the list during scrolling and everything works perfectly.

    Also using detectVerticalDragGestures does not work as smoothly as my original suggestion of using modfiier.scrollable(...) but maybe that's a desired effect.