Fix Scrollbar when the list contains sticky header (#8181)
* Fix Scrollbar when the list contains sticky header * Fix VerticalFastScroller when the list contains sticky header * exposé
This commit is contained in:
parent
8500add09f
commit
fba244423f
3 changed files with 30 additions and 5 deletions
|
@ -32,6 +32,7 @@ import eu.kanade.presentation.components.BadgeGroup
|
||||||
import eu.kanade.presentation.components.EmptyScreen
|
import eu.kanade.presentation.components.EmptyScreen
|
||||||
import eu.kanade.presentation.components.LoadingScreen
|
import eu.kanade.presentation.components.LoadingScreen
|
||||||
import eu.kanade.presentation.components.ScrollbarLazyColumn
|
import eu.kanade.presentation.components.ScrollbarLazyColumn
|
||||||
|
import eu.kanade.presentation.components.Scroller.STICKY_HEADER_KEY_PREFIX
|
||||||
import eu.kanade.presentation.theme.header
|
import eu.kanade.presentation.theme.header
|
||||||
import eu.kanade.presentation.util.horizontalPadding
|
import eu.kanade.presentation.util.horizontalPadding
|
||||||
import eu.kanade.presentation.util.plus
|
import eu.kanade.presentation.util.plus
|
||||||
|
@ -85,7 +86,7 @@ private fun MigrateSourceList(
|
||||||
ScrollbarLazyColumn(
|
ScrollbarLazyColumn(
|
||||||
contentPadding = contentPadding + topPaddingValues,
|
contentPadding = contentPadding + topPaddingValues,
|
||||||
) {
|
) {
|
||||||
stickyHeader(key = "header") {
|
stickyHeader(key = STICKY_HEADER_KEY_PREFIX) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.background(MaterialTheme.colorScheme.background)
|
.background(MaterialTheme.colorScheme.background)
|
||||||
|
|
|
@ -42,9 +42,10 @@ import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.IntOffset
|
import androidx.compose.ui.unit.IntOffset
|
||||||
import androidx.compose.ui.unit.LayoutDirection
|
import androidx.compose.ui.unit.LayoutDirection
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.util.fastFirstOrNull
|
||||||
import androidx.compose.ui.util.fastForEach
|
import androidx.compose.ui.util.fastForEach
|
||||||
import androidx.compose.ui.util.fastMaxBy
|
import androidx.compose.ui.util.fastMaxBy
|
||||||
import eu.kanade.presentation.util.plus
|
import eu.kanade.presentation.components.Scroller.STICKY_HEADER_KEY_PREFIX
|
||||||
import kotlinx.coroutines.channels.BufferOverflow
|
import kotlinx.coroutines.channels.BufferOverflow
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
@ -53,6 +54,11 @@ import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws vertical fast scroller to a lazy list
|
||||||
|
*
|
||||||
|
* Set key with [STICKY_HEADER_KEY_PREFIX] prefix to any sticky header item in the list.
|
||||||
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun VerticalFastScroller(
|
fun VerticalFastScroller(
|
||||||
listState: LazyListState,
|
listState: LazyListState,
|
||||||
|
@ -386,7 +392,8 @@ private fun computeScrollRange(state: LazyGridState): Int {
|
||||||
private fun computeScrollOffset(state: LazyListState): Int {
|
private fun computeScrollOffset(state: LazyListState): Int {
|
||||||
if (state.layoutInfo.totalItemsCount == 0) return 0
|
if (state.layoutInfo.totalItemsCount == 0) return 0
|
||||||
val visibleItems = state.layoutInfo.visibleItemsInfo
|
val visibleItems = state.layoutInfo.visibleItemsInfo
|
||||||
val startChild = visibleItems.first()
|
val startChild = visibleItems
|
||||||
|
.fastFirstOrNull { (it.key as? String)?.startsWith(STICKY_HEADER_KEY_PREFIX)?.not() ?: true }!!
|
||||||
val endChild = visibleItems.last()
|
val endChild = visibleItems.last()
|
||||||
val minPosition = min(startChild.index, endChild.index)
|
val minPosition = min(startChild.index, endChild.index)
|
||||||
val maxPosition = max(startChild.index, endChild.index)
|
val maxPosition = max(startChild.index, endChild.index)
|
||||||
|
@ -401,13 +408,18 @@ private fun computeScrollOffset(state: LazyListState): Int {
|
||||||
private fun computeScrollRange(state: LazyListState): Int {
|
private fun computeScrollRange(state: LazyListState): Int {
|
||||||
if (state.layoutInfo.totalItemsCount == 0) return 0
|
if (state.layoutInfo.totalItemsCount == 0) return 0
|
||||||
val visibleItems = state.layoutInfo.visibleItemsInfo
|
val visibleItems = state.layoutInfo.visibleItemsInfo
|
||||||
val startChild = visibleItems.first()
|
val startChild = visibleItems
|
||||||
|
.fastFirstOrNull { (it.key as? String)?.startsWith(STICKY_HEADER_KEY_PREFIX)?.not() ?: true }!!
|
||||||
val endChild = visibleItems.last()
|
val endChild = visibleItems.last()
|
||||||
val laidOutArea = endChild.bottom - startChild.top
|
val laidOutArea = endChild.bottom - startChild.top
|
||||||
val laidOutRange = abs(startChild.index - endChild.index) + 1
|
val laidOutRange = abs(startChild.index - endChild.index) + 1
|
||||||
return (laidOutArea.toFloat() / laidOutRange * state.layoutInfo.totalItemsCount).roundToInt()
|
return (laidOutArea.toFloat() / laidOutRange * state.layoutInfo.totalItemsCount).roundToInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object Scroller {
|
||||||
|
const val STICKY_HEADER_KEY_PREFIX = "sticky:"
|
||||||
|
}
|
||||||
|
|
||||||
private val ThumbLength = 48.dp
|
private val ThumbLength = 48.dp
|
||||||
private val ThumbThickness = 8.dp
|
private val ThumbThickness = 8.dp
|
||||||
private val ThumbShape = RoundedCornerShape(ThumbThickness / 2)
|
private val ThumbShape = RoundedCornerShape(ThumbThickness / 2)
|
||||||
|
|
|
@ -62,11 +62,18 @@ import androidx.compose.ui.platform.LocalLayoutDirection
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.LayoutDirection
|
import androidx.compose.ui.unit.LayoutDirection
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.util.fastFirstOrNull
|
||||||
import androidx.compose.ui.util.fastSumBy
|
import androidx.compose.ui.util.fastSumBy
|
||||||
|
import eu.kanade.presentation.components.Scroller.STICKY_HEADER_KEY_PREFIX
|
||||||
import kotlinx.coroutines.channels.BufferOverflow
|
import kotlinx.coroutines.channels.BufferOverflow
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws horizontal scrollbar to a LazyList.
|
||||||
|
*
|
||||||
|
* Set key with [STICKY_HEADER_KEY_PREFIX] prefix to any sticky header item in the list.
|
||||||
|
*/
|
||||||
fun Modifier.drawHorizontalScrollbar(
|
fun Modifier.drawHorizontalScrollbar(
|
||||||
state: LazyListState,
|
state: LazyListState,
|
||||||
reverseScrolling: Boolean = false,
|
reverseScrolling: Boolean = false,
|
||||||
|
@ -74,6 +81,11 @@ fun Modifier.drawHorizontalScrollbar(
|
||||||
positionOffsetPx: Float = 0f,
|
positionOffsetPx: Float = 0f,
|
||||||
): Modifier = drawScrollbar(state, Orientation.Horizontal, reverseScrolling, positionOffsetPx)
|
): Modifier = drawScrollbar(state, Orientation.Horizontal, reverseScrolling, positionOffsetPx)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws vertical scrollbar to a LazyList.
|
||||||
|
*
|
||||||
|
* Set key with [STICKY_HEADER_KEY_PREFIX] prefix to any sticky header item in the list.
|
||||||
|
*/
|
||||||
fun Modifier.drawVerticalScrollbar(
|
fun Modifier.drawVerticalScrollbar(
|
||||||
state: LazyListState,
|
state: LazyListState,
|
||||||
reverseScrolling: Boolean = false,
|
reverseScrolling: Boolean = false,
|
||||||
|
@ -106,7 +118,7 @@ private fun Modifier.drawScrollbar(
|
||||||
0f
|
0f
|
||||||
} else {
|
} else {
|
||||||
items
|
items
|
||||||
.first()
|
.fastFirstOrNull { (it.key as? String)?.startsWith(STICKY_HEADER_KEY_PREFIX)?.not() ?: true }!!
|
||||||
.run {
|
.run {
|
||||||
val startPadding = if (reverseDirection) layoutInfo.afterContentPadding else layoutInfo.beforeContentPadding
|
val startPadding = if (reverseDirection) layoutInfo.afterContentPadding else layoutInfo.beforeContentPadding
|
||||||
startPadding + ((estimatedItemSize * index - offset) / totalSize * viewportSize)
|
startPadding + ((estimatedItemSize * index - offset) / totalSize * viewportSize)
|
||||||
|
|
Reference in a new issue