Android View XML 中的数据绑定条件语句过多,如何在没有额外侦听器的情况下进入 ViewModel
Too many databinding conditional statements in Android View XML, How to move into ViewModel without additional listener
这是我可以在这里展示的最简单的示例:
片段(以备不时之需,但不重要!!)
class EgFragment:Fragment() {
private lateinit var viewModel: EgViewModel
private lateinit var binding: EgBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = EgBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(this, ViewModelFactory(requireContext(), this))
.get(EgViewModel::class.java)
binding.setLifecycleOwner(viewLifecycleOwner)
binding.vm = viewModel
}
}
查看模型
class EgViewModel: ViewModel() {
val emailInput:MutableLiveData<String> = MutableLiveData("")
val phoneInput:MutableLiveData<String> = MutableLiveData("")
val phoneEnabled:MutableLiveData<Boolean> = MutableLiveData(false)
}
Layout/XML
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="vm"
type="air.EgViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:id="@+id/email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@={vm.emailInput}"
app:layout_constraintBottom_toTopOf="@id/phone"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/phone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:enabled="@{vm.emailInput.length()>6}"
android:text="@={vm.phoneInput}"
app:layout_constraintTop_toBottomOf="@id/email" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
好的,这是非常简单的布局,请注意这一行:
android:enabled="@{vm.emailInput.length()>6}"
在这个例子中,当我在 email
中输入超过 7 个字符后,它会自动启用第二个 EditText (phone
)。
对于这种情况,我不需要任何 onTextChangeListener
或 onPropertyChangeListener
。
问题:
How to move this line of condition vm.emailInput.length()>6
into
viewModel and remain everything the same without adding any listener?
我试过了:
试验 01:
视图模型:
fun updatePhoneEnabled(): Boolean {
return emailInput.value?.length ?: 0 > 6
}
xml:
<EditText
android:id="@+id/phone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:enabled="@{vm.updatePhoneEnabled()}"
android:text="@={vm.phoneInput}"
app:layout_constraintTop_toBottomOf="@id/email" />
这肯定是行不通的,因为它只会检查 init 而不会检查每次输入。
试验 02:
xml phone 我改成如下:
<EditText
android:id="@+id/phone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:enabled="@{vm.emailInput != null ? vm.updatePhoneEnabled() : false}"
android:text="@={vm.phoneInput}"
app:layout_constraintTop_toBottomOf="@id/email" />
这看起来很奇怪但可行的解决方案,因为 char typed 正在更新 emailInput ,因此,它触发了对每次键入的检查。但是,在实际情况中,我放在前面的空检查器
会不合适。参考下面的例子。
为什么我要这样做
因为我的队友是这样写一行代码的:(当然这是在实际项目中,我尽量用最简单的方式提问)
android:clickable="@{viewmodel.timer <= 0L && viewmodel.taskState == TaskState.NORMAL && (viewmodel.gameInfo.data != null ? viewmodel.parseStringToInt(viewmodel.gameInfo.data.fishAvailable) : 0) > 0}"
我不知道你们怎么想,但老实说,我不喜欢以这种方式附加所有条件检查。希望我很清楚。
请帮忙。谢谢。
您可以使用转换观察实时数据并对值执行简单操作
查看 official 文档以获取更多信息
您可以根据自己的情况执行以下操作
val isEnabled = Transformations.map(emailInput) {
it?.apply {
length > 6
}
}
并且在您的 XML 文件中,您可以像使用任何其他实时数据一样使用 isEnabled
<EditText
android:id="@+id/phone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:enabled="@{vm.isEnabled}"
android:text="@={vm.phoneInput}"
app:layout_constraintTop_toBottomOf="@id/email" />
感谢@rcs 的回答,而且我发现了这个 ,这对解决问题也很有用。最重要的是,假设我有 2 个依赖项需要监听,我可以在答案提供的实用程序之上进行操作,这个 CombinedLiveData
可以监听构造函数参数提供的任何源,即 emailInput
和 phoneInput
.
val isPhoneEnabled: CombinedLiveData<Boolean> = CombinedLiveData(emailInput, phoneInput) {
checkEnablity()
}
private fun checkEnablity(): Boolean {
return emailInput.value?.length ?: 0 > 6 &&
phoneInput.value?.length ?: 0 == 0
}
这是我可以在这里展示的最简单的示例:
片段(以备不时之需,但不重要!!)
class EgFragment:Fragment() {
private lateinit var viewModel: EgViewModel
private lateinit var binding: EgBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = EgBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(this, ViewModelFactory(requireContext(), this))
.get(EgViewModel::class.java)
binding.setLifecycleOwner(viewLifecycleOwner)
binding.vm = viewModel
}
}
查看模型
class EgViewModel: ViewModel() {
val emailInput:MutableLiveData<String> = MutableLiveData("")
val phoneInput:MutableLiveData<String> = MutableLiveData("")
val phoneEnabled:MutableLiveData<Boolean> = MutableLiveData(false)
}
Layout/XML
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="vm"
type="air.EgViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:id="@+id/email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@={vm.emailInput}"
app:layout_constraintBottom_toTopOf="@id/phone"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/phone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:enabled="@{vm.emailInput.length()>6}"
android:text="@={vm.phoneInput}"
app:layout_constraintTop_toBottomOf="@id/email" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
好的,这是非常简单的布局,请注意这一行:
android:enabled="@{vm.emailInput.length()>6}"
在这个例子中,当我在 email
中输入超过 7 个字符后,它会自动启用第二个 EditText (phone
)。
对于这种情况,我不需要任何 onTextChangeListener
或 onPropertyChangeListener
。
问题:
How to move this line of condition
vm.emailInput.length()>6
into viewModel and remain everything the same without adding any listener?
我试过了:
试验 01:
视图模型:
fun updatePhoneEnabled(): Boolean {
return emailInput.value?.length ?: 0 > 6
}
xml:
<EditText
android:id="@+id/phone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:enabled="@{vm.updatePhoneEnabled()}"
android:text="@={vm.phoneInput}"
app:layout_constraintTop_toBottomOf="@id/email" />
这肯定是行不通的,因为它只会检查 init 而不会检查每次输入。
试验 02:
xml phone 我改成如下:
<EditText
android:id="@+id/phone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:enabled="@{vm.emailInput != null ? vm.updatePhoneEnabled() : false}"
android:text="@={vm.phoneInput}"
app:layout_constraintTop_toBottomOf="@id/email" />
这看起来很奇怪但可行的解决方案,因为 char typed 正在更新 emailInput ,因此,它触发了对每次键入的检查。但是,在实际情况中,我放在前面的空检查器 会不合适。参考下面的例子。
为什么我要这样做
因为我的队友是这样写一行代码的:(当然这是在实际项目中,我尽量用最简单的方式提问)
android:clickable="@{viewmodel.timer <= 0L && viewmodel.taskState == TaskState.NORMAL && (viewmodel.gameInfo.data != null ? viewmodel.parseStringToInt(viewmodel.gameInfo.data.fishAvailable) : 0) > 0}"
我不知道你们怎么想,但老实说,我不喜欢以这种方式附加所有条件检查。希望我很清楚。 请帮忙。谢谢。
您可以使用转换观察实时数据并对值执行简单操作 查看 official 文档以获取更多信息
您可以根据自己的情况执行以下操作
val isEnabled = Transformations.map(emailInput) {
it?.apply {
length > 6
}
}
并且在您的 XML 文件中,您可以像使用任何其他实时数据一样使用 isEnabled
<EditText
android:id="@+id/phone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:enabled="@{vm.isEnabled}"
android:text="@={vm.phoneInput}"
app:layout_constraintTop_toBottomOf="@id/email" />
感谢@rcs 的回答,而且我发现了这个 CombinedLiveData
可以监听构造函数参数提供的任何源,即 emailInput
和 phoneInput
.
val isPhoneEnabled: CombinedLiveData<Boolean> = CombinedLiveData(emailInput, phoneInput) {
checkEnablity()
}
private fun checkEnablity(): Boolean {
return emailInput.value?.length ?: 0 > 6 &&
phoneInput.value?.length ?: 0 == 0
}