Android DataBinding下M3 TextField正则校验问题:输入不生效时如何修正
问题分析
你遇到的核心问题是:EditText的文本变更由用户输入直接触发,而单向绑定android:text="@{viewModel.text}"仅在ViewModel状态更新时同步UI,不会反向将UI的无效输入回滚到ViewModel的当前状态。当输入不符合规则时,ViewModel的text值未更新,但UI层的文本已经被用户修改,导致状态不一致。
解决方案
方案一:通过事件通知UI回滚文本(轻量且符合MVVM)
这种方式让ViewModel仅负责状态管理和校验,通过事件通知UI层执行回滚操作,不依赖View引用。
1. 修改ViewModel代码
添加一个用于通知回滚的SharedFlow,在校验不通过时发送事件:
private val _text = MutableStateFlow("") val text: StateFlow<String> = _text.asStateFlow() // 用于通知UI回滚文本的事件流 private val _rollbackTextEvent = MutableSharedFlow<Unit>() val rollbackTextEvent: SharedFlow<Unit> = _rollbackTextEvent.asSharedFlow() fun updateText(text: CharSequence) { val regex = "..." // 替换为你的正则校验规则 val isValid = Pattern.compile(regex).matcher(text).find() if (isValid) { _text.value = text.toString() } else { // 发送回滚事件 _rollbackTextEvent.tryEmit(Unit) } }
2. 在Activity/Fragment中监听事件
在UI层收集回滚事件,将EditText文本重置为ViewModel当前的text值:
// 假设你已经通过DataBinding获取了binding实例 lifecycleScope.launch { viewModel.rollbackTextEvent.collect { // 获取TextInputEditText并重置文本 binding.outlinedTextField.editText?.setText(viewModel.text.value) // 可选:将光标定位到文本末尾,提升用户体验 binding.outlinedTextField.editText?.setSelection(viewModel.text.value.length) } }
3. XML保持原配置不变
你的原有XML代码无需修改,继续使用单向绑定和onTextChanged回调。
方案二:自定义BindingAdapter(更优雅的双向绑定校验)
通过自定义BindingAdapter实现带校验的双向绑定,让文本校验和状态同步逻辑封装在Binding层,无需在UI层处理事件。
1. 编写自定义BindingAdapter
创建一个BindingAdapters类,实现带校验的双向绑定逻辑:
import android.text.Editable import android.text.TextWatcher import android.widget.EditText import androidx.databinding.BindingAdapter import androidx.databinding.InverseBindingAdapter import androidx.databinding.InverseBindingListener import java.util.regex.Pattern object BindingAdapters { // 正向绑定:将ViewModel的text同步到EditText @BindingAdapter("validatedText", "validationRegex", requireAll = true) fun bindValidatedText(editText: EditText, text: String, regex: String) { // 避免循环更新 if (editText.text.toString() != text) { editText.setText(text) } // 初始化保存初始文本作为回滚基准 editText.setTag(R.id.previous_text_tag, text) } // 反向绑定:从EditText获取文本并同步到ViewModel @InverseBindingAdapter(attribute = "validatedText") fun getValidatedText(editText: EditText): String { return editText.text.toString() } // 监听文本变化,触发校验和反向绑定 @BindingAdapter("app:validatedTextAttrChanged") fun setValidatedTextListener(editText: EditText, listener: InverseBindingListener) { editText.addTextChangedListener(object : TextWatcher { private var isRollingBack = false override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} override fun afterTextChanged(s: Editable?) { if (isRollingBack) { isRollingBack = false return } s?.let { input -> val regex = editText.tag as? String ?: return val isValid = Pattern.compile(regex).matcher(input).find() if (!isValid) { // 回滚到之前的文本 isRollingBack = true val previousText = editText.getTag(R.id.previous_text_tag) as? String ?: "" editText.setText(previousText) } else { // 保存当前有效文本作为下一次回滚的基准 editText.setTag(R.id.previous_text_tag, input.toString()) // 触发反向绑定,更新ViewModel listener.onChange() } } } }) } }
2. 添加资源ID(用于存储之前的文本)
在res/values/ids.xml中添加一个ID:
<resources> <item name="previous_text_tag" type="id"/> </resources>
3. 修改ViewModel代码
添加公开的正则表达式属性:
private val _text = MutableStateFlow("") val text: StateFlow<String> = _text.asStateFlow() // 公开正则校验规则,供BindingAdapter使用 val validationRegex = "..." // 替换为你的正则校验规则
4. 修改XML配置
替换原有的android:text和android:onTextChanged,使用自定义的双向绑定属性:
<com.google.android.material.textfield.TextInputEditText android:layout_width="match_parent" android:layout_height="wrap_content" app:validatedText="@={viewModel.text}" app:validationRegex="@{viewModel.validationRegex}" android:inputType="numberDecimal"/>
内容的提问来源于stack exchange,提问作者Mustafa Tatarhan
相关产品推荐
相关产品推荐

