【问题标题】:Conflict with EditText: Watcher to the View + Observer to the MutableLiveData与 EditText 冲突:View 的观察者 + MutableLiveData 的观察者
【发布时间】:2020-01-23 10:08:00
【问题描述】:

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

它是一个 EditText,显然会在视图(片段)中进行修改。但我也希望能够在 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?我不明白什么?

提前非常感谢!

【问题讨论】:

  • getComment() 的实现在哪里?你能展示你所有的 ViewModel 类吗
  • @Rafsanjani getComment() 只返回评论。这是片段:paste.debian.net/1127266,这是 ViewModel:paste.debian.net/1127265
  • 能否提供 ViewModel 类代码?
  • @SamirSpahic 是的,这里是:paste.debian.net/1127265
  • 尝试将getter字段改为LiveData类型而不是MutableLiveData

标签: java android view viewmodel mutablelivedata


【解决方案1】:

在你的观察者中评论liveData,只需先取消注册TextWatcher,然后在评论liveData中的setText之后,重新注册TextWatcher,应该没问题:)

【讨论】:

  • 注销 TextWatcher 是什么意思?
  • 当前您的代码是 commentView.addTextChangedListener(anonymouseTextWatcherClass) 。您可以这样做,首先初始化 textWatcher 对象,如 textWatcher = TextWatcher() 并将其保存为字段变量。然后,当您要为 EditText 设置值时,首先通过调用此函数commentView.removeTextChangedListener(textWatcher); 取消注册 textWatcher,然后您可以安全地设置值而无需触发 textWatcher。完成设置值后,再次注册您的 textWatcher
  • 很高兴听到 :) 也许您可以将其设置为已接受的答案
【解决方案2】:

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

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

您应该能够删除 Activity 中的两个侦听器,因为数据绑定会为您执行此操作。

build.gradle:

android {
    dataBinding {
        enabled = true
    }
}

布局:

  • 在顶层添加 &lt;layout&gt; 元素
  • 为您的视图模型定义一个变量
  • 将您的 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 字段与您的 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

【讨论】:

  • 非常感谢您的回复,我一定会调查的。虽然我读过一些关于数据绑定库的负面 cmets,但说将逻辑或函数放在静态 xml 文件中是不好的做法。你怎么看? Android 的数据绑定现在是标准吗?
  • 您应该避免在 xml 文件中放置任何重要的逻辑。但是在这里,作为一个典型的基本数据绑定示例,实际上没有逻辑:它只是将视图映射到视图模型字段(通常我将MutableLiveDatas 公开为字段(kotlin vals)所以我通常会使用android:text="@={viewModel.comment}"而不是函数getComment())
【解决方案3】:

由于接受的答案在所有情况下都不适用于我(当文本在 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.

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-09-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-09
    • 1970-01-01
    • 2018-06-15
    相关资源
    最近更新 更多