【问题标题】:Exposed drop-down menu for jetpack composeJetpack compose 的公开下拉菜单
【发布时间】:2021-07-10 15:23:30
【问题描述】:

我想知道是否有针对 jetpack compose 的 Exposed 下拉菜单的解决方案? 我在 jetpack compose 中找不到这个组件的合适解决方案。有什么帮助吗?

【问题讨论】:

    标签: android kotlin android-jetpack-compose android-jetpack-compose-text android-compose-textfield


    【解决方案1】:

    1.1.0-alpha06版本在ExposedDropdownMenuBox的基础上引入了ExposedDropdownMenu的实现,里面有TextFieldDropdownMenu

    类似:

        val options = listOf("Option 1", "Option 2", "Option 3", "Option 4", "Option 5")
        var expanded by remember { mutableStateOf(false) }
        var selectedOptionText by remember { mutableStateOf(options[0]) }
        
        ExposedDropdownMenuBox(
            expanded = expanded,
            onExpandedChange = {
                expanded = !expanded
            }
        ) {
            TextField(
                readOnly = true,
                value = selectedOptionText,
                onValueChange = { },
                label = { Text("Label") },
                trailingIcon = {
                    ExposedDropdownMenuDefaults.TrailingIcon(
                        expanded = expanded
                    )
                },
                colors = ExposedDropdownMenuDefaults.textFieldColors()
            )
            ExposedDropdownMenu(
                expanded = expanded,
                onDismissRequest = {
                    expanded = false
                }
            ) {
                options.forEach { selectionOption ->
                    DropdownMenuItem(
                        onClick = {
                            selectedOptionText = selectionOption
                            expanded = false
                        }
                    ) {
                        Text(text = selectionOption)
                    }
                }
            }
        }
    

    1.0.x 版本没有内置组件。
    您可以使用OutlinedTextField + DropdownMenu

    这只是一个基本的(非常基本的)实现:

    var expanded by remember { mutableStateOf(false) }
    val suggestions = listOf("Item1","Item2","Item3")
    var selectedText by remember { mutableStateOf("") }
    
    var textfieldSize by remember { mutableStateOf(Size.Zero)}
    
    val icon = if (expanded)
        Icons.Filled.ArrowDropUp //it requires androidx.compose.material:material-icons-extended
    else
        Icons.Filled.ArrowDropDown
    
    
    Column() {
        OutlinedTextField(
            value = selectedText,
            onValueChange = { selectedText = it },
            modifier = Modifier
                .fillMaxWidth()
                .onGloballyPositioned { coordinates ->
                    //This value is used to assign to the DropDown the same width
                    textfieldSize = coordinates.size.toSize()
                },
            label = {Text("Label")},
            trailingIcon = {
                Icon(icon,"contentDescription",
                     Modifier.clickable { expanded = !expanded })
            }
        )
        DropdownMenu(
            expanded = expanded,
            onDismissRequest = { expanded = false },
            modifier = Modifier
                .width(with(LocalDensity.current){textfieldSize.width.toDp()})
        ) {
            suggestions.forEach { label ->
                DropdownMenuItem(onClick = {
                    selectedText = label
                }) {
                    Text(text = label)
                }
            }
        }
    }
    

    【讨论】:

    • 在 Google 的 issuetracker 中提交了一个错误:https://issuetracker.google.com/issues/173532272 希望它在稳定版发布之前实现。
    • 有没有办法让DropdownMenu的宽度和OutlinedTextField的宽度一样?
    • 有没有办法将菜单放置在文本字段上方而不是下方?
    • 如何让textview变亮而不是变暗?
    • 我在 1.1.0 最终版本上,ExposedDropdownMenu()ExposedDropdownMenuBox() 都没有为我导入,但很可能我缺少一个依赖项。
    【解决方案2】:

    这是我为使宽度与文本字段相同所做的操作:复制和修改 Gabriele 的答案。

    var expanded by remember { mutableStateOf(false) }
    val suggestions = listOf("Item1","Item2","Item3")
    var selectedText by remember { mutableStateOf("") }
    
    var dropDownWidth by remember { mutableStateOf(0) }
    
    val icon = if (expanded)
        Icons.Filled.....
    else
        Icons.Filled.ArrowDropDown
    
    
    Column() {
        OutlinedTextField(
            value = selectedText,
            onValueChange = { selectedText = it },
            modifier = Modifier.fillMaxWidth()
                .onSizeChanged {
                    dropDownWidth = it.width
                },
            label = {Text("Label")},
            trailingIcon = {
                Icon(icon,"contentDescription", Modifier.clickable { expanded = !expanded })
            }
        )
        DropdownMenu(
            expanded = expanded,
            onDismissRequest = { expanded = false },
            modifier = Modifier
                    .width(with(LocalDensity.current){dropDownWidth.toDp()})
        ) {
            suggestions.forEach { label ->
                DropdownMenuItem(onClick = {
                    selectedText = label
                }) {
                    Text(text = label)
                }
            }
        }
    }
    

    【讨论】:

      【解决方案3】:

      这是我的版本。 我在没有使用TextField 的情况下实现了这一点(所以没有键盘)。 有“常规”和“轮廓”两种版本。

      import androidx.compose.animation.core.animateFloatAsState
      import androidx.compose.foundation.background
      import androidx.compose.foundation.border
      import androidx.compose.foundation.clickable
      import androidx.compose.foundation.layout.*
      import androidx.compose.foundation.shape.ZeroCornerSize
      import androidx.compose.material.*
      import androidx.compose.material.icons.Icons
      import androidx.compose.material.icons.filled.ExpandMore
      import androidx.compose.runtime.*
      import androidx.compose.ui.Alignment
      import androidx.compose.ui.Modifier
      import androidx.compose.ui.draw.clip
      import androidx.compose.ui.draw.drawBehind
      import androidx.compose.ui.draw.rotate
      import androidx.compose.ui.geometry.Offset
      import androidx.compose.ui.geometry.Size
      import androidx.compose.ui.graphics.Color
      import androidx.compose.ui.graphics.Shape
      import androidx.compose.ui.layout.onGloballyPositioned
      import androidx.compose.ui.platform.LocalDensity
      import androidx.compose.ui.platform.LocalFocusManager
      import androidx.compose.ui.unit.Dp
      import androidx.compose.ui.unit.dp
      import androidx.compose.ui.unit.toSize
      import kotlinx.coroutines.delay
      import kotlinx.coroutines.launch
      
      
      // ExposedDropDownMenu will be added in Jetpack Compose 1.1.0.
      // This is a reimplementation while waiting.
      // See https://stackoverflow.com/questions/67111020/exposed-drop-down-menu-for-jetpack-compose/6904285
      
      @Composable
      fun SimpleExposedDropDownMenu(
          values: List<String>,
          selectedIndex: Int,
          onChange: (Int) -> Unit,
          label: @Composable () -> Unit,
          modifier: Modifier,
          backgroundColor: Color = MaterialTheme.colors.onSurface.copy(alpha = TextFieldDefaults.BackgroundOpacity),
          shape: Shape = MaterialTheme.shapes.small.copy(bottomEnd = ZeroCornerSize, bottomStart = ZeroCornerSize)
      ) {
          SimpleExposedDropDownMenuImpl(
              values = values,
              selectedIndex = selectedIndex,
              onChange = onChange,
              label = label,
              modifier = modifier,
              backgroundColor = backgroundColor,
              shape = shape,
              decorator = { color, width, content ->
                  Box(
                      Modifier
                          .drawBehind {
                              val strokeWidth = width.value * density
                              val y = size.height - strokeWidth / 2
                              drawLine(
                                  color,
                                  Offset(0f, y),
                                  Offset(size.width, y),
                                  strokeWidth
                              )
                          }
                  ) {
                      content()
                  }
              }
          )
      }
      
      @Composable
      fun SimpleOutlinedExposedDropDownMenu(
          values: List<String>,
          selectedIndex: Int,
          onChange: (Int) -> Unit,
          label: @Composable () -> Unit,
          modifier: Modifier,
          backgroundColor: Color = MaterialTheme.colors.onSurface.copy(alpha = TextFieldDefaults.BackgroundOpacity),
          shape: Shape = MaterialTheme.shapes.small
      ) {
          SimpleExposedDropDownMenuImpl(
              values = values,
              selectedIndex = selectedIndex,
              onChange = onChange,
              label = label,
              modifier = modifier,
              backgroundColor = backgroundColor,
              shape = shape,
              decorator = { color, width, content ->
                  Box(
                      Modifier
                          .border(width, color, shape)
                  ) {
                      content()
                  }
              }
          )
      }
      
      @Composable
      private fun SimpleExposedDropDownMenuImpl(
          values: List<String>,
          selectedIndex: Int,
          onChange: (Int) -> Unit,
          label: @Composable () -> Unit,
          modifier: Modifier,
          backgroundColor: Color,
          shape: Shape,
          decorator: @Composable (Color, Dp, @Composable () -> Unit) -> Unit
      ) {
          var expanded by remember { mutableStateOf(false) }
          var textfieldSize by remember { mutableStateOf(Size.Zero) }
      
          val indicatorColor =
              if (expanded) MaterialTheme.colors.primary.copy(alpha = ContentAlpha.high)
              else MaterialTheme.colors.onSurface.copy(alpha = TextFieldDefaults.UnfocusedIndicatorLineOpacity)
          val indicatorWidth = (if (expanded) 2 else 1).dp
          val labelColor =
              if (expanded) MaterialTheme.colors.primary.copy(alpha = ContentAlpha.high)
              else MaterialTheme.colors.onSurface.copy(ContentAlpha.medium)
          val trailingIconColor = MaterialTheme.colors.onSurface.copy(alpha = TextFieldDefaults.IconOpacity)
      
          val rotation: Float by animateFloatAsState(if (expanded) 180f else 0f)
      
          val focusManager = LocalFocusManager.current
      
          Column(modifier = modifier.width(IntrinsicSize.Min)) {
              decorator(indicatorColor, indicatorWidth) {
                  Box(
                      Modifier
                          .fillMaxWidth()
                          .background(color = backgroundColor, shape = shape)
                          .onGloballyPositioned { textfieldSize = it.size.toSize() }
                          .clip(shape)
                          .clickable {
                              expanded = !expanded
                              focusManager.clearFocus()
                          }
                          .padding(start = 16.dp, end = 12.dp, top = 7.dp, bottom = 10.dp)
                  ) {
                      Column(Modifier.padding(end = 32.dp)) {
                          ProvideTextStyle(value = MaterialTheme.typography.caption.copy(color = labelColor)) {
                              label()
                          }
                          Text(
                              text = values[selectedIndex],
                              modifier = Modifier.padding(top = 1.dp)
                          )
                      }
                      Icon(
                          imageVector = Icons.Filled.ExpandMore,
                          contentDescription = "Change",
                          tint = trailingIconColor,
                          modifier = Modifier
                              .align(Alignment.CenterEnd)
                              .padding(top = 4.dp)
                              .rotate(rotation)
                      )
      
                  }
              }
      
              DropdownMenu(
                  expanded = expanded,
                  onDismissRequest = { expanded = false },
                  modifier = Modifier
                      .width(with(LocalDensity.current) { textfieldSize.width.toDp() })
              ) {
                  values.forEachIndexed { i, v ->
                      val scope = rememberCoroutineScope()
                      DropdownMenuItem(
                          onClick = {
                              onChange(i)
                              scope.launch {
                                  delay(150)
                                  expanded = false
                              }
                          }
                      ) {
                          Text(v)
                      }
                  }
              }
          }
      }
      

      【讨论】:

      • 感谢您分享此内容。这个看起来和工作方式与我期望的 ExposedDropdown 完全一样。我唯一需要改变的是使用Icons.Filled.ArrowDropDown 而不是Icons.Filled.ExpandMore
      猜你喜欢
      • 2022-11-06
      • 2021-12-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-04-30
      相关资源
      最近更新 更多