Is there any way to programmatically scroll LazyColumn
to some item in the list? I thought that it can be done by hoisting the LazyColumn
argument state: LazyListState = rememberLazyListState()
but I have no idea how I can change this state e.g. on Button click.
The LazyListState
supports the scroll position via
- the
scrollToItem()
function, which ‘immediately’ snaps the scroll position, animateScrollToItem()
which scrolls using an animation
Something like:
val listState = rememberLazyListState()
// Remember a CoroutineScope to be able to launch
val coroutineScope = rememberCoroutineScope()
LazyColumn(state = listState) {
// ...
}
Button (
onClick = {
coroutineScope.launch {
// Animate scroll to the 10th item
listState.animateScrollToItem(index = 10)
}
}
){
Text("Click")
}
waitForFirstLayout
, which never continues. Any idea what could be wrong? –
Boneset coroutineScope
you can launch via LaunchedEffect(true) { listState.animateScrollToItem(index = 10) }
. –
Huelva In Compose 1.0.0-alpha07, There is no public API, But some internal API is there to LazyListState#snapToItemIndex
.
/**
* Instantly brings the item at [index] to the top of the viewport, offset by [scrollOffset]
* pixels.
*
* Cancels the currently running scroll, if any, and suspends until the cancellation is
* complete.
*
* @param index the data index to snap to
* @param scrollOffset the number of pixels past the start of the item to snap to
*/
@OptIn(ExperimentalFoundationApi::class)
suspend fun snapToItemIndex(
@IntRange(from = 0)
index: Int,
@IntRange(from = 0)
scrollOffset: Int = 0
) = scrollableController.scroll {
scrollPosition.update(
index = DataIndex(index),
scrollOffset = scrollOffset,
// `true` will be replaced with the real value during the forceRemeasure() execution
canScrollForward = true
)
remeasurement.forceRemeasure()
}
Maybe in the upcoming release, we can see the updates.
Here is my code that makes sticky headers, list and scroll
@ExperimentalFoundationApi
@Composable
private fun ListView(data: YourClass) {
//this is to remember state, internal API also use the same
val state = rememberLazyListState()
LazyColumn(Modifier.fillMaxSize(), state) {
itemsIndexed(items = data.list, itemContent = { index, dataItem ->
ItemView(dataItem)// your row
})
// header after some data, according to your condition
stickyHeader {
ItemDecoration()// compose fun for sticky header
}
// More items after header
itemsIndexed(items = data.list2, itemContent = { index, dataItem ->
ItemView(dataItem)// your row
})
}
// scroll to top
// I am scrolling to top every time data changes, use accordingly
CoroutineScope(Dispatchers.Main).launch {
state.snapToItemIndex(0, 0)
}
}
}
try with
lazyListState.animateScrollToItem(lazyListState.firstVisibleItemIndex)
@Composable
fun CircularScrollList(
value: Long,
onValueChange: () -> Unit = {}
) {
val lazyListState = rememberLazyListState()
val scope = rememberCoroutineScope()
val items = CircularAdapter(listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9))
scope.launch { lazyListState.scrollToItem(items.midIndex()) }
LazyColumn(
modifier = Modifier
.requiredHeight(height = 120.dp)
.border(width = 1.dp, color = Color.Black),
state = lazyListState,
) {
items(items) {
if (!lazyListState.isScrollInProgress) {
scope.launch {
lazyListState.animateScrollToItem(lazyListState.firstVisibleItemIndex)
}
}
Text(
text = "$it",
modifier = Modifier.requiredHeight(40.dp),
style = TextStyle(fontSize = 30.sp)
)
}
}
}
class CircularAdapter(
private val content: List<Int>
) : List<Int> {
fun midIndex(): Int = Int.MAX_VALUE / 2 + 6
override val size: Int = Int.MAX_VALUE
override fun contains(element: Int): Boolean = content.contains(element = element)
override fun containsAll(elements: Collection<Int>): Boolean = content.containsAll(elements)
override fun get(index: Int): Int = content[index % content.size]
override fun indexOf(element: Int): Int = content.indexOf(element)
override fun isEmpty(): Boolean = content.isEmpty()
override fun iterator(): Iterator<Int> = content.iterator()
override fun lastIndexOf(element: Int): Int = content.lastIndexOf(element)
override fun listIterator(): ListIterator<Int> = content.listIterator()
override fun listIterator(index: Int): ListIterator<Int> = content.listIterator(index)
override fun subList(fromIndex: Int, toIndex: Int): List<Int> =
content.subList(fromIndex, toIndex)
}
If someone needs to do this without any user interaction, you can listen for the LazyList
's loadState
. Google has recommended it here.
In my case, I needed to scroll to the top after prepending items. Because I use a Mediator
, just filtering by the "main" loadState.prepend
wasn't enough, so this is what worked for me. It should work for other similar operations as well.
LaunchedEffect(listState) {
snapshotFlow { content.loadState }
// We are only interested on the top items being added (prepend operation)
.distinctUntilChangedBy { it.prepend }
// These may differ and update at different times, so we want to make sure all of them
// are updated to NotLoading before proceeding
.filter {
it.prepend is LoadState.NotLoading &&
it.source.prepend is LoadState.NotLoading &&
it.mediator?.prepend is LoadState.NotLoading
}
.collect { listState.scrollToItem(0) }
}
I have solved 2 way
one way: I choosed this best way
LaunchedEffect(true) {
repeat(Int.MAX_VALUE) {
delay(TIME_DELAY_BANNER)
pagerState.animateScrollToPage(page = it % pagerState.pageCount)
}
}
two way:
var index = pagerState.currentPage
LaunchedEffect(true) {
while (true) {
delay(TIME_DELAY_BANNER)
if (index == pagerState.pageCount) {
index = 0
}
pagerState.animateScrollToPage(page = index++)
}
}
This is working solution if you want to do this in viewModel. You probably want to use delay when you call it right after recomposition call:
private val _lazyColumnScrollState = MutableStateFlow(LazyListState())
val lazyColumnScrollState get() = _lazyColumnScrollState.asStateFlow()
private fun scrollUp(itemIndex: Int) {
viewModelScope.launch {
// Delay: need to wait for the content recomposition
delay(250)
_lazyColumnScrollState.value.scrollToItem(itemIndex)
}
}
I solved it like this
@Composable
fun LazyColumDeItemLineaEstIyF(
listadoDeParadas: List<LineasYIndice>,
onConfirm: (Int) -> Unit,
preselectedPos: Int,
modifier: Modifier = Modifier
) {
val listState = rememberLazyListState()
val coroutineScope = rememberCoroutineScope()
var unaVez=true
LaunchedEffect(unaVez) {
coroutineScope.launch {
listState.animateScrollToItem(index = preselectedPos)
unaVez=false
}
}
LazyColumn(
state=listState,
verticalArrangement = Arrangement.spacedBy(0.dp),
contentPadding = PaddingValues(
horizontal = 1.dp,
vertical =1.dp
),
) {
items(
items= listadoDeParadas,
) { item ->
ItemLineaGM(
item= item,
selected = (item.id== preselectedPos),
onConfirm=onConfirm
)
}
}
}
© 2022 - 2024 — McMap. All rights reserved.