You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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))
                    }
                }
            }
        }
    }
}

关键改动说明

  1. 泛型声明:在函数名后添加<T>,将OptionsFieldState<String>改为OptionsFieldState<T>,让组件支持任意类型。
  2. 新增labelProvider参数:通过(T) -> String类型的函数,把泛型对象转换为界面上显示的文本,解决自定义对象无法直接显示的问题。
  3. 文本显示适配:
    • EditableRow的value字段:通过optionsFieldState.value?.let(labelProvider) ?: ""将选中的泛型对象转为显示文本。
    • DropdownMenuItem中的Text:调用labelProvider(option)显示每个选项的文本。
  4. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 06:10:38