与EditText冲突:观察者对视图+观察者对MutableLiveData

时间:2020-01-23 10:08:00

标签: java android view viewmodel mutablelivedata

我无法理解 Fragment + ViewModel 范例如何与像 EditText 这样的View一起使用。

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

这是Fragment类中的代码:

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) { }
        });

如您所见,我设置了一个观察者,因此当我更改MutableLiveData的值时,视图也会更改。我设置了 watcher ,因此(当我使用应用程序时)更改View的值时,MutableLiveData也会更改。

这是ModelView类的代码:

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

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

非常感谢!

3 个答案:

答案 0 :(得分:0)

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

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

您应该能够删除活动中的两个侦听器,因为数据绑定会为您这样做。

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>

片段(对不起,科特琳的例子):

  • 使用您的viewmodel对象将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进行数据绑定
  • 也许您可以使用字段引用而不是像@={viewModel.comment}这样的方法引用来引用xml中的实时数据

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

答案 1 :(得分:0)

在您的评论liveData观察者中,只需先取消注册TextWatcher,然后从评论liveData中取消setText,然后重新注册TextWatcher,就可以了:)

答案 2 :(得分:0)

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

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

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