Jetpack Compose 是否提供 Material AutoComplete TextView 替代品?
Does Jetpack Compose offer a Material AutoComplete TextView replacement?
在将我的应用程序迁移到 Jetpack compose 的过程中,我遇到了我的应用程序的一部分,其中 TextField 需要自动完成功能。
但是,从版本 1.0.0-alpha05 开始,我找不到任何使用 Compose API 来实现此目的的功能。我发现最接近的是 DropdownMenu
和 DropdownMenuItem
可组合项,但似乎需要大量手动管道才能从中创建自动完成菜单。
当然,显而易见的事情就是等待 Jetpack Compose 的未来更新。但是我想知道,有没有人在他们的迁移中遇到这个问题找到了解决方案?
正如您所说,目前还没有这个组件。您有两个选择:使用 DropDownMenu
和 BaseTextField
创建您自己的自定义或使用混合 xml-autocomplete 并通过 androidx.compose.ui.platform.ComposeView
撰写屏幕
检查我使用 XML 制作的代码并在 compose 中使用该布局
使用 AndroidView
。我们可以使用这个解决方案,直到它被默认包含在 compose 中。
您可以根据需要对其进行自定义和设置样式。我亲自在我的项目中尝试过,效果很好
<!-- text_input_field.xml -->
<!-- You can style your textfield here in XML with styles -->
<!-- this file should be in res/layout -->
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.textfield.TextInputLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<AutoCompleteTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Label"
android:inputType="none" />
</com.google.android.material.textfield.TextInputLayout>
// TextFieldWithDropDown.kt
// TextField with dropdown was not included by default in jetpack compose (1.0.2) and less
@Composable
fun TextFieldWithDropDown(
items: List<String>,
selectedValue: String?,
modifier: Modifier = Modifier,
onSelect: (Int) -> Unit
) {
AndroidView(
factory = { context ->
val textInputLayout = TextInputLayout
.inflate(context, R.layout.text_input_field, null) as TextInputLayout
// If you need to use different styled layout for light and dark themes
// you can create two different xml layouts one for light and another one for dark
// and inflate the one you need here.
val autoCompleteTextView = textInputLayout.editText as? AutoCompleteTextView
val adapter = ArrayAdapter(context, android.R.layout.simple_list_item_1, items)
autoCompleteTextView?.setAdapter(adapter)
autoCompleteTextView?.setText(selectedValue, false)
autoCompleteTextView?.setOnItemClickListener { _, _, index, _ -> onSelect(index) }
textInputLayout
},
update = { textInputLayout ->
// This block will be called when recomposition happens
val autoCompleteTextView = textInputLayout.editText as? AutoCompleteTextView
val adapter = ArrayAdapter(textInputLayout.context, android.R.layout.simple_list_item_1, items)
autoCompleteTextView?.setAdapter(adapter)
autoCompleteTextView?.setText(selectedValue, false)
},
modifier = modifier
)
}
// MainActivity.kt
// It's important to use AppCompatActivity instead of ComponentActivity to get the material
// look on our XML based textfield
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Column {
TextFieldWithDropDown(
items = listOf("One", "Two", "Three"),
selectedValue = "Two",
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
// You can also set the value to a state
index -> println("$index was selected")
}
}
}
}
}
至少在 v1.0.2 之前没有
所以我在 this gist
中实现了一个很好用的 compose
我也放在这里:
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.window.PopupProperties
@Composable
fun TextFieldWithDropdown(
modifier: Modifier = Modifier,
value: TextFieldValue,
setValue: (TextFieldValue) -> Unit,
onDismissRequest: () -> Unit,
dropDownExpanded: Boolean,
list: List<String>,
label: String = ""
) {
Box(modifier) {
TextField(
modifier = Modifier
.fillMaxWidth()
.onFocusChanged { focusState ->
if (!focusState.isFocused)
onDismissRequest()
},
value = value,
onValueChange = setValue,
label = { Text(label) },
colors = TextFieldDefaults.outlinedTextFieldColors()
)
DropdownMenu(
expanded = dropDownExpanded,
properties = PopupProperties(
focusable = false,
dismissOnBackPress = true,
dismissOnClickOutside = true
),
onDismissRequest = onDismissRequest
) {
list.forEach { text ->
DropdownMenuItem(onClick = {
setValue(
TextFieldValue(
text,
TextRange(text.length)
)
)
}) {
Text(text = text)
}
}
}
}
}
使用方法
val all = listOf("aaa", "baa", "aab", "abb", "bab")
val dropDownOptions = mutableStateOf(listOf<String>())
val textFieldValue = mutableStateOf(TextFieldValue())
val dropDownExpanded = mutableStateOf(false)
fun onDropdownDismissRequest() {
dropDownExpanded.value = false
}
fun onValueChanged(value: TextFieldValue) {
dropDownExpanded.value = true
textFieldValue.value = value
dropDownOptions.value = all.filter { it.startsWith(value.text) && it != value.text }.take(3)
}
@Composable
fun TextFieldWithDropdownUsage() {
TextFieldWithDropdown(
modifier = Modifier.fillMaxWidth(),
value = textFieldValue.value,
setValue = ::onValueChanged,
onDismissRequest = ::onDropdownDismissRequest,
dropDownExpanded = dropDownExpanded.value,
list = dropDownOptions.value,
label = "Label"
)
从 compose 1.1.0-alpha06 开始,Compose Material 现在提供 ExposedDropdownMenu
可组合项 API here,可用于实现下拉菜单这有助于自动完成过程。实际的自动完成逻辑必须自己实现。
API 文档给出了以下用于可编辑字段的用法示例:
val options = listOf("Option 1", "Option 2", "Option 3", "Option 4", "Option 5")
var exp by remember { mutableStateOf(false) }
var selectedOption by remember { mutableStateOf("") }
ExposedDropdownMenuBox(expanded = exp, onExpandedChange = { exp = !exp }) {
TextField(
value = selectedOptionText,
onValueChange = { selectedOptionText = it },
label = { Text("Label") },
trailingIcon = {
ExposedDropdownMenuDefaults.TrailingIcon(expanded = exp)
},
colors = ExposedDropdownMenuDefaults.textFieldColors()
)
// filter options based on text field value (i.e. crude autocomplete)
val filterOpts = options.filter { it.contains(selectedOption, ignoreCase = true) }
if (filterOpts.isNotEmpty()) {
ExposedDropdownMenu(expanded = exp, onDismissRequest = { exp = false }) {
filterOpts.forEach { option ->
DropdownMenuItem(
onClick = {
selectedOption = option
exp = false
}
) {
Text(text = option)
}
}
}
}
}
在将我的应用程序迁移到 Jetpack compose 的过程中,我遇到了我的应用程序的一部分,其中 TextField 需要自动完成功能。
但是,从版本 1.0.0-alpha05 开始,我找不到任何使用 Compose API 来实现此目的的功能。我发现最接近的是 DropdownMenu
和 DropdownMenuItem
可组合项,但似乎需要大量手动管道才能从中创建自动完成菜单。
当然,显而易见的事情就是等待 Jetpack Compose 的未来更新。但是我想知道,有没有人在他们的迁移中遇到这个问题找到了解决方案?
正如您所说,目前还没有这个组件。您有两个选择:使用 DropDownMenu
和 BaseTextField
创建您自己的自定义或使用混合 xml-autocomplete 并通过 androidx.compose.ui.platform.ComposeView
检查我使用 XML 制作的代码并在 compose 中使用该布局
使用 AndroidView
。我们可以使用这个解决方案,直到它被默认包含在 compose 中。
您可以根据需要对其进行自定义和设置样式。我亲自在我的项目中尝试过,效果很好
<!-- text_input_field.xml -->
<!-- You can style your textfield here in XML with styles -->
<!-- this file should be in res/layout -->
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.textfield.TextInputLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<AutoCompleteTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Label"
android:inputType="none" />
</com.google.android.material.textfield.TextInputLayout>
// TextFieldWithDropDown.kt
// TextField with dropdown was not included by default in jetpack compose (1.0.2) and less
@Composable
fun TextFieldWithDropDown(
items: List<String>,
selectedValue: String?,
modifier: Modifier = Modifier,
onSelect: (Int) -> Unit
) {
AndroidView(
factory = { context ->
val textInputLayout = TextInputLayout
.inflate(context, R.layout.text_input_field, null) as TextInputLayout
// If you need to use different styled layout for light and dark themes
// you can create two different xml layouts one for light and another one for dark
// and inflate the one you need here.
val autoCompleteTextView = textInputLayout.editText as? AutoCompleteTextView
val adapter = ArrayAdapter(context, android.R.layout.simple_list_item_1, items)
autoCompleteTextView?.setAdapter(adapter)
autoCompleteTextView?.setText(selectedValue, false)
autoCompleteTextView?.setOnItemClickListener { _, _, index, _ -> onSelect(index) }
textInputLayout
},
update = { textInputLayout ->
// This block will be called when recomposition happens
val autoCompleteTextView = textInputLayout.editText as? AutoCompleteTextView
val adapter = ArrayAdapter(textInputLayout.context, android.R.layout.simple_list_item_1, items)
autoCompleteTextView?.setAdapter(adapter)
autoCompleteTextView?.setText(selectedValue, false)
},
modifier = modifier
)
}
// MainActivity.kt
// It's important to use AppCompatActivity instead of ComponentActivity to get the material
// look on our XML based textfield
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Column {
TextFieldWithDropDown(
items = listOf("One", "Two", "Three"),
selectedValue = "Two",
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
// You can also set the value to a state
index -> println("$index was selected")
}
}
}
}
}
至少在 v1.0.2 之前没有
所以我在 this gist
中实现了一个很好用的 compose我也放在这里:
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.window.PopupProperties
@Composable
fun TextFieldWithDropdown(
modifier: Modifier = Modifier,
value: TextFieldValue,
setValue: (TextFieldValue) -> Unit,
onDismissRequest: () -> Unit,
dropDownExpanded: Boolean,
list: List<String>,
label: String = ""
) {
Box(modifier) {
TextField(
modifier = Modifier
.fillMaxWidth()
.onFocusChanged { focusState ->
if (!focusState.isFocused)
onDismissRequest()
},
value = value,
onValueChange = setValue,
label = { Text(label) },
colors = TextFieldDefaults.outlinedTextFieldColors()
)
DropdownMenu(
expanded = dropDownExpanded,
properties = PopupProperties(
focusable = false,
dismissOnBackPress = true,
dismissOnClickOutside = true
),
onDismissRequest = onDismissRequest
) {
list.forEach { text ->
DropdownMenuItem(onClick = {
setValue(
TextFieldValue(
text,
TextRange(text.length)
)
)
}) {
Text(text = text)
}
}
}
}
}
使用方法
val all = listOf("aaa", "baa", "aab", "abb", "bab")
val dropDownOptions = mutableStateOf(listOf<String>())
val textFieldValue = mutableStateOf(TextFieldValue())
val dropDownExpanded = mutableStateOf(false)
fun onDropdownDismissRequest() {
dropDownExpanded.value = false
}
fun onValueChanged(value: TextFieldValue) {
dropDownExpanded.value = true
textFieldValue.value = value
dropDownOptions.value = all.filter { it.startsWith(value.text) && it != value.text }.take(3)
}
@Composable
fun TextFieldWithDropdownUsage() {
TextFieldWithDropdown(
modifier = Modifier.fillMaxWidth(),
value = textFieldValue.value,
setValue = ::onValueChanged,
onDismissRequest = ::onDropdownDismissRequest,
dropDownExpanded = dropDownExpanded.value,
list = dropDownOptions.value,
label = "Label"
)
从 compose 1.1.0-alpha06 开始,Compose Material 现在提供 ExposedDropdownMenu
可组合项 API here,可用于实现下拉菜单这有助于自动完成过程。实际的自动完成逻辑必须自己实现。
API 文档给出了以下用于可编辑字段的用法示例:
val options = listOf("Option 1", "Option 2", "Option 3", "Option 4", "Option 5")
var exp by remember { mutableStateOf(false) }
var selectedOption by remember { mutableStateOf("") }
ExposedDropdownMenuBox(expanded = exp, onExpandedChange = { exp = !exp }) {
TextField(
value = selectedOptionText,
onValueChange = { selectedOptionText = it },
label = { Text("Label") },
trailingIcon = {
ExposedDropdownMenuDefaults.TrailingIcon(expanded = exp)
},
colors = ExposedDropdownMenuDefaults.textFieldColors()
)
// filter options based on text field value (i.e. crude autocomplete)
val filterOpts = options.filter { it.contains(selectedOption, ignoreCase = true) }
if (filterOpts.isNotEmpty()) {
ExposedDropdownMenu(expanded = exp, onDismissRequest = { exp = false }) {
filterOpts.forEach { option ->
DropdownMenuItem(
onClick = {
selectedOption = option
exp = false
}
) {
Text(text = option)
}
}
}
}
}