/**
* Renders a scrollbar.
*
* <ul> <li> A scrollbar is composed of two components: a track and a knob. The knob moves across
* the track <li> The scrollbar appears automatically when the user starts scrolling and disappears
* after the scrolling is finished </ul>
*
* @param state The [LazyListState] that has been passed into the lazy list or lazy row
* @param horizontal If `true`, this will be a horizontally-scrolling (left and right) scroll bar,
* if `false`, it will be vertically-scrolling (up and down)
* @param alignEnd If `true`, the scrollbar will appear at the "end" of the scrollable composable it
* is decorating (at the right-hand side in left-to-right locales or left-hand side in right-to-left
* locales, for the vertical scrollbars -or- the bottom for horizontal scrollbars). If `false`, the
* scrollbar will appear at the "start" of the scrollable composable it is decorating (at the
* left-hand side in left-to-right locales or right-hand side in right-to-left locales, for the
* vertical scrollbars -or- the top for horizontal scrollbars)
* @param thickness How thick/wide the track and knob should be
* @param fixedKnobRatio If not `null`, the knob will always have this size, proportional to the
* size of the track. You should consider doing this if the size of the items in the scrollable
* composable is not uniform, to avoid the knob from oscillating in size as you scroll through the
* list
* @param knobCornerRadius The corner radius for the knob
* @param trackCornerRadius The corner radius for the track
* @param knobColor The color of the knob
* @param trackColor The color of the track. Make it [Color.Transparent] to hide it
* @param padding Edge padding to "squeeze" the scrollbar start/end in so it's not flush with the
* contents of the scrollable composable it is decorating
* @param visibleAlpha The alpha when the scrollbar is fully faded-in
* @param hiddenAlpha The alpha when the scrollbar is fully faded-out. Use a non-`0` number to keep
* the scrollbar from ever fading out completely
* @param fadeInAnimationDurationMs The duration of the fade-in animation when the scrollbar appears
* once the user starts scrolling
* @param fadeOutAnimationDurationMs The duration of the fade-out animation when the scrollbar
* disappears after the user is finished scrolling
* @param fadeOutAnimationDelayMs Amount of time to wait after the user is finished scrolling before
* the scrollbar begins its fade-out animation
*/
@Composable
fun Modifier.scrollbar(
state: LazyListState,
horizontal: Boolean,
alignEnd: Boolean = true,
thickness: Dp = 4.dp,
fixedKnobRatio: Float? = null,
knobCornerRadius: Dp = 4.dp,
trackCornerRadius: Dp = 2.dp,
knobColor: Color = Color.Black,
trackColor: Color = Color.White,
padding: Dp = 0.dp,
visibleAlpha: Float = 1f,
hiddenAlpha: Float = 0f,
fadeInAnimationDurationMs: Int = 150,
fadeOutAnimationDurationMs: Int = 500,
fadeOutAnimationDelayMs: Int = 1000,
): Modifier {
check(thickness > 0.dp) { "Thickness must be a positive integer." }
check(fixedKnobRatio == null || fixedKnobRatio < 1f) {
"A fixed knob ratio must be smaller than 1."
}
check(knobCornerRadius >= 0.dp) { "Knob corner radius must be greater than or equal to 0." }
check(trackCornerRadius >= 0.dp) { "Track corner radius must be greater than or equal to 0." }
check(hiddenAlpha <= visibleAlpha) { "Hidden alpha cannot be greater than visible alpha." }
check(fadeInAnimationDurationMs >= 0) {
"Fade in animation duration must be greater than or equal to 0."
}
check(fadeOutAnimationDurationMs >= 0) {
"Fade out animation duration must be greater than or equal to 0."
}
check(fadeOutAnimationDelayMs >= 0) {
"Fade out animation delay must be greater than or equal to 0."
}
val targetAlpha =
if (state.isScrollInProgress) {
visibleAlpha
} else {
hiddenAlpha
}
val animationDurationMs =
if (state.isScrollInProgress) {
fadeInAnimationDurationMs
} else {
fadeOutAnimationDurationMs
}
val animationDelayMs =
if (state.isScrollInProgress) {
0
} else {
fadeOutAnimationDelayMs
}
val alpha by
animateFloatAsState(
targetValue = targetAlpha,
animationSpec =
tween(delayMillis = animationDelayMs, durationMillis = animationDurationMs))
return drawWithContent {
drawContent()
state.layoutInfo.visibleItemsInfo.firstOrNull()?.let { firstVisibleItem ->
if (state.isScrollInProgress || alpha > 0f) {
// Size of the viewport, the entire size of the scrollable composable we are decorating with
// this scrollbar.
val viewportSize =
if (horizontal) {
size.width
} else {
size.height
} - padding.toPx() * 2
// The size of the first visible item. We use this to estimate how many items can fit in the
// viewport. Of course, this works perfectly when all items have the same size. When they
// don't, the scrollbar knob size will grow and shrink as we scroll.
val firstItemSize = firstVisibleItem.size
// The *estimated* size of the entire scrollable composable, as if it's all on screen at
// once. It is estimated because it's possible that the size of the first visible item does
// not represent the size of other items. This will cause the scrollbar knob size to grow
// and shrink as we scroll, if the item sizes are not uniform.
val estimatedFullListSize = firstItemSize * state.layoutInfo.totalItemsCount
// The difference in position between the first pixels visible in our viewport as we scroll
// and the top of the fully-populated scrollable composable, if it were to show all the
// items at once. At first, the value is 0 since we start all the way to the top (or start
// edge). As we scroll down (or towards the end), this number will grow.
val viewportOffsetInFullListSpace =
state.firstVisibleItemIndex * firstItemSize + state.firstVisibleItemScrollOffset
// Where we should render the knob in our composable.
val knobPosition =
(viewportSize / estimatedFullListSize) * viewportOffsetInFullListSpace + padding.toPx()
// How large should the knob be.
val knobSize =
fixedKnobRatio?.let { it * viewportSize }
?: (viewportSize * viewportSize) / estimatedFullListSize
// Draw the track
drawRoundRect(
color = trackColor,
topLeft =
when {
// When the scrollbar is horizontal and aligned to the bottom:
horizontal && alignEnd -> Offset(padding.toPx(), size.height - thickness.toPx())
// When the scrollbar is horizontal and aligned to the top:
horizontal && !alignEnd -> Offset(padding.toPx(), 0f)
// When the scrollbar is vertical and aligned to the end:
alignEnd -> Offset(size.width - thickness.toPx(), padding.toPx())
// When the scrollbar is vertical and aligned to the start:
else -> Offset(0f, padding.toPx())
},
size =
if (horizontal) {
Size(size.width - padding.toPx() * 2, thickness.toPx())
} else {
Size(thickness.toPx(), size.height - padding.toPx() * 2)
},
alpha = alpha,
cornerRadius = CornerRadius(x = trackCornerRadius.toPx(), y = trackCornerRadius.toPx()),
)
// Draw the knob
drawRoundRect(
color = knobColor,
topLeft =
when {
// When the scrollbar is horizontal and aligned to the bottom:
horizontal && alignEnd -> Offset(knobPosition, size.height - thickness.toPx())
// When the scrollbar is horizontal and aligned to the top:
horizontal && !alignEnd -> Offset(knobPosition, 0f)
// When the scrollbar is vertical and aligned to the end:
alignEnd -> Offset(size.width - thickness.toPx(), knobPosition)
// When the scrollbar is vertical and aligned to the start:
else -> Offset(0f, knobPosition)
},
size =
if (horizontal) {
Size(knobSize, thickness.toPx())
} else {
Size(thickness.toPx(), knobSize)
},
alpha = alpha,
cornerRadius = CornerRadius(x = knobCornerRadius.toPx(), y = knobCornerRadius.toPx()),
)
}
}
}
}
9条答案
按热度按时间5cnsuln71#
现在这实际上是可能的(他们已经在LazyListState中添加了更多的东西),而且很容易做到。这是一个非常原始的滚动条(总是可见/不能拖动/等),它使用项目索引来计算拇指位置,因此在只有几个项目的列表中滚动时可能看起来不太好:
UPD:我已经更新了代码。我已经弄明白了如何显示/隐藏滚动条时,LazyColumn正在滚动或不+添加淡入淡出动画。我还将drawBehind()更改为drawWithContent(),因为前者在内容后面绘制,所以在某些情况下可能会在滚动条顶部绘制,这很可能是不可取的。
rlcwz9us2#
这在
LazyColumn
/LazyRow
中还不可能实现。它计划在某个时候添加,但目前还没有具体的计划版本。我会在可能的时候更新这个答案。
bvjxkvbb3#
我使用了the answer by @Dmitry并在其之上构建:
fixedKnobRatio
参数的功能xsuvu9jc4#
这可能会有所帮助:https://github.com/sahruday/Carousel是一种类似的方法,使用Compose作为Composable函数。
同时适用于
CarouselScrollState
(在ScrollState
上添加的参数)和LazyList
。如果高度是变化的或混合的项目,我不建议添加滚动指示器。
svdrlsy45#
如果您来自库的JetBrains Compose Multiplatform方面(无论如何,滚动条在桌面上更有意义),请查看https://github.com/JetBrains/compose-multiplatform/tree/master/tutorials/Desktop_Components
它们提供了
VerticalScrollbar
/HorizontalScrollbar
可组合组件,可以与惰性可滚动组件一起使用。tez616oj6#
将下面的代码复制粘贴到单个Kotlin文件中。
然后调用可组合函数
LazyColumnWithScrollbar
。该函数的参数与LazyColumn
类似。4bbkushb7#
您可以按照下面的代码库滚动
用于垂直滚动
用于水平滚动
8nuwlpux8#
使用Jetpack Compose构建带有滚动条的LazyColumn
1sbrub3j9#
这里是一个简单的
Kotlin
代码片段,用于使用 Android Jetpack Compose创建HorizontalScrollBar
:注意:LazyRow/LazyColumn中还没有直接的滚动条。但也有好消息。Compose提供了以多种方式自定义组件的规定。
快乐编码:)*