0

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.

FirstVisibleItem

@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())
        )
    }
}

Percentage scroll

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
        }
General Grievance
  • 4,555
  • 31
  • 31
  • 45

0 Answers0