与 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
.
我无法理解 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
.