在 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 在可组合项中观察),以及一个方法在 TextFieldonValueChange 中调用。此方法检查输入是否可以解析为十进制数,如果是,则将更改应用到状态对象,如果不是,则保留以前的值。

但是,我注意到有时我在 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.)。

顺便说一句,这里是我已经尝试过的示例代码的一些变体:

这对我来说像是一个错误,但我想我会先在这里问一下,以防我以某种明显的方式滥用框架。

我认为这是 Compose 中的一个已知错误。在问题跟踪器中查看此问题。 2022 年 1 月 22 日有一个修复它的提交,但也许它还没有进入发布......

https://issuetracker.google.com/issues/200577798

此外,这个已于 2022 年 2 月 9 日修复的相关问题可能还没有发布 https://issuetracker.google.com/issues/206656075