在 Jetpack Compose 中过滤文本字段中的输入时出现奇怪的行为
Weird behavior when filtering input in a text field in Jetpack Compose
我通过将 activity 迁移到现有的 Android 应用中,将 Jetpack Compose 1.1.1 引入它。在 activity 的 XML 布局中,我有一个 EditText
配置为 android:inputType="numberDecimal"
。
在 Compose 版本中,我现在有一个视图模型,它包含一个 TextFieldValue
作为状态对象的一部分(通过 StateFlow
在可组合项中观察),以及一个方法在 TextField
的 onValueChange
中调用。此方法检查输入是否可以解析为十进制数,如果是,则将更改应用到状态对象,如果不是,则保留以前的值。
但是,我注意到有时我在 onValueChange
中获得的输入是被视图模型方法拒绝的先前文本(而不是状态对象中的当前值),这会导致什么被拒绝的有效输入。就好像 TextField
将先前键入的文本保持在某种内部状态,即使我的状态尚未更新。
这里是相关代码的提炼:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme {
Screen()
}
}
}
}
class MainViewModel: ViewModel() {
data class State(
val prompt: String,
val input: TextFieldValue,
)
private val innerState = MutableStateFlow(State(prompt = "Prompt", input = TextFieldValue("")))
val state = innerState.asStateFlow()
private val decimalSeparator by lazy { DecimalFormatSymbols.getInstance().decimalSeparator }
private val decimalMatcher by lazy {
Pattern.compile("(0|[1-9]\d*)(${"\$decimalSeparator"}\d*)?").matcher("")
}
fun setInput(input: TextFieldValue) {
Log.i("MainViewModel", "Got input: $input")
val newValue = when {
input.text.isEmpty() -> {
// Accept empty string
input
}
decimalMatcher.reset(input.text).matches() -> {
// Accept input
input
}
else -> {
// Reject input (apply previous value)
state.value.input
}
}
Log.i("MainViewModel", "Applying input: $newValue")
innerState.value = innerState.value.copy(input = newValue)
}
}
@Composable
fun Screen(viewModel: MainViewModel = viewModel()) {
val state by viewModel.state.collectAsState()
Column {
Text(text = state.prompt)
TextField(value = state.input, onValueChange = { viewModel.setInput(it) })
}
}
如果我键入以下字符序列 ['2', '.', '.', '.', '5']
这是我得到的日志输出:
I/MainViewModel: Got input: TextFieldValue(text='2', selection=TextRange(1, 1), composition=null)
I/MainViewModel: Applying input: TextFieldValue(text='2', selection=TextRange(1, 1), composition=null)
I/MainViewModel: Got input: TextFieldValue(text='2.', selection=TextRange(2, 2), composition=null)
I/MainViewModel: Applying input: TextFieldValue(text='2.', selection=TextRange(2, 2), composition=null)
I/MainViewModel: Got input: TextFieldValue(text='2..', selection=TextRange(3, 3), composition=null)
I/MainViewModel: Applying input: TextFieldValue(text='2.', selection=TextRange(2, 2), composition=null)
I/MainViewModel: Got input: TextFieldValue(text='2...', selection=TextRange(4, 4), composition=null)
I/MainViewModel: Applying input: TextFieldValue(text='2.', selection=TextRange(2, 2), composition=null)
I/MainViewModel: Got input: TextFieldValue(text='2...5', selection=TextRange(5, 5), composition=null)
I/MainViewModel: Applying input: TextFieldValue(text='2.', selection=TextRange(2, 2), composition=null)
我期望的是第二个和第三个 .
字符将被拒绝,当输入 5
时,文本字段将显示为 2.5
,但 5 是也被拒绝了,因为我得到的输入是 2...5
(即使在我的状态下它仍然是 2.
)。
顺便说一句,这里是我已经尝试过的示例代码的一些变体:
- 用
MainScreen
中的 var input by remember { mutableStateOf(TextFieldValue("")) }
替换视图模型状态,直接在 onValueChange
lambda 中进行过滤:行为完全相同
- 在我的状态对象中使用简单的
String
而不是 TextFieldValue
:有时我会得到预期的输入,但其他时候我会像以前一样得到重复的 .
。更奇怪的是,如果我等待足够长的时间并再次输入,那么我将得到预期的输入,这对于 TextFieldValue
案例 是不正确的
这对我来说像是一个错误,但我想我会先在这里问一下,以防我以某种明显的方式滥用框架。
我认为这是 Compose 中的一个已知错误。在问题跟踪器中查看此问题。
2022 年 1 月 22 日有一个修复它的提交,但也许它还没有进入发布......
https://issuetracker.google.com/issues/200577798
此外,这个已于 2022 年 2 月 9 日修复的相关问题可能还没有发布
https://issuetracker.google.com/issues/206656075
我通过将 activity 迁移到现有的 Android 应用中,将 Jetpack Compose 1.1.1 引入它。在 activity 的 XML 布局中,我有一个 EditText
配置为 android:inputType="numberDecimal"
。
在 Compose 版本中,我现在有一个视图模型,它包含一个 TextFieldValue
作为状态对象的一部分(通过 StateFlow
在可组合项中观察),以及一个方法在 TextField
的 onValueChange
中调用。此方法检查输入是否可以解析为十进制数,如果是,则将更改应用到状态对象,如果不是,则保留以前的值。
但是,我注意到有时我在 onValueChange
中获得的输入是被视图模型方法拒绝的先前文本(而不是状态对象中的当前值),这会导致什么被拒绝的有效输入。就好像 TextField
将先前键入的文本保持在某种内部状态,即使我的状态尚未更新。
这里是相关代码的提炼:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme {
Screen()
}
}
}
}
class MainViewModel: ViewModel() {
data class State(
val prompt: String,
val input: TextFieldValue,
)
private val innerState = MutableStateFlow(State(prompt = "Prompt", input = TextFieldValue("")))
val state = innerState.asStateFlow()
private val decimalSeparator by lazy { DecimalFormatSymbols.getInstance().decimalSeparator }
private val decimalMatcher by lazy {
Pattern.compile("(0|[1-9]\d*)(${"\$decimalSeparator"}\d*)?").matcher("")
}
fun setInput(input: TextFieldValue) {
Log.i("MainViewModel", "Got input: $input")
val newValue = when {
input.text.isEmpty() -> {
// Accept empty string
input
}
decimalMatcher.reset(input.text).matches() -> {
// Accept input
input
}
else -> {
// Reject input (apply previous value)
state.value.input
}
}
Log.i("MainViewModel", "Applying input: $newValue")
innerState.value = innerState.value.copy(input = newValue)
}
}
@Composable
fun Screen(viewModel: MainViewModel = viewModel()) {
val state by viewModel.state.collectAsState()
Column {
Text(text = state.prompt)
TextField(value = state.input, onValueChange = { viewModel.setInput(it) })
}
}
如果我键入以下字符序列 ['2', '.', '.', '.', '5']
这是我得到的日志输出:
I/MainViewModel: Got input: TextFieldValue(text='2', selection=TextRange(1, 1), composition=null)
I/MainViewModel: Applying input: TextFieldValue(text='2', selection=TextRange(1, 1), composition=null)
I/MainViewModel: Got input: TextFieldValue(text='2.', selection=TextRange(2, 2), composition=null)
I/MainViewModel: Applying input: TextFieldValue(text='2.', selection=TextRange(2, 2), composition=null)
I/MainViewModel: Got input: TextFieldValue(text='2..', selection=TextRange(3, 3), composition=null)
I/MainViewModel: Applying input: TextFieldValue(text='2.', selection=TextRange(2, 2), composition=null)
I/MainViewModel: Got input: TextFieldValue(text='2...', selection=TextRange(4, 4), composition=null)
I/MainViewModel: Applying input: TextFieldValue(text='2.', selection=TextRange(2, 2), composition=null)
I/MainViewModel: Got input: TextFieldValue(text='2...5', selection=TextRange(5, 5), composition=null)
I/MainViewModel: Applying input: TextFieldValue(text='2.', selection=TextRange(2, 2), composition=null)
我期望的是第二个和第三个 .
字符将被拒绝,当输入 5
时,文本字段将显示为 2.5
,但 5 是也被拒绝了,因为我得到的输入是 2...5
(即使在我的状态下它仍然是 2.
)。
顺便说一句,这里是我已经尝试过的示例代码的一些变体:
- 用
MainScreen
中的var input by remember { mutableStateOf(TextFieldValue("")) }
替换视图模型状态,直接在onValueChange
lambda 中进行过滤:行为完全相同 - 在我的状态对象中使用简单的
String
而不是TextFieldValue
:有时我会得到预期的输入,但其他时候我会像以前一样得到重复的.
。更奇怪的是,如果我等待足够长的时间并再次输入,那么我将得到预期的输入,这对于TextFieldValue
案例 是不正确的
这对我来说像是一个错误,但我想我会先在这里问一下,以防我以某种明显的方式滥用框架。
我认为这是 Compose 中的一个已知错误。在问题跟踪器中查看此问题。 2022 年 1 月 22 日有一个修复它的提交,但也许它还没有进入发布......
https://issuetracker.google.com/issues/200577798
此外,这个已于 2022 年 2 月 9 日修复的相关问题可能还没有发布 https://issuetracker.google.com/issues/206656075