Jetpack Compose下拉菜单泛型改造需求及现有实现
将特定类型Dropdown改造为泛型组件
我已经实现了一个针对String类型的下拉菜单,现在需要将其改造为泛型组件,以便在任意场景复用自定义对象,减少样板代码。
当前实现
@Composable fun SingleLineDropDown(optionsFieldState: OptionsFieldState<String>, hintText: String, isEditable: Boolean = true) { var expanded by remember { mutableStateOf(false) } var textFieldSize by remember { mutableStateOf(Size.Zero) } val icon = if (expanded) Icons.Filled.KeyboardArrowUp else Icons.Filled.KeyboardArrowDown val modifier = if (isEditable) { Modifier.clickable { expanded = !expanded } } else Modifier Box { EditableRow( value = optionsFieldState.value ?: "", onValueChange = { optionsFieldState.value = it }, enabled = false, error = optionsFieldState.error.orEmpty(), modifier = modifier .fillMaxWidth() .onGloballyPositioned { coordinates -> // 用这个值让下拉菜单和输入框宽度一致 textFieldSize = coordinates.size.toSize() }, hintText = hintText, trailingIcon = { Icon( icon, null, modifier ) }, colors = TextFieldDefaults.outlinedTextFieldColors( disabledTextColor = if (isEditable) MaterialTheme.colors.onBackground else Color.LightGray ) ) if (isEditable) { DropdownMenu( expanded = expanded, onDismissRequest = { expanded = false }, modifier = Modifier.width(with(LocalDensity.current) { textFieldSize.width.toDp() }) ) { optionsFieldState.options.forEach { option -> DropdownMenuItem(onClick = { optionsFieldState.value = option expanded = !expanded }) { Text(text = option) } } } } } }
调用示例
SingleLineDropDown( optionsFieldState = state.province, hintText = stringResource(id = R.string.province_territory), isEditable = state.isAddressEditable )
OptionFieldState说明
OptionFieldState是Compose UI的自定义状态类,定义如下:
open class OptionsFieldState<T>( initialValue: T?, options: List<T> = listOf(), validators: List<Validator> = listOf(), onValueChanged: () -> Unit = {}, )
改造方案
要将组件改为泛型,核心是解决自定义对象的文本显示问题,因为泛型类型T无法直接作为Text组件的输入。我们需要添加一个函数参数,用来将泛型对象转换为可显示的字符串。
改造后的泛型组件代码
@Composable fun <T> SingleLineDropDown( optionsFieldState: OptionsFieldState<T>, hintText: String, labelProvider: (T) -> String, // 新增:将泛型对象转为显示文本的函数 isEditable: Boolean = true ) { var expanded by remember { mutableStateOf(false) } var textFieldSize by remember { mutableStateOf(Size.Zero) } val icon = if (expanded) Icons.Filled.KeyboardArrowUp else Icons.Filled.KeyboardArrowDown val modifier = if (isEditable) { Modifier.clickable { expanded = !expanded } } else Modifier Box { EditableRow( // 将泛型对象转为显示文本,为空时显示空字符串 value = optionsFieldState.value?.let(labelProvider) ?: "", onValueChange = { /* 泛型场景下输入框不可编辑,此处留空或按需处理 */ }, enabled = false, error = optionsFieldState.error.orEmpty(), modifier = modifier .fillMaxWidth() .onGloballyPositioned { coordinates -> textFieldSize = coordinates.size.toSize() }, hintText = hintText, trailingIcon = { Icon(icon, null, modifier) }, colors = TextFieldDefaults.outlinedTextFieldColors( disabledTextColor = if (isEditable) MaterialTheme.colors.onBackground else Color.LightGray ) ) if (isEditable) { DropdownMenu( expanded = expanded, onDismissRequest = { expanded = false }, modifier = Modifier.width(with(LocalDensity.current) { textFieldSize.width.toDp() }) ) { optionsFieldState.options.forEach { option -> DropdownMenuItem(onClick = { optionsFieldState.value = option expanded = false }) { // 使用labelProvider转换对象为显示文本 Text(text = labelProvider(option)) } } } } } }
关键改动说明
- 泛型声明:在函数名后添加
<T>,将OptionsFieldState<String>改为OptionsFieldState<T>,让组件支持任意类型。 - 新增labelProvider参数:通过
(T) -> String类型的函数,把泛型对象转换为界面上显示的文本,解决自定义对象无法直接显示的问题。 - 文本显示适配:
- EditableRow的value字段:通过
optionsFieldState.value?.let(labelProvider) ?: ""将选中的泛型对象转为显示文本。 - DropdownMenuItem中的Text:调用
labelProvider(option)显示每个选项的文本。
- EditableRow的value字段:通过
- onValueChange处理:因为泛型场景下输入框是不可编辑的(原代码中enabled=false),所以onValueChange可以留空,或者根据需求做兼容。
调用泛型组件示例
假设我们有一个自定义的Province类:
data class Province(val id: Int, val name: String)
对应的OptionsFieldState:
val provinceState = remember { OptionsFieldState<Province>( initialValue = null, options = listOf( Province(1, "北京"), Province(2, "上海"), Province(3, "广东") ) ) }
调用泛型组件:
SingleLineDropDown( optionsFieldState = provinceState, hintText = stringResource(id = R.string.province_territory), labelProvider = { it.name }, // 指定显示Province的name字段 isEditable = true )
内容的提问来源于stack exchange,提问作者Arpit Patel
相关产品推荐
相关产品推荐

