7

I am making a calendar with the help of a lazyRow. I now have the problem that I want the row to snap to the index after a certain scroll amount so it shouldn't be possible to be stuck in between indexes. Is there a way to do that?

    LazyRow(state = calendarViewModel.listState, modifier = Modifier.fillMaxWidth()) {
        calendarYears.forEach {
            items(it.months.count()) { index ->
                calendarViewModel.onEvent(CalendarEvent.ClickedMenuItem(index))
                CalendarRowItem(
                    modifier = Modifier.fillParentMaxWidth(),
                    calendarSize = it.months[index].amountOfDays,
                    initWeekday = it.months[index].startDayOfMonth.ordinal,
                    textColor = MaterialTheme.colors.secondaryVariant,
                    clickedColor = MaterialTheme.colors.primary,
                    textStyle = MaterialTheme.typography.body1
                )
            }
        }
    }
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
jens
  • 207
  • 2
  • 9

3 Answers3

13

Starting with compose 1.3.0 you can use the FlingBehavior that performs snapping of items to a given position:

val state = rememberLazyListState()

LazyRow(
    modifier = Modifier.fillMaxSize(),
    verticalAlignment = Alignment.CenterVertically,
    state = state,
    flingBehavior = rememberSnapFlingBehavior(lazyListState = state)
) {
  //item content
}

enter image description here

Gabriele Mariotti
  • 320,139
  • 94
  • 887
  • 841
10

You can use the HorizontalPager from accompanist library which provides this fling behavior out-of-the-box and it uses LazyRow internally.

Another option could be use the Snapper library created by @chris-banes

Add the dependency in your build.gradle.

dependencies {
    implementation "dev.chrisbanes.snapper:snapper:<version>"
}

and use it in your LazyRow.

val lazyListState = rememberLazyListState()

LazyRow(
    state = lazyListState,
    flingBehavior = rememberSnapperFlingBehavior(lazyListState),
) {
    // content
}

Result:

enter image description here

nglauber
  • 18,674
  • 6
  • 70
  • 75
  • How did you get the first item to snap to the middle of the screen? I've tried using the Snapper library and the first item doesn't scroll beyond the left edge of the LazyRow. – cincy_anddeveloper Mar 22 '23 at 04:26
  • I'm using `BoxWithConstraints` to get the screen width. Then then I'm using a `Layout` to place it in the center... See the full code of the example above [here](https://github.com/nglauber/JetpackComposePlayground/blob/master/app/src/main/java/br/com/nglauber/jetpackcomposeplayground/screens/RowSnapperScreen.kt) – nglauber Mar 27 '23 at 12:57
1

Building on top of Gabriele's answer, for snapping based on first visible item (side snapping to the start) we need to do the following:

val scrollState = rememberLazyListState()
val positionInLayout: Density.(Float, Float) -> Float = { _, _ ->
    // This value tells where to snap on the x axis within the viewport
    // Setting it to 0 results in snapping of the first visible item to the left side (or right side if RTL)
    0f
}
val snappingLayout = remember(scrollState) { SnapLayoutInfoProvider(scrollState, positionInLayout) }
val flingBehavior = rememberSnapFlingBehavior(snappingLayout)
LazyRow(
    modifier = modifier,
    state = scrollState,
    contentPadding = PaddingValues(horizontal = 1.5f.u()),
    flingBehavior = flingBehavior,
    content = {}
)
Nimrod Dayan
  • 3,050
  • 4
  • 29
  • 45