Rok Oblak
07/23/2024, 8:02 AMStylianos Gakis
07/23/2024, 8:20 AMStylianos Gakis
07/23/2024, 8:31 AMRok Oblak
07/23/2024, 8:38 AMyoussef hachicha
07/23/2024, 8:53 AM@Composable
fun Modifier.drawVerticalScrollbar(
state: LazyListState,
reverseScrolling: Boolean = false,
thickness: Dp = 4.dp,
color: Color = MaterialTheme.colorScheme.onPrimaryContainer,
): Modifier = drawScrollbar(state, Orientation.Vertical, reverseScrolling, thickness, color)
@Composable
private fun Modifier.drawScrollbar(
state: LazyListState,
orientation: Orientation,
reverseScrolling: Boolean,
thickness: Dp = 4.dp,
color: Color = MaterialTheme.colorScheme.onPrimaryContainer,
): Modifier =
drawScrollbar(
orientation,
reverseScrolling,
color,
) { reverseDirection, atEnd, indicatorColor, alpha ->
val layoutInfo = state.layoutInfo
val viewportSize = layoutInfo.viewportEndOffset - layoutInfo.viewportStartOffset
val items = layoutInfo.visibleItemsInfo
val itemsSize = items.sumOf { it.size }
if (items.size < layoutInfo.totalItemsCount || itemsSize > viewportSize) {
val estimatedItemSize = if (items.isEmpty()) 0f else itemsSize.toFloat() / items.size
val totalSize = estimatedItemSize * layoutInfo.totalItemsCount
val canvasSize = if (orientation == Orientation.Horizontal) size.width else size.height
val thumbSize = viewportSize / totalSize * canvasSize
val startOffset =
if (items.isEmpty())
0f
else
items.first().run {
(estimatedItemSize * index - offset) / totalSize * canvasSize
}
drawScrollbar(
orientation,
reverseDirection,
atEnd,
indicatorColor,
alpha,
thumbSize,
startOffset,
thickness,
)
}
}
@Composable
private fun Modifier.drawScrollbar(
orientation: Orientation,
reverseScrolling: Boolean,
barColor: Color = MaterialTheme.colorScheme.onPrimaryContainer,
onDraw: DrawScope.(
reverseDirection: Boolean,
atEnd: Boolean,
color: Color,
alpha: () -> Float,
) -> Unit,
): Modifier =
composed {
val scrolled =
remember {
MutableSharedFlow<Unit>(
extraBufferCapacity = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST,
)
}
val nestedScrollConnection =
remember(orientation, scrolled) {
object : NestedScrollConnection {
override fun onPostScroll(
consumed: Offset,
available: Offset,
source: NestedScrollSource,
): Offset {
val delta =
if (orientation == Orientation.Horizontal) consumed.x else consumed.y
if (delta != 0f) scrolled.tryEmit(Unit)
return Offset.Zero
}
}
}
val alpha = remember { Animatable(0f) }
LaunchedEffect(scrolled, alpha) {
scrolled.collectLatest {
alpha.snapTo(1f)
delay(ScrollBarFadeDuration.toLong())
alpha.animateTo(0f, animationSpec = FadeOutAnimationSpec)
}
}
val isLtr = LocalLayoutDirection.current == LayoutDirection.Ltr
val reverseDirection =
if (orientation == Orientation.Horizontal) {
if (isLtr) reverseScrolling else !reverseScrolling
} else {
reverseScrolling
}
val atEnd = if (orientation == Orientation.Vertical) isLtr else true
Modifier
.nestedScroll(nestedScrollConnection)
.drawWithContent {
drawContent()
onDraw(reverseDirection, atEnd, barColor, alpha::value)
}
}
youssef hachicha
07/23/2024, 8:54 AMStylianos Gakis
07/23/2024, 9:32 AMyoussef hachicha
07/23/2024, 9:41 AM