Through using an example found here LazyListModifier I've tried to modify it to write a Line progress indicator but unable to make the indicator go all the way to the end or start. It seems within the LazyStateList
you are given the information about which view is visible; First or Last however making the line scroll smoothly to First or Last position is what I'm struggling to achieve.
I have modified the original post to instead use the width but when we get to the last item in the list as it's not the firstVisibleItem the line indicator will not update.
@Composable
fun Modifier.simpleHorizontalScrollbar(
state: LazyListState,
height: Dp = 8.dp
): Modifier {
return drawWithContent {
drawContent()
val firstVisibleElementIndex = state.layoutInfo.visibleItemsInfo.firstOrNull()?.index
if (firstVisibleElementIndex != null) {
val elementWidth = this.size.width / state.layoutInfo.totalItemsCount
val scrollbarOffsetX = firstVisibleElementIndex * elementWidth
+ state.firstVisibleItemScrollOffset / 4 /*Added to make the scroll smooth*/
drawRect(
color = Color.Red,
topLeft = Offset(scrollbarOffsetX, this.size.height),
size = Size(elementWidth, height.toPx())
)
}
}
}
Through another answer I found a means to get the visibility percentage of the view however this takes the visible on screen view and not an overall value of the total scroll.
Through using the percentage I can make it scroll smoothly and to the end but this is dependent on the lastVisibleView
being 100% visible so the line indicator will not start at index 0.
@Composable
fun Modifier.simpleHorizontalScrollbar(
state: LazyListState,
height: Dp = 8.dp,
): Modifier {
val currentIndex = remember { mutableStateOf(0) }
return drawWithContent {
drawContent()
val isLast = state.layoutInfo.visibleItemsInfo.last().index == state.layoutInfo.totalItemsCount - 1
val lastVisibleItem = state.layoutInfo.visibleItemsInfo.lastOrNull() ?: return@drawWithContent
var scrollPercentage = state.visibilityPercent(lastVisibleItem)
Log.d("Scroll - ", "Last Visible Index: ${lastVisibleItem.index}")
val visibleIndex = if (state.visibilityPercent(lastVisibleItem) == 1f && (currentIndex.value != lastVisibleItem.index || isLast)) {
if (!isLast) {
scrollPercentage = 0f
}
Log.d("Scroll -", "Next Index: ${lastVisibleItem.index}")
currentIndex.value = lastVisibleItem.index
lastVisibleItem.index
} else {
lastVisibleItem.index - 1
}
Log.d("Scroll -", "Percentage $scrollPercentage")
Log.d("Scroll -", "visibleIndex: $visibleIndex")
val elementWidth = this.size.width / state.layoutInfo.totalItemsCount
val offsetX = (elementWidth * scrollPercentage) + (visibleIndex * elementWidth)
val finalOffsetX = minOf(offsetX, this.size.width - elementWidth)
Log.d("Scroll -", "scroll Value: $finalOffsetX")
drawRect(
color = Color.Red,
topLeft = Offset(finalOffsetX, this.size.height),
size = Size(elementWidth, height.toPx())
)
}
}
I also have modified it to use the first positions scroll until it is off screen however the scroll for index 1 is then ignored as it will have already passed meaning when the visibleIndex
increases it will 'jump'. Adding the below:
val isFirst = state.layoutInfo.visibleItemsInfo.first().key == 0
val firstVisibleItem = state.layoutInfo.visibleItemsInfo.firstOrNull() ?: return@drawWithContent
val firstScrollPercentage = state.visibilityPercent(firstVisibleItem)
val visibleIndex = if (isFirst) {
scrollPercentage = abs(firstScrollPercentage - 1)
0
}