Snap to an index Lazyrow
Asked Answered
P

4

11

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
                )
            }
        }
    }
Pinta answered 17/4, 2022 at 10:15 Comment(5)
You can use the HorizontalPager from accompanist library which has this fling behavior out-of-the-box and uses LazyRow internally. github.com/google/accompanist/tree/main/pagerJemy
is there no way to do it with built in functions from jetpack composePinta
This might helpSocialistic
Do you want to snap to the first visible index? or one view per screen like a pager?Figural
one view per screenPinta
J
12

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

Jemy answered 17/4, 2022 at 14:18 Comment(2)
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.Luteolin
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 hereJemy
K
23

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

Krishnakrishnah answered 20/12, 2022 at 19:3 Comment(3)
I want to snap my starting item, the first visible item and not the middle one, is there any way to do that sir?Veronique
@zaidkhan check out my answer https://mcmap.net/q/956481/-snap-to-an-index-lazyrowCaracole
This gets me ``` java.lang.IllegalStateException: AnimationVector cannot contain a NaN. AnimationVector1D: value = NaN. ```Durand
J
12

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

Jemy answered 17/4, 2022 at 14:18 Comment(2)
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.Luteolin
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 hereJemy
C
4

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 = {}
)
Caracole answered 30/8, 2023 at 7:29 Comment(1)
Unable to pass positionLayout to SnapLayoutInfoProvider as the expected type is SnapLayoutInfoProviderBonnice
W
3
val centerItemIndex by remember {
    derivedStateOf {
        val layoutInfo = listState.layoutInfo
        val visibleItems = layoutInfo.visibleItemsInfo
        val viewportCenter = (layoutInfo.viewportStartOffset + layoutInfo.viewportEndOffset) / 2

        visibleItems.minByOrNull { abs((it.offset + it.size / 2) - viewportCenter) }?.index ?: 0
    }
}
Woodson answered 11/1 at 10:2 Comment(1)
You can try this brotherWoodson

© 2022 - 2024 — McMap. All rights reserved.