Jetpack Compose通用实现的DropDown。

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

Jetpack Compose DropDown with generic implementation

问题

Here's the translated code portion:

目前我有一个使用特定类型实现的 `DropDown`我想要将这个下拉菜单变成通用的这样我就可以在任何地方重用它并且去掉样板代码

**当前实现**

```kotlin
@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
)

OptionsFieldState 是 Compose UI 的自定义状态类

open class OptionsFieldState<T>(
    initialValue: T?,
    options: List<T> = listOf(),
    validators: List<Validator> = listOf(),
    onValueChanged: () -> Unit = {},
)
英文:

Currently, I have a DropDown implemented with particular types. I would like to make that drop-down menu generic so I can reuse it everywhere with custom objects and remove boilerplate code.

Current Implementation

    @Composable
fun SingleLineDropDown(optionsFieldState: OptionsFieldState&lt;String&gt;, 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 ?: &quot;&quot;,
            onValueChange = { optionsFieldState.value = it },
            enabled = false,
            error = optionsFieldState.error.orEmpty(),
            modifier = modifier
                .fillMaxWidth()
                .onGloballyPositioned { coordinates -&gt;
                    //This value is used to assign to the DropDown the same width
                    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 -&gt;
                    DropdownMenuItem(onClick = {
                        optionsFieldState.value = option
                        expanded = !expanded
                    }) {
                        Text(text = option)
                    }
                }
            }
        }
    }
}

Caller function

SingleLineDropDown(
    optionsFieldState = state.province,
    hintText = stringResource(id = R.string.province_territory),
    isEditable = state.isAddressEditable
)

OptionFiledState is custom state for compose UI

open class OptionsFieldState&lt;T&gt;(
    initialValue: T?,
    options: List&lt;T&gt; = listOf(),
    validators: List&lt;Validator&gt; = listOf(),
    onValueChanged: () -&gt; Unit = {},
)

答案1

得分: 1

If you want your drop-down menu to be applicable to generics custom objects, you can change your SingleLineDropDown function with generics, along side one extra parameters for the function to make it work.

@Composable
fun <T> SingleLineDropDown(
    optionsFieldState: OptionsFieldState<T>,
    hintText: String,
    isEditable: Boolean = true,
    displayText: @Composable (T) -> Unit // Composable function to display the selected option
) {
    // Rest of your code...

    Box {
        EditableRow(
            // Rest of your code...
        )
        if (isEditable) {
            DropdownMenu(
                // Rest of your code...
            ) {
                optionsFieldState.options.forEach { option ->
                    DropdownMenuItem(onClick = {
                        optionsFieldState.value = option
                        expanded = !expanded
                    }) {
                        displayText(option) // Call the displayText function to show the selected option
                    }
                }
            }
        }
    }
}

Now you can use it the following way:

SingleLineDropDown(
    optionsFieldState = state.province,
    hintText = stringResource(id = R.string.province_territory),
    isEditable = state.isAddressEditable
) { option ->
    // Customize how the selected option is displayed
    Text(text = option.name) // Assuming the custom object has a "name" property
}

Update
You can also provide a default value for the generic to be used in the EditableRow, something like this:

@Composable
fun <T> SingleLineDropDown(
    optionsFieldState: OptionsFieldState<T>,
    hintText: String,
    isEditable: Boolean = true,
    displayText: @Composable (T) -> Unit,
    defaultOptionValue: T
) {
    // Rest of your code...

    Box {
        EditableRow(
            value = optionsFieldState.value ?: defaultOptionValue,
            onValueChange = { optionsFieldState.value = it },
            enabled = false,
            error = optionsFieldState.error.orEmpty(),
            modifier = modifier
                .fillMaxWidth()
                .onGloballyPositioned { coordinates ->
                    // This value is used to assign to the DropDown the same width
                    textFieldSize = coordinates.size.toSize()
                },
            hintText = hintText,
            trailingIcon = {
                Icon(
                    icon, null, modifier
                )
            },
            colors = TextFieldDefaults.outlinedTextFieldColors(
                disabledTextColor = if (isEditable) MaterialTheme.colors.onBackground else Color.LightGray
            )
        )
        // Rest of your code...
    }
}
英文:

If you want your drop-down menu to be applicable to generics custom objects, you can change your SingleLineDropDown function with generics, along side one extra parameters for the function to make it work

Follow this example to understand how it can be done:

@Composable
fun &lt;T&gt; SingleLineDropDown(
    optionsFieldState: OptionsFieldState&lt;T&gt;,
    hintText: String,
    isEditable: Boolean = true,
    displayText: @Composable (T) -&gt; Unit // Composable function to display the selected option
) {
    // Rest of your code...

    Box {
        EditableRow(
            // Rest of your code...
        )
        if (isEditable) {
            DropdownMenu(
                // Rest of your code...
            ) {
                optionsFieldState.options.forEach { option -&gt;
                    DropdownMenuItem(onClick = {
                        optionsFieldState.value = option
                        expanded = !expanded
                    }) {
                        displayText(option) // Call the displayText function to show the selected option
                    }
                }
            }
        }
    }
}

Now you can use it the following way:

SingleLineDropDown(
    optionsFieldState = state.province,
    hintText = stringResource(id = R.string.province_territory),
    isEditable = state.isAddressEditable
) { option -&gt;
    // Customize how the selected option is displayed
    Text(text = option.name) // Assuming the custom object has a &quot;name&quot; property
}

Update
You can also provide a default value for the generic to be used in the EditableRow, something like that:

@Composable
fun &lt;T&gt; SingleLineDropDown(
    optionsFieldState: OptionsFieldState&lt;T&gt;,
    hintText: String,
    isEditable: Boolean = true,
    displayText: @Composable (T) -&gt; Unit,
    defaultOptionValue : T
) {
    // Rest of your code...

    Box {
        EditableRow(
            value = optionsFieldState.value ?: defaultOptionValue,
            onValueChange = { optionsFieldState.value = it },
            enabled = false,
            error = optionsFieldState.error.orEmpty(),
            modifier = modifier
                .fillMaxWidth()
                .onGloballyPositioned { coordinates -&gt;
                    //This value is used to assign to the DropDown the same width
                    textFieldSize = coordinates.size.toSize()
                },
            hintText = hintText,
            trailingIcon = {
                Icon(
                    icon, null, modifier
                )
            },
            colors = TextFieldDefaults.outlinedTextFieldColors(
                disabledTextColor = if (isEditable) MaterialTheme.colors.onBackground else Color.LightGray
            )
        )
       // Rest of your code...

huangapple
  • 本文由 发表于 2023年5月22日 01:00:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/76301010.html
匿名

发表评论

匿名网友

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

确定