Add scrollbar indicator to LazyColumn (#7164)
This commit is contained in:
parent
aa2370b381
commit
3b2362c784
14 changed files with 337 additions and 20 deletions
|
@ -147,6 +147,7 @@ dependencies {
|
|||
implementation(compose.material.icons)
|
||||
implementation(compose.animation)
|
||||
implementation(compose.ui.tooling)
|
||||
implementation(compose.ui.util)
|
||||
implementation(compose.accompanist.webview)
|
||||
implementation(compose.accompanist.swiperefresh)
|
||||
|
||||
|
|
|
@ -18,7 +18,6 @@ import androidx.compose.foundation.layout.navigationBars
|
|||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Settings
|
||||
|
@ -52,6 +51,7 @@ import eu.kanade.presentation.components.DIVIDER_ALPHA
|
|||
import eu.kanade.presentation.components.Divider
|
||||
import eu.kanade.presentation.components.EmptyScreen
|
||||
import eu.kanade.presentation.components.PreferenceRow
|
||||
import eu.kanade.presentation.components.ScrollbarLazyColumn
|
||||
import eu.kanade.presentation.util.horizontalPadding
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.extension.model.Extension
|
||||
|
@ -80,7 +80,7 @@ fun ExtensionDetailsScreen(
|
|||
|
||||
var showNsfwWarning by remember { mutableStateOf(false) }
|
||||
|
||||
LazyColumn(
|
||||
ScrollbarLazyColumn(
|
||||
modifier = Modifier.nestedScroll(nestedScrollInterop),
|
||||
contentPadding = WindowInsets.navigationBars.asPaddingValues(),
|
||||
) {
|
||||
|
|
|
@ -10,7 +10,6 @@ import androidx.compose.foundation.layout.WindowInsets
|
|||
import androidx.compose.foundation.layout.asPaddingValues
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
|
@ -41,6 +40,7 @@ import com.google.accompanist.swiperefresh.SwipeRefresh
|
|||
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
|
||||
import eu.kanade.presentation.browse.components.BaseBrowseItem
|
||||
import eu.kanade.presentation.browse.components.ExtensionIcon
|
||||
import eu.kanade.presentation.components.ScrollbarLazyColumn
|
||||
import eu.kanade.presentation.components.SwipeRefreshIndicator
|
||||
import eu.kanade.presentation.theme.header
|
||||
import eu.kanade.presentation.util.horizontalPadding
|
||||
|
@ -113,7 +113,7 @@ fun ExtensionContent(
|
|||
) {
|
||||
var trustState by remember { mutableStateOf<Extension.Untrusted?>(null) }
|
||||
|
||||
LazyColumn(
|
||||
ScrollbarLazyColumn(
|
||||
contentPadding = WindowInsets.navigationBars.asPaddingValues() + topPaddingValues,
|
||||
) {
|
||||
items(
|
||||
|
|
|
@ -3,7 +3,6 @@ package eu.kanade.presentation.browse
|
|||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.asPaddingValues
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
|
@ -15,6 +14,7 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
|
|||
import eu.kanade.domain.manga.model.Manga
|
||||
import eu.kanade.presentation.components.EmptyScreen
|
||||
import eu.kanade.presentation.components.LoadingScreen
|
||||
import eu.kanade.presentation.components.ScrollbarLazyColumn
|
||||
import eu.kanade.presentation.manga.components.BaseMangaListItem
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrateMangaState
|
||||
|
@ -54,7 +54,7 @@ fun MigrateMangaContent(
|
|||
EmptyScreen(textResource = R.string.empty_screen)
|
||||
return
|
||||
}
|
||||
LazyColumn(
|
||||
ScrollbarLazyColumn(
|
||||
modifier = Modifier.nestedScroll(nestedScrollInterop),
|
||||
contentPadding = WindowInsets.navigationBars.asPaddingValues(),
|
||||
) {
|
||||
|
|
|
@ -4,7 +4,6 @@ import androidx.compose.foundation.layout.WindowInsets
|
|||
import androidx.compose.foundation.layout.asPaddingValues
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
|
@ -21,6 +20,7 @@ import eu.kanade.presentation.browse.components.BaseSourceItem
|
|||
import eu.kanade.presentation.components.EmptyScreen
|
||||
import eu.kanade.presentation.components.ItemBadges
|
||||
import eu.kanade.presentation.components.LoadingScreen
|
||||
import eu.kanade.presentation.components.ScrollbarLazyColumn
|
||||
import eu.kanade.presentation.theme.header
|
||||
import eu.kanade.presentation.util.horizontalPadding
|
||||
import eu.kanade.presentation.util.plus
|
||||
|
@ -62,7 +62,7 @@ fun MigrateSourceList(
|
|||
return
|
||||
}
|
||||
|
||||
LazyColumn(
|
||||
ScrollbarLazyColumn(
|
||||
modifier = Modifier.nestedScroll(nestedScrollInterop),
|
||||
contentPadding = WindowInsets.navigationBars.asPaddingValues() + topPaddingValues,
|
||||
) {
|
||||
|
|
|
@ -3,7 +3,6 @@ package eu.kanade.presentation.browse
|
|||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.asPaddingValues
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.Switch
|
||||
|
@ -20,6 +19,7 @@ import eu.kanade.presentation.browse.components.BaseSourceItem
|
|||
import eu.kanade.presentation.components.EmptyScreen
|
||||
import eu.kanade.presentation.components.LoadingScreen
|
||||
import eu.kanade.presentation.components.PreferenceRow
|
||||
import eu.kanade.presentation.components.ScrollbarLazyColumn
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.browse.source.FilterUiModel
|
||||
import eu.kanade.tachiyomi.ui.browse.source.SourceFilterState
|
||||
|
@ -60,7 +60,7 @@ fun SourcesFilterContent(
|
|||
return
|
||||
}
|
||||
|
||||
LazyColumn(
|
||||
ScrollbarLazyColumn(
|
||||
modifier = Modifier.nestedScroll(nestedScrollInterop),
|
||||
contentPadding = WindowInsets.navigationBars.asPaddingValues(),
|
||||
) {
|
||||
|
|
|
@ -7,7 +7,6 @@ import androidx.compose.foundation.layout.asPaddingValues
|
|||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.PushPin
|
||||
|
@ -36,6 +35,7 @@ import eu.kanade.domain.source.model.Source
|
|||
import eu.kanade.presentation.browse.components.BaseSourceItem
|
||||
import eu.kanade.presentation.components.EmptyScreen
|
||||
import eu.kanade.presentation.components.LoadingScreen
|
||||
import eu.kanade.presentation.components.ScrollbarLazyColumn
|
||||
import eu.kanade.presentation.theme.header
|
||||
import eu.kanade.presentation.util.horizontalPadding
|
||||
import eu.kanade.presentation.util.plus
|
||||
|
@ -87,7 +87,7 @@ fun SourceList(
|
|||
|
||||
var sourceState by remember { mutableStateOf<Source?>(null) }
|
||||
|
||||
LazyColumn(
|
||||
ScrollbarLazyColumn(
|
||||
modifier = Modifier.nestedScroll(nestedScrollConnection),
|
||||
contentPadding = WindowInsets.navigationBars.asPaddingValues() + topPaddingValues,
|
||||
) {
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
package eu.kanade.presentation.components
|
||||
|
||||
import androidx.compose.foundation.gestures.FlingBehavior
|
||||
import androidx.compose.foundation.gestures.ScrollableDefaults
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.calculateEndPadding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.kanade.presentation.util.drawVerticalScrollbar
|
||||
|
||||
/**
|
||||
* LazyColumn with scrollbar.
|
||||
*/
|
||||
@Composable
|
||||
fun ScrollbarLazyColumn(
|
||||
modifier: Modifier = Modifier,
|
||||
state: LazyListState = rememberLazyListState(),
|
||||
contentPadding: PaddingValues = PaddingValues(0.dp),
|
||||
reverseLayout: Boolean = false,
|
||||
verticalArrangement: Arrangement.Vertical =
|
||||
if (!reverseLayout) Arrangement.Top else Arrangement.Bottom,
|
||||
horizontalAlignment: Alignment.Horizontal = Alignment.Start,
|
||||
flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
|
||||
userScrollEnabled: Boolean = true,
|
||||
content: LazyListScope.() -> Unit,
|
||||
) {
|
||||
val direction = LocalLayoutDirection.current
|
||||
val density = LocalDensity.current
|
||||
val positionOffset = remember(contentPadding) {
|
||||
with(density) { contentPadding.calculateEndPadding(direction).toPx() }
|
||||
}
|
||||
LazyColumn(
|
||||
modifier = modifier
|
||||
.drawVerticalScrollbar(
|
||||
state = state,
|
||||
reverseScrolling = reverseLayout,
|
||||
positionOffsetPx = positionOffset,
|
||||
),
|
||||
state = state,
|
||||
contentPadding = contentPadding,
|
||||
reverseLayout = reverseLayout,
|
||||
verticalArrangement = verticalArrangement,
|
||||
horizontalAlignment = horizontalAlignment,
|
||||
flingBehavior = flingBehavior,
|
||||
userScrollEnabled = userScrollEnabled,
|
||||
content = content,
|
||||
)
|
||||
}
|
|
@ -10,7 +10,6 @@ import androidx.compose.foundation.layout.fillMaxHeight
|
|||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.selection.toggleable
|
||||
import androidx.compose.material.icons.Icons
|
||||
|
@ -45,6 +44,7 @@ import eu.kanade.domain.history.model.HistoryWithRelations
|
|||
import eu.kanade.presentation.components.EmptyScreen
|
||||
import eu.kanade.presentation.components.LoadingScreen
|
||||
import eu.kanade.presentation.components.MangaCover
|
||||
import eu.kanade.presentation.components.ScrollbarLazyColumn
|
||||
import eu.kanade.presentation.util.horizontalPadding
|
||||
import eu.kanade.presentation.util.plus
|
||||
import eu.kanade.presentation.util.topPaddingValues
|
||||
|
@ -107,7 +107,7 @@ fun HistoryContent(
|
|||
var removeState by remember { mutableStateOf<HistoryWithRelations?>(null) }
|
||||
|
||||
val scrollState = rememberLazyListState()
|
||||
LazyColumn(
|
||||
ScrollbarLazyColumn(
|
||||
modifier = Modifier
|
||||
.nestedScroll(nestedScroll),
|
||||
contentPadding = WindowInsets.navigationBars.asPaddingValues() + topPaddingValues,
|
||||
|
|
|
@ -3,7 +3,6 @@ package eu.kanade.presentation.more
|
|||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.asPaddingValues
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.CloudOff
|
||||
import androidx.compose.material.icons.outlined.GetApp
|
||||
|
@ -24,6 +23,7 @@ import androidx.compose.ui.res.painterResource
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import eu.kanade.presentation.components.Divider
|
||||
import eu.kanade.presentation.components.PreferenceRow
|
||||
import eu.kanade.presentation.components.ScrollbarLazyColumn
|
||||
import eu.kanade.presentation.components.SwitchPreference
|
||||
import eu.kanade.presentation.util.quantityStringResource
|
||||
import eu.kanade.tachiyomi.R
|
||||
|
@ -44,7 +44,7 @@ fun MoreScreen(
|
|||
val uriHandler = LocalUriHandler.current
|
||||
val downloadQueueState by presenter.downloadQueueState.collectAsState()
|
||||
|
||||
LazyColumn(
|
||||
ScrollbarLazyColumn(
|
||||
modifier = Modifier.nestedScroll(nestedScrollInterop),
|
||||
contentPadding = WindowInsets.navigationBars.asPaddingValues(),
|
||||
) {
|
||||
|
|
|
@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.WindowInsets
|
|||
import androidx.compose.foundation.layout.asPaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Public
|
||||
import androidx.compose.runtime.Composable
|
||||
|
@ -20,6 +19,7 @@ import androidx.compose.ui.res.painterResource
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import eu.kanade.presentation.components.LinkIcon
|
||||
import eu.kanade.presentation.components.PreferenceRow
|
||||
import eu.kanade.presentation.components.ScrollbarLazyColumn
|
||||
import eu.kanade.presentation.more.LogoHeader
|
||||
import eu.kanade.tachiyomi.BuildConfig
|
||||
import eu.kanade.tachiyomi.R
|
||||
|
@ -37,7 +37,7 @@ fun AboutScreen(
|
|||
val context = LocalContext.current
|
||||
val uriHandler = LocalUriHandler.current
|
||||
|
||||
LazyColumn(
|
||||
ScrollbarLazyColumn(
|
||||
modifier = Modifier.nestedScroll(nestedScrollInterop),
|
||||
contentPadding = WindowInsets.navigationBars.asPaddingValues(),
|
||||
) {
|
||||
|
|
|
@ -4,7 +4,6 @@ import androidx.annotation.StringRes
|
|||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.asPaddingValues
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
|
@ -12,13 +11,14 @@ import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
|||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import eu.kanade.presentation.components.PreferenceRow
|
||||
import eu.kanade.presentation.components.ScrollbarLazyColumn
|
||||
|
||||
@Composable
|
||||
fun SettingsMainScreen(
|
||||
nestedScrollInterop: NestedScrollConnection,
|
||||
sections: List<SettingsSection>,
|
||||
) {
|
||||
LazyColumn(
|
||||
ScrollbarLazyColumn(
|
||||
modifier = Modifier.nestedScroll(nestedScrollInterop),
|
||||
contentPadding = WindowInsets.navigationBars.asPaddingValues(),
|
||||
) {
|
||||
|
|
257
app/src/main/java/eu/kanade/presentation/util/Scrollbar.kt
Normal file
257
app/src/main/java/eu/kanade/presentation/util/Scrollbar.kt
Normal file
|
@ -0,0 +1,257 @@
|
|||
package eu.kanade.presentation.util
|
||||
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2022 Albert Chang
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Code taken from https://gist.github.com/mxalbert1996/33a360fcab2105a31e5355af98216f5a
|
||||
* with some modifications to handle contentPadding.
|
||||
*
|
||||
* Modifiers for regular scrollable list is omitted.
|
||||
*/
|
||||
|
||||
import android.view.ViewConfiguration
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.gestures.Orientation
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.composed
|
||||
import androidx.compose.ui.draw.CacheDrawScope
|
||||
import androidx.compose.ui.draw.DrawResult
|
||||
import androidx.compose.ui.draw.drawWithCache
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.drawscope.DrawScope
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.util.fastSumBy
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
|
||||
fun Modifier.drawHorizontalScrollbar(
|
||||
state: LazyListState,
|
||||
reverseScrolling: Boolean = false,
|
||||
// The amount of offset the scrollbar position towards the top of the layout
|
||||
positionOffsetPx: Float = 0f,
|
||||
): Modifier = drawScrollbar(state, Orientation.Horizontal, reverseScrolling, positionOffsetPx)
|
||||
|
||||
fun Modifier.drawVerticalScrollbar(
|
||||
state: LazyListState,
|
||||
reverseScrolling: Boolean = false,
|
||||
// The amount of offset the scrollbar position towards the start of the layout
|
||||
positionOffsetPx: Float = 0f,
|
||||
): Modifier = drawScrollbar(state, Orientation.Vertical, reverseScrolling, positionOffsetPx)
|
||||
|
||||
private fun Modifier.drawScrollbar(
|
||||
state: LazyListState,
|
||||
orientation: Orientation,
|
||||
reverseScrolling: Boolean,
|
||||
positionOffset: Float,
|
||||
): Modifier = drawScrollbar(
|
||||
orientation, reverseScrolling,
|
||||
) { reverseDirection, atEnd, thickness, color, alpha ->
|
||||
val layoutInfo = state.layoutInfo
|
||||
val viewportSize = if (orientation == Orientation.Horizontal) {
|
||||
layoutInfo.viewportSize.width
|
||||
} else {
|
||||
layoutInfo.viewportSize.height
|
||||
} - layoutInfo.beforeContentPadding - layoutInfo.afterContentPadding
|
||||
val items = layoutInfo.visibleItemsInfo
|
||||
val itemsSize = items.fastSumBy { it.size }
|
||||
val showScrollbar = items.size < layoutInfo.totalItemsCount || itemsSize > viewportSize
|
||||
val estimatedItemSize = if (items.isEmpty()) 0f else itemsSize.toFloat() / items.size
|
||||
val totalSize = estimatedItemSize * layoutInfo.totalItemsCount
|
||||
val thumbSize = viewportSize / totalSize * viewportSize
|
||||
val startOffset = if (items.isEmpty()) 0f else items
|
||||
.first()
|
||||
.run {
|
||||
val startPadding = if (reverseDirection) layoutInfo.afterContentPadding else layoutInfo.beforeContentPadding
|
||||
startPadding + ((estimatedItemSize * index - offset) / totalSize * viewportSize)
|
||||
}
|
||||
val drawScrollbar = onDrawScrollbar(
|
||||
orientation, reverseDirection, atEnd, showScrollbar,
|
||||
thickness, color, alpha, thumbSize, startOffset, positionOffset,
|
||||
)
|
||||
onDrawWithContent {
|
||||
drawContent()
|
||||
drawScrollbar()
|
||||
}
|
||||
}
|
||||
|
||||
private fun CacheDrawScope.onDrawScrollbar(
|
||||
orientation: Orientation,
|
||||
reverseDirection: Boolean,
|
||||
atEnd: Boolean,
|
||||
showScrollbar: Boolean,
|
||||
thickness: Float,
|
||||
color: Color,
|
||||
alpha: () -> Float,
|
||||
thumbSize: Float,
|
||||
scrollOffset: Float,
|
||||
positionOffset: Float,
|
||||
): DrawScope.() -> Unit {
|
||||
val topLeft = if (orientation == Orientation.Horizontal) {
|
||||
Offset(
|
||||
if (reverseDirection) size.width - scrollOffset - thumbSize else scrollOffset,
|
||||
if (atEnd) size.height - positionOffset - thickness else positionOffset,
|
||||
)
|
||||
} else {
|
||||
Offset(
|
||||
if (atEnd) size.width - positionOffset - thickness else positionOffset,
|
||||
if (reverseDirection) size.height - scrollOffset - thumbSize else scrollOffset,
|
||||
)
|
||||
}
|
||||
val size = if (orientation == Orientation.Horizontal) {
|
||||
Size(thumbSize, thickness)
|
||||
} else {
|
||||
Size(thickness, thumbSize)
|
||||
}
|
||||
|
||||
return {
|
||||
if (showScrollbar) {
|
||||
drawRect(
|
||||
color = color,
|
||||
topLeft = topLeft,
|
||||
size = size,
|
||||
alpha = alpha(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Modifier.drawScrollbar(
|
||||
orientation: Orientation,
|
||||
reverseScrolling: Boolean,
|
||||
onBuildDrawCache: CacheDrawScope.(
|
||||
reverseDirection: Boolean,
|
||||
atEnd: Boolean,
|
||||
thickness: Float,
|
||||
color: Color,
|
||||
alpha: () -> Float,
|
||||
) -> DrawResult,
|
||||
): 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)
|
||||
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
|
||||
|
||||
val context = LocalContext.current
|
||||
val thickness = remember { ViewConfiguration.get(context).scaledScrollBarSize.toFloat() }
|
||||
val color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.364f)
|
||||
Modifier
|
||||
.nestedScroll(nestedScrollConnection)
|
||||
.drawWithCache {
|
||||
onBuildDrawCache(reverseDirection, atEnd, thickness, color, alpha::value)
|
||||
}
|
||||
}
|
||||
|
||||
private val FadeOutAnimationSpec = tween<Float>(
|
||||
durationMillis = ViewConfiguration.getScrollBarFadeDuration(),
|
||||
delayMillis = ViewConfiguration.getScrollDefaultDelay(),
|
||||
)
|
||||
|
||||
@Preview(widthDp = 400, heightDp = 400, showBackground = true)
|
||||
@Composable
|
||||
fun LazyListScrollbarPreview() {
|
||||
val state = rememberLazyListState()
|
||||
LazyColumn(
|
||||
modifier = Modifier.drawVerticalScrollbar(state),
|
||||
state = state,
|
||||
) {
|
||||
items(50) {
|
||||
Text(
|
||||
text = "Item ${it + 1}",
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 400, showBackground = true)
|
||||
@Composable
|
||||
fun LazyListHorizontalScrollbarPreview() {
|
||||
val state = rememberLazyListState()
|
||||
LazyRow(
|
||||
modifier = Modifier.drawHorizontalScrollbar(state),
|
||||
state = state,
|
||||
) {
|
||||
items(50) {
|
||||
Text(
|
||||
text = (it + 1).toString(),
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 8.dp, vertical = 16.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@ activity = "androidx.activity:activity-compose:1.6.0-alpha03"
|
|||
foundation = { module = "androidx.compose.foundation:foundation", version.ref="compose" }
|
||||
animation = { module = "androidx.compose.animation:animation", version.ref="compose" }
|
||||
ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref="compose" }
|
||||
ui-util = { module = "androidx.compose.ui:ui-util", version.ref="compose" }
|
||||
|
||||
material3-core = "androidx.compose.material3:material3:1.0.0-alpha12"
|
||||
material3-adapter = "com.google.android.material:compose-theme-adapter-3:1.0.10"
|
||||
|
|
Reference in a new issue