Yan*_*ick 9 android android-jetpack-compose
有没有办法添加滚动条来添加LazyColumn
(ScrollableColumn
已弃用)。Javadoc 没有提到有关 Jetpack Compose 中滚动条的任何内容。
澄清一下,这是我想要实现的设计:
甚至可以在 Jetpack Compose 中做到这一点吗?或者不支持滚动条?
War*_*lax 22
我已经接受了@Dmitry 的答案并在此基础上构建:
fixedKnobRatio
项目没有统一大小的情况下传递参数的功能/**
* 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()),
)
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
Rya*_*ley 10
这在LazyColumn
/ 中尚不可能LazyRow
。
计划在某个时候添加它,但还没有具体的计划发布。我会在可能的时候更新这个答案。
现在实际上是可能的(他们已经在 LazyListState 中添加了更多的东西),而且很容易做到。这是一个非常原始的滚动条(始终可见/无法拖动/等),它使用项目索引来确定拇指位置,因此在只有少数项目的列表中滚动时它可能看起来不太好:
@Composable
fun Modifier.simpleVerticalScrollbar(
state: LazyListState,
width: Dp = 8.dp
): Modifier {
val targetAlpha = if (state.isScrollInProgress) 1f else 0f
val duration = if (state.isScrollInProgress) 150 else 500
val alpha by animateFloatAsState(
targetValue = targetAlpha,
animationSpec = tween(durationMillis = duration)
)
return drawWithContent {
drawContent()
val firstVisibleElementIndex = state.layoutInfo.visibleItemsInfo.firstOrNull()?.index
val needDrawScrollbar = state.isScrollInProgress || alpha > 0.0f
// Draw scrollbar if scrolling or if the animation is still running and lazy column has content
if (needDrawScrollbar && firstVisibleElementIndex != null) {
val elementHeight = this.size.height / state.layoutInfo.totalItemsCount
val scrollbarOffsetY = firstVisibleElementIndex * elementHeight
val scrollbarHeight = state.layoutInfo.visibleItemsInfo.size * elementHeight
drawRect(
color = Color.Red,
topLeft = Offset(this.size.width - width.toPx(), scrollbarOffsetY),
size = Size(width.toPx(), scrollbarHeight),
alpha = alpha
)
}
}
}
Run Code Online (Sandbox Code Playgroud)
UPD:我已经更新了代码。我已经弄清楚如何在 LazyColumn 被滚动时显示/隐藏滚动条 + 添加淡入/淡出动画。我还将 drawBehind() 更改为 drawWithContent() 因为前者在内容后面绘制,因此在某些情况下它可能会绘制在滚动条的顶部,这很可能是不可取的。
如果您来自库的JetBrains Compose Multiplatform端(无论如何,滚动条在桌面上更有意义),请查看 https://github.com/JetBrains/compose-multiplatform/tree/master/tutorials/桌面_组件
它们提供了可与延迟滚动组件一起使用的VerticalScrollbar
/可组合项。HorizontalScrollbar
import androidx.compose.foundation.VerticalScrollbar
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.rememberScrollbarAdapter
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import androidx.compose.ui.window.rememberWindowState
fun main() = application {
Window(
onCloseRequest = ::exitApplication,
title = "Scrollbars",
state = rememberWindowState(width = 250.dp, height = 400.dp)
) {
LazyScrollable()
}
}
@Composable
fun LazyScrollable() {
Box(
modifier = Modifier.fillMaxSize()
.background(color = Color(180, 180, 180))
.padding(10.dp)
) {
val state = rememberLazyListState()
LazyColumn(Modifier.fillMaxSize().padding(end = 12.dp), state) {
items(1000) { x ->
TextBox("Item #$x")
Spacer(modifier = Modifier.height(5.dp))
}
}
VerticalScrollbar(
modifier = Modifier.align(Alignment.CenterEnd).fillMaxHeight(),
adapter = rememberScrollbarAdapter(
scrollState = state
)
)
}
}
@Composable
fun TextBox(text: String = "Item") {
Box(
modifier = Modifier.height(32.dp)
.fillMaxWidth()
.background(color = Color(0, 0, 0, 20))
.padding(start = 10.dp),
contentAlignment = Alignment.CenterStart
) {
Text(text = text)
}
}
Run Code Online (Sandbox Code Playgroud)
更新:请注意,上述可组合项可能无法在AlertDialog
. 相反,通过构建Scaffold
.
归档时间: |
|
查看次数: |
1407 次 |
最近记录: |