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)。

对于这种情况,我不需要任何 onTextChangeListeneronPropertyChangeListener

问题:

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 &lt;= 0L &amp;&amp; viewmodel.taskState == TaskState.NORMAL &amp;&amp; (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 可以监听构造函数参数提供的任何源,即 emailInputphoneInput.

    val isPhoneEnabled: CombinedLiveData<Boolean> = CombinedLiveData(emailInput, phoneInput) {
        checkEnablity()
    }

    private fun checkEnablity(): Boolean {
        return emailInput.value?.length ?: 0 > 6 &&
                phoneInput.value?.length ?: 0 == 0
    }