英文:
Compose Desktop DropdownMenu with vertical scroll bar
问题
以下是您要翻译的内容:
"I'm taking a DropdownMenu from Kotlin Compose for Desktop and I want to include a vertical scroll bar. The source code for the DropdownMenu is here. They have a sample which works fine but I can't get it to display the vertical scroll bar. It doesn't display by default.
There is a VerticalScrollbar example also that I have attempted but I haven't gotten it to work with the DropdownMenu
.
Putting a verticalScroll()
within the DropdownMenu(Modifier)
results in an error Vertically scrolled component was measured with an infinity maximum height constraints, which is disallowed...
.
Adding a VerticalScrollbar()
beneath the DropdownMenu
results in an error Can't represent a size of 1073741824 in Constraints
.
So as far as I can tell there is something in the DropdownMenu
's Popup
or something like that which is making this difficult for me.
Is there a way I can implement the visible scroll bar? My layout is like this so far. (you can scroll down to the Dropdown menu below...)"
请注意,这是您的文本的中文翻译。如果您需要任何其他帮助,请随时告诉我。
英文:
I'm taking a DropdownMenu from Kotlin Compose for Desktop and I want to include a vertical scroll bar. The source code for the DropdownMenu is here. They have a sample which works fine but I can't get it to display the vertical scroll bar. It doesn't display by default.
There is a VerticalScrollbar example also that I have attempted but I haven't gotten it to work with the DropdownMenu
.
Putting a verticalScroll()
within the DropdownMenu(Modifier)
results in an error Vertically scrolled component was measured with an infinity maximum height constraints, which is disallowed...
.
Adding a VerticalScrollbar()
beneath the DropdownMenu
results in an error Can't represent a size of 1073741824 in Constraints
.
So as far as I can tell there is something in the DropdownMenu
's Popup
or something like that which is making this difficult for me.
Is there a way I can implement the visible scroll bar? My layout is like this so far. (you can scroll down to the Dropdown menu below...)
data class Lookup(val id: String, val name: String)
fun main() = application {
Window(
onCloseRequest = ::exitApplication,
state = rememberWindowState(width = 1280.dp, height = 800.dp)
) {
MaterialTheme {
Scaffold {
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally
) {
val lookups = LookupService.getLookups() // about 75 items
val (expanded, setExpanded) = remember { mutableStateOf(false) }
val (selected, setSelected) = remember { mutableStateOf<Lookup?>(null) }
Spacer(Modifier.height(20.dp))
Box(Modifier.wrapContentSize(Alignment.TopStart)) {
val icon = if (expanded) {
Icons.Filled.KeyboardArrowUp
} else {
Icons.Filled.KeyboardArrowDown
}
OutlinedTextField(
value = selected?.name ?: "",
onValueChange = { },
modifier = Modifier
.width(360.dp)
.onKeyEvent {
if (it.key == Key.DirectionDown && !expanded) {
setExpanded(true)
return@onKeyEvent true
}
return false
}
.clickable { setExpanded(true) },
singleLine = true,
label = { Text("Select an item") },
trailingIcon = {
Icon(icon, "Select an item", Modifier.clickable {
setExpanded(!expanded)
})
},
enabled = expanded,
colors = TextFieldDefaults.textFieldColors(
disabledTextColor = LocalContentColor.current.copy(LocalContentAlpha.current),
backgroundColor = Color.Transparent
)
)
DropdownMenu( // Desktop version, so it creates a "Popup" per the source code
expanded = expanded,
onDismissRequest = {
runBlocking { // to handle a glitch where the dropdown may "unexpand & expand" again on clicking
delay(200)
setExpanded(false)
}
},
modifier = Modifier
.width(360.dp)
.background(Color.White)
.clip(RoundedCornerShape(5.dp))
// SHOULD HAVE VERTICAL SCROLLBAR SHOW UP AS PART OF THIS DROPDOWNMENU COLUMN
) {
lookups.forEach { lookup ->
DropdownMenuItem(
onClick = {
setExpanded(false)
setSelected(lookup)
}
) {
Text(lookup.name)
}
}
}
}
}
}
}
}
}
答案1
得分: 0
我在当前开放的问题中找到了答案 Scrollbar doesn't work for DropdownMenu #587。
这是我在 Desktop 版本的 DropdownMenu 中让滚动条工作的方式。
- 将此版本的 DesktopMenu.desktop.kt 复制到您的项目中。
- 将此版本的 Menu.kt 复制到您的项目中。
- 在您的 Menu.kt 中添加此 Card,将其包装在显示内容的
Column
周围。 - 根据问题中的 这个评论 在第 3 步的
Card
中添加以下内容。
Box(
modifier = modifier
.width(IntrinsicSize.Max)
) {
val scrollState = rememberScrollState()
var columnSize by remember { mutableStateOf<IntSize?>(null) }
Column(
modifier = Modifier
.padding(vertical = DropdownMenuVerticalPadding)
.verticalScroll(scrollState)
.onSizeChanged { size ->
columnSize = size
},
content = content
)
columnSize?.let { size ->
VerticalScrollbar(
modifier = Modifier
.align(Alignment.CenterEnd)
.height(with(LocalDensity.current) { size.height.toDp() }),
scrollState = scrollState
)
}
}
- 最后一点是,这可能会超出我的屏幕,所以我返回了当前版本的 DesktopMenu.desktop.kt 来将
DesktopDropdownMenuPositionProvider
的这个版本放回我的DesktopMenu.desktop.kt
中。
/**
* Positions a dropdown relative to another widget (its anchor).
*/
@Immutable
internal data class DesktopDropdownMenuPositionProvider(
val contentOffset: DpOffset,
val density: Density,
val onPositionCalculated: (IntRect, IntRect) -> Unit = { _, _ -> }
) : PopupPositionProvider {
override fun calculatePosition(
anchorBounds: IntRect,
windowSize: IntSize,
layoutDirection: LayoutDirection,
popupContentSize: IntSize
): IntOffset {
val isLtr = layoutDirection == LayoutDirection.Ltr
// Coerce such that this..this+size fits into min..max; if impossible, align with min
fun Int.coerceWithSizeIntoRangePreferMin(size: Int, min: Int, max: Int) = when {
this < min -> min
this + size > max -> max - size
else -> this
}
// Coerce such that this..this+size fits into min..max; if impossible, align with max
fun Int.coerceWithSizeIntoRangePreferMax(size: Int, min: Int, max: Int) = when {
this + size > max -> max - size
this < min -> min
else -> this
}
fun Int.coerceWithSizeIntoRange(size: Int, min: Int, max: Int) = when {
isLtr -> coerceWithSizeIntoRangePreferMin(size, min, max)
else -> coerceWithSizeIntoRangePreferMax(size, min, max)
}
// The min margin above and below the menu, relative to the screen.
val verticalMargin = with(density) { MenuVerticalMargin.roundToPx() }
// The content offset specified using the dropdown offset parameter.
val contentOffsetX = with(density) { contentOffset.x.roundToPx() }
val contentOffsetY = with(density) { contentOffset.y.roundToPx() }
// Compute horizontal position.
val preferredX = if (isLtr) {
anchorBounds.left + contentOffsetX
}
else {
anchorBounds.right - contentOffsetX - popupContentSize.width
}
val x = preferredX.coerceWithSizeIntoRange(
size = popupContentSize.width,
min = 0,
max = windowSize.width
)
// Compute vertical position.
val toBottom = maxOf(anchorBounds.bottom + contentOffsetY, verticalMargin)
val toTop = anchorBounds.top - contentOffsetY - popupContentSize.height
val toCenter = anchorBounds.top - popupContentSize.height / 2
val toWindowBottom = windowSize.height - popupContentSize.height - verticalMargin
var y = sequenceOf(toBottom, toTop, toCenter, toWindowBottom).firstOrNull {
it >= verticalMargin &&
it + popupContentSize.height <= windowSize.height - verticalMargin
} ?: toTop
// Desktop specific vertical position checking
val aboveAnchor = anchorBounds.top + contentOffsetY
val belowAnchor = windowSize.height - anchorBounds.bottom - contentOffsetY
if (belowAnchor >= aboveAnchor) {
y = anchorBounds.bottom + contentOffsetY
}
if (y + popupContentSize.height > windowSize.height) {
y = windowSize.height - popupContentSize.height
}
y = y.coerceAtLeast(0)
onPositionCalculated(
anchorBounds,
IntRect(x, y, x + popupContentSize.width, y + popupContentSize.height)
)
return IntOffset(x, y)
}
}
英文:
I found the answer in a currently open issue Scrollbar doesn't work for DropdownMenu
#587.
This is how I just got my scrollbar working within the Desktop version of DropdownMenu.
- Copy this version of DesktopMenu.desktop.kt into your project.
- Copy this version of Menu.kt into your project.
- Add this Card into your Menu.kt, wrapping around the
Column
which displays the content. - As per this comment in the issue, add this into the
Card
from point 3.
Box(
modifier = modifier
.width(IntrinsicSize.Max)
) {
val scrollState = rememberScrollState()
var columnSize by remember { mutableStateOf<IntSize?>(null) }
Column(
modifier = Modifier
.padding(vertical = DropdownMenuVerticalPadding)
.verticalScroll(scrollState)
.onSizeChanged { size ->
columnSize = size
},
content = content
)
columnSize?.let { size ->
VerticalScrollbar(
modifier = Modifier
.align(Alignment.CenterEnd)
.height(with(LocalDensity.current) { size.height.toDp() }),
scrollState = scrollState
)
}
}
- Last point is that this then would overflow above my screen, so I went back to the current version of DesktopMenu.desktop.kt to put back in this version of
DesktopDropdownMenuPositionProvider
into myDesktopMenu.desktop.kt
.
/**
* Positions a dropdown relative to another widget (its anchor).
*/
@Immutable
internal data class DesktopDropdownMenuPositionProvider(
val contentOffset: DpOffset,
val density: Density,
val onPositionCalculated: (IntRect, IntRect) -> Unit = { _, _ -> }
) : PopupPositionProvider {
override fun calculatePosition(
anchorBounds: IntRect,
windowSize: IntSize,
layoutDirection: LayoutDirection,
popupContentSize: IntSize
): IntOffset {
val isLtr = layoutDirection == LayoutDirection.Ltr
// Coerce such that this..this+size fits into min..max; if impossible, align with min
fun Int.coerceWithSizeIntoRangePreferMin(size: Int, min: Int, max: Int) = when {
this < min -> min
this + size > max -> max - size
else -> this
}
// Coerce such that this..this+size fits into min..max; if impossible, align with max
fun Int.coerceWithSizeIntoRangePreferMax(size: Int, min: Int, max: Int) = when {
this + size > max -> max - size
this < min -> min
else -> this
}
fun Int.coerceWithSizeIntoRange(size: Int, min: Int, max: Int) = when {
isLtr -> coerceWithSizeIntoRangePreferMin(size, min, max)
else -> coerceWithSizeIntoRangePreferMax(size, min, max)
}
// The min margin above and below the menu, relative to the screen.
val verticalMargin = with(density) { MenuVerticalMargin.roundToPx() }
// The content offset specified using the dropdown offset parameter.
val contentOffsetX = with(density) { contentOffset.x.roundToPx() }
val contentOffsetY = with(density) { contentOffset.y.roundToPx() }
// Compute horizontal position.
val preferredX = if (isLtr) {
anchorBounds.left + contentOffsetX
}
else {
anchorBounds.right - contentOffsetX - popupContentSize.width
}
val x = preferredX.coerceWithSizeIntoRange(
size = popupContentSize.width,
min = 0,
max = windowSize.width
)
// Compute vertical position.
val toBottom = maxOf(anchorBounds.bottom + contentOffsetY, verticalMargin)
val toTop = anchorBounds.top - contentOffsetY - popupContentSize.height
val toCenter = anchorBounds.top - popupContentSize.height / 2
val toWindowBottom = windowSize.height - popupContentSize.height - verticalMargin
var y = sequenceOf(toBottom, toTop, toCenter, toWindowBottom).firstOrNull {
it >= verticalMargin &&
it + popupContentSize.height <= windowSize.height - verticalMargin
} ?: toTop
// Desktop specific vertical position checking
val aboveAnchor = anchorBounds.top + contentOffsetY
val belowAnchor = windowSize.height - anchorBounds.bottom - contentOffsetY
if (belowAnchor >= aboveAnchor) {
y = anchorBounds.bottom + contentOffsetY
}
if (y + popupContentSize.height > windowSize.height) {
y = windowSize.height - popupContentSize.height
}
y = y.coerceAtLeast(0)
onPositionCalculated(
anchorBounds,
IntRect(x, y, x + popupContentSize.width, y + popupContentSize.height)
)
return IntOffset(x, y)
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论