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:
Ivan Iskandar 2022-10-10 22:59:01 +07:00 committed by GitHub
parent 8500add09f
commit fba244423f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 30 additions and 5 deletions

View File

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

View File

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

View File

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