DropdownMenu与垂直滚动条的Compose桌面组件

huangapple go评论94阅读模式
英文:

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 中让滚动条工作的方式。

  1. 将此版本的 DesktopMenu.desktop.kt 复制到您的项目中。
  2. 将此版本的 Menu.kt 复制到您的项目中。
  3. 在您的 Menu.kt 中添加此 Card,将其包装在显示内容的 Column 周围。
  4. 根据问题中的 这个评论 在第 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
        )
    }
}
  1. 最后一点是,这可能会超出我的屏幕,所以我返回了当前版本的 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.

  1. Copy this version of DesktopMenu.desktop.kt into your project.
  2. Copy this version of Menu.kt into your project.
  3. Add this Card into your Menu.kt, wrapping around the Column which displays the content.
  4. 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&lt;IntSize?&gt;(null) }
    Column(
        modifier = Modifier
            .padding(vertical = DropdownMenuVerticalPadding)
            .verticalScroll(scrollState)
            .onSizeChanged { size -&gt;
                columnSize = size
            },
        content = content
    )
    columnSize?.let { size -&gt;
        VerticalScrollbar(
            modifier = Modifier
                .align(Alignment.CenterEnd)
                .height(with(LocalDensity.current) { size.height.toDp() }),
            scrollState = scrollState
        )
    }
}
  1. 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 my 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) -&gt; Unit = { _, _ -&gt; }
) : 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 &lt; min -&gt; min
            this + size &gt; max -&gt; max - size
            else -&gt; 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 &gt; max -&gt; max - size
            this &lt; min -&gt; min
            else -&gt; this
        }

        fun Int.coerceWithSizeIntoRange(size: Int, min: Int, max: Int) = when {
            isLtr -&gt; coerceWithSizeIntoRangePreferMin(size, min, max)
            else -&gt; 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 &gt;= verticalMargin &amp;&amp;
                it + popupContentSize.height &lt;= windowSize.height - verticalMargin
        } ?: toTop

        // Desktop specific vertical position checking
        val aboveAnchor = anchorBounds.top + contentOffsetY
        val belowAnchor = windowSize.height - anchorBounds.bottom - contentOffsetY

        if (belowAnchor &gt;= aboveAnchor) {
            y = anchorBounds.bottom + contentOffsetY
        }

        if (y + popupContentSize.height &gt; 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)
    }
}

huangapple
  • 本文由 发表于 2023年6月13日 02:50:49
  • 转载请务必保留本文链接:https://go.coder-hub.com/76459497.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定