与 EditText 的冲突:Viewer 的 Watcher + MutableLiveData 的 Observer

Conflict with EditText: Watcher to the View + Observer to the MutableLiveData

我无法理解 Fragment + ViewModel 范例如何与 EditText.

之类的视图一起工作

它是一个 EditText,显然会在视图(片段)中进行修改。但我也希望能够在 ViewModel 中修改它:例如擦除其文本。

片段中的代码如下 class:

public void onActivityCreated(@Nullable Bundle savedInstanceState) {
...
        comment = mViewModel.getComment();
        comment.observe(getViewLifecycleOwner(), new Observer<String>() {
            @Override
            public void onChanged(String s) {
                commentView.setText(s);
            }
        });
...
        commentView.addTextChangedListener(new TextWatcher() {
            @Override
            public void afterTextChanged(Editable s) {
                mViewModel.setComment(String.valueOf(s));
            }
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) { }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) { }
        });

如您所见,我设置了一个 observer,因此当我更改 MutableLiveData 的值时,视图会发生变化。我设置了一个 watcher 所以当我(使用应用程序时)更改视图的值时,MutableLiveData 会更改。

这是 ModelView 的代码 class:

public void addRegister() {
...
String comment = this.comment.getValue();
...
this.comment.setValue("");

当我运行应用程序没有弹出错误,但挂起。我猜是因为无限循环。 我应该如何使用这种 View + ViewModel 范例来处理 EditTexts?我有什么不明白的?

非常感谢!

您可以为此使用双向数据绑定:

  • 当用户输入文本时:实时数据将被更新
  • 如果您以编程方式设置实时数据值,EditText 内容将更新

您应该能够删除 activity 中的两个侦听器,因为数据绑定会为您完成。

build.gradle:

android {
    dataBinding {
        enabled = true
    }
}

布局:

  • 在顶层添加一个 <layout> 元素
  • 为您的视图模型定义一个变量
  • 将您的 EditText 连接到视图模型
<layout>
    <data>
        <variable
            name="viewModel"
            type="com.mycompany.AddRegisterViewModel" />
    </data>
    <EditText
                android:id="..."
                android:layout_width="..."
                android:layout_height="..."
                android:text="@={viewModel.getComment()}" />
</layout>

片段(抱歉,kotlin 示例):

  • 将 xml 中的 viewModel 字段与您的视图模型对象挂钩:
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        val binding: MyFragmentBinding = DataBindingUtil.inflate(inflater, R.layout.my_fragment, container, false)
        binding.setViewModel(myViewModel)

请注意,您需要等号,@= 才能进行双向数据绑定。如果您只使用 @{viewModel.getComment()},那么如果您以编程方式设置实时数据值,编辑文本将被更新,但其他方式将不起作用。

备注:

  • 如果您愿意,可以使用 ObservableField 而不是 MutableLiveData 进行数据绑定
  • 也许您可以使用字段引用而不是方法引用来引用 xml 中的实时数据,例如 @={viewModel.comment}

参考:Android 双向数据绑定文档:https://developer.android.com/topic/libraries/data-binding/two-way

在评论liveData的观察者中,先取消注册TextWatcher,然后在comment liveData中设置Text后,重新注册TextWatcher,应该没问题:)

由于接受的答案在所有情况下都不适合我(当 ViewModel 中的文本通过 EditText 本身之外的其他方式更改时),而且我也不想进行数据绑定,所以我想出了使用以下解决方案,其中一个标志跟踪由 TextWatcher 启动的更新,并在调用观察者时中断循环:

这是我在 Kotlin 中的代码。 对于 activity:

class SecondActivity : AppCompatActivity() {

    /** Flag avoids endless loops from TextWatcher and observer */
    private var textChangedByListener = true
    private val viewModel by viewModels<SecondViewModel>()
    private lateinit var binding:SecondActivityBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = SecondActivityBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.editText.addTextChangedListener(object: TextWatcher {
            override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3:     Int) {            }
            override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {                }

            override fun afterTextChanged(editable: Editable?) {
                textChangedByListener = true
                viewModel.editText = editable.toString()
            }

        })
        viewModel.editTextLiveData.observe(this) { text -> setEditTextFromViewModel(text) }
    }

    private fun setEditTextFromViewModel(text: String?) {
        if (!textChangedByListener) {
            //text change was not initiated by the EditText itself, and
            //therefore EditText does not yet contain the new text.
            binding.editText.setText(text)
        } else {
            //Don't move that outside of else, because it would then
            //immediately overwrite the value set by TextWatcher
            //which is triggered by the above setText() call.
            textChangedByListener = false
        }
    }

}

为了完整起见,还有 ViewModel:

class SecondViewModel() : ViewModel()
{
    var editText: String
        get() {
            return editTextLiveData.value ?: "InitialLiveData"
        }
        set(value) {
            editTextLiveData.value = value
        }

    var editTextLiveData = MutableLiveData<String>()
}

以防万一,您不熟悉视图绑定:您可以替换

binding.editText

findViewById(R.id.editTextId) as EditText.