简体   繁体   English

Jetpack Compose:在 DropDownMenu 中模仿 spinner.setSelection()

[英]Jetpack Compose: Mimicking spinner.setSelection() inside of a DropDownMenu

The use case is that you have 10s or 100s of items inside of a dropdown menu, the dropdown options have some ordering - as with number values or alphabetical listing of words - and selections are made in succession.用例是您在下拉菜单中有 10 或 100 项,下拉选项有一些排序 - 如数字值或单词的字母列表 - 并且选择是连续进行的。

When the user reopens the menu, you'd like for it to open in the same region as their last selection, so that for instance you don't jump from "car" to "apple" but rather from "car" to "cat".当用户重新打开菜单时,您希望它在与他们上次选择的区域相同的区域中打开,例如,您不会从“car”跳转到“apple”,而是从“car”跳转到“cat” ”。 Or if they just opted to view order number 358, they can quickly view order number 359.或者,如果他们只是选择查看订单号 358,他们可以快速查看订单号 359。

Using views, you could create a Spinner and put all of your items in an ArrayAdapter and then call spinner.setSelection() to scroll directly to the index you want.使用视图,您可以创建一个Spinner并将所有项目放入ArrayAdapter中,然后调用spinner.setSelection()直接滚动到您想要的索引。

DropdownMenu doesn't have anything like HorizontalPager 's scrollToPage() . DropdownMenu没有类似HorizontalPager ntalPager 的scrollToPage()的东西。 So what solutions might exist to achieve this?那么可能存在哪些解决方案来实现这一目标呢?

So far, I've tried adding verticalScroll() to the DropdownMenu 's modifier and trying to do arithmetic with the scrollState.到目前为止,我已经尝试将verticalScroll()添加到DropdownMenu的修饰符并尝试对 scrollState 进行算术运算。 But it crashes at runtime with an error saying the component has infinite height, the same error you get if you try to nest scrollable components like a LazyColumn inside of a Column with verticalScroll.但它在运行时崩溃,并显示该组件具有无限高度的错误,如果您尝试将LazyColumn等可滚动组件嵌套在具有verticalScroll 的Column内,则会出现相同的错误。

It's a known issue .这是一个已知问题

DropdownMenu has its own vertical scroll modifier inside, and there is no API to work with it. DropdownMenu内部有自己的垂直滚动修改器,并且没有 API 可以使用它。

Until this problem is fixed by providing a suitable API, the only workaround I can think of is to create your own view - you can take the source code of DropdownMenu as reference.在通过提供合适的 API 解决此问题之前,我能想到的唯一解决方法是创建自己的视图 - 您可以参考DropdownMenu的源代码。

I'll post a more detailed answer here because I don't want to mislead anyone with my comment above.我将在这里发布更详细的答案,因为我不想用上面的评论误导任何人。

If you're in Android Studio, click the three dots on the mouse-hover quick documentation box and select "edit source" to open the source for DropdownMenu in AndroidMenu.android.kt.如果您在 Android Studio 中,请单击鼠标悬停快速文档框上的三个点和 select “编辑源代码”以在 AndroidMenu.ZC31B32364CE19CA8FCD150A4CEECk 中打开DropdownMenu的源代码。 Then observe that it uses a composable called DropdownMenuItemContent .然后观察它使用了一个名为DropdownMenuItemContent的组合。 Edit source again and you're in Menu.kt.再次编辑源代码,您将在 Menu.kt 中。

You'll see this:你会看到这个:

@Composable
internal fun DropdownMenuContent(
...
...
...
{
        Column(
            modifier = modifier
                .padding(vertical = DropdownMenuVerticalPadding)
                .width(IntrinsicSize.Max)
                .verticalScroll(rememberScrollState()),//<-We want this
            content = content
        )
    }

So in your custom composable just replace that rememberScrollState() with your favorite variable name for a ScrollState.因此,在您的自定义组合中,只需将rememberScrollState()替换为您最喜欢的 ScrollState 变量名称。

And then chain that reference all the way back up to your original view.然后将该引用一直链接到您的原始视图。

Getting Access to the ScrollState访问 ScrollState

@Composable 
fun MyCustomDropdownMenu(
    expanded:Boolean,
    scrollStateProvidedByTopParent:ScrollState,
    ...
    ...
   )
   {...}

@Composable
fun MyCustomDropdownMenuContent(
    scrollStateProvidedByTopParent:ScrollState,
    ...
    ...
    )
    {...}


//And now for your actual content

@Composable
fun TopParent(){
    val scrollStateProvidedByTopParent=rememberScrollState()
    val spinnerExpanded by remember{mutableStateOf(false)}
    ...
    ...
    Box{
       Row(modifier=Modifier.clickable(onClick={spinnerExpanded=!spinnerExpanded}))//<-More about this line in the sequel
    {
      Text("Options")
      Icon(imageVector = Icons.Filled.ArrowDropDown, contentDescription = "")
      
      MyCustomDropdownMenu(
      expanded = spinnerExpanded,
      scrollStateProvidedByTopParent=scrollStateProvidedByTopParent,
      onDismissRequest = { spinnerExpanded = false }) 
    {//your spinner content}

       }
    }
}

The above only specifies how to access the ScrollState of the DropdownMenu .上面只指定了如何访问DropdownMenuScrollState But once you have the ScrollState , you'll have to do some arithmetic to get the scroll position right when it opens.但是一旦你有了ScrollState ,你就必须做一些算术才能让滚动 position 在它打开时正确。 Here's one way that seems alright.这是一种看起来不错的方法。

Calculating the scroll distance计算滚动距离

Even after setting the contents of the menu items explicitly, the distance was never quite right if I relied on those values.即使在明确设置菜单项的内容之后,如果我依赖这些值,距离也永远不会完全正确。 So I used an onTextLayout callback inside the Text of my menu items in order to get the true Text height at the time of rendering.因此,我在菜单项的Text中使用了onTextLayout回调,以便在渲染时获得真实的Text高度。 Then I use that value for the arithmetic.然后我将该值用于算术。 It looks like this:它看起来像这样:

@Composable
fun TopParent(){
    val scrollStateProvidedByTopParent=rememberScrollState()
    val spinnerExpanded by remember{mutableStateOf(false)}
    val chosenText:String by remember{mutableStateOf(myListOfSpinnerOptions[0])
    val height by remember{mutableStateOf(0)}
    val heightHasBeenChecked by remember{mutableStateOf(false)}
    val coroutineScope=rememberCoroutineScope()

    ...
    ...
    Box{
       Row(modifier=Modifier.clickable(onClick={spinnerExpanded=!spinnerExpanded
 coroutineScope.launch{scrollStateProvidedByTopParent.scrollTo(height*myListOfSpinnerOptions.indexOf[chosenText])}}))//<-This gets some arithmetic for scrolling distance
    {
      Text("Options")
      Icon(imageVector = Icons.Filled.ArrowDropDown, contentDescription = "")
          
      MyCustomDropdownMenu(
      expanded = spinnerExpanded,
      scrollStateProvidedByTopParent=scrollStateProvidedByTopParent,
      onDismissRequest = { spinnerExpanded = false }) {
        myListOfSpinnerOptions.forEach{option->  
        DropdownMenuItem(onClick={
            chosenText=option    
            spinnerExpanded=false
            }){
              Text(option,onTextLayout={layoutResult->
              if (!heightHasBeenChecked){
                 height=layoutResults.size.height
                 heightHasBeenChecked=true
                 }
              }
              )

              }
          }
      }  
   }
}  

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM