为什么 data bing 不使用 LiveData 或 Observable 字段?

Why doesn't data bing use LiveData or Observable fields?

在我看来,单向或双向数据 bing 使用 LiveDataObservable fields

以下代码来自项目https://github.com/enpassio/Databinding

控件 android:id="@+id/toyNameEditText" 的属性 android:text="@={viewModel.toyBeingModified.toyName}" 与双向数据 bing 绑定到 viewModel.toyBeingModified.toyName

我很奇怪为什么viewModel.toyBeingModified既不是LiveData也不是Observable fields,你能告诉我吗?

fragment_add_toy.xml

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data class="AddToyBinding">

        <variable
            name="viewModel"
            type="com.enpassion.twowaydatabindingkotlin.viewmodel.AddToyViewModel" />

        <import type="com.enpassion.twowaydatabindingkotlin.utils.BindingUtils"/>
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="@dimen/margin_standard">

        <androidx.cardview.widget.CardView
            android:id="@+id/cardEditText"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginBottom="@dimen/margin_standard"
            app:cardBackgroundColor="@color/skin_rose"
            app:cardCornerRadius="@dimen/card_corner_radius"
            app:cardElevation="@dimen/card_elevation"
            app:contentPadding="@dimen/padding_standard"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent">

            <androidx.constraintlayout.widget.ConstraintLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent">

                ...

                <com.google.android.material.textfield.TextInputLayout
                    android:id="@+id/toyNameLayout"
                    style="@style/Widget.Enpassio.TextInputLayout"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:hint="@string/toy_name"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintHorizontal_bias="0.0"
                    app:layout_constraintStart_toStartOf="@+id/guidelineET"
                    app:layout_constraintTop_toTopOf="parent"
                    tools:layout_editor_absoluteY="418dp">

                    <com.google.android.material.textfield.TextInputEditText
                        android:id="@+id/toyNameEditText"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:inputType="textCapWords"
                        android:text="@={viewModel.toyBeingModified.toyName}"/>

                </com.google.android.material.textfield.TextInputLayout>
            </androidx.constraintlayout.widget.ConstraintLayout>
        </androidx.cardview.widget.CardView>
        ...
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

AddToyViewModel.kt

class AddToyViewModel(private val mRepo: ToyRepository, private val chosenToy: ToyEntry?) : ViewModel() {

    val toyBeingModified: ToyEntry

    private var mIsEdit: Boolean = false

    init {
        if (chosenToy != null) {
            //This is edit case
            toyBeingModified = chosenToy.copy()
            mIsEdit = true           
        } else {
            /*This is for adding a new toy. We initialize a ToyEntry with default or null values
            This is because two-way databinding in the AddToyFragment is designed to
            register changes automatically, but it will need a toy object to register those changes.*/
            toyBeingModified = emptyToy
            mIsEdit = false          
        }
    }

    private fun insertToy(toy: ToyEntry) {
        mRepo.insertToy(toy)
    }
    ...
}

ToyEntry.kt

data class ToyEntry(
    var toyName: String,
    var categories: Map<String, Boolean>,
    var gender: Gender = Gender.UNISEX,
    var procurementType: ProcurementType? = null,
    @PrimaryKey(autoGenerate = true) val toyId: Int = 0
): Parcelable{

    /*This function is needed for a healthy comparison of two items,
    particularly for detecting changes in the contents of the map.
    Native copy method of the data class assign a map with same reference
    to the copied item, so equals() method cannot detect changes in the content.*/
    fun copy() : ToyEntry{
        val newCategories = mutableMapOf<String, Boolean>()
        newCategories.putAll(categories)
        return ToyEntry(toyName, newCategories, gender, procurementType, toyId)
    }
}

我建议首先从单向数据绑定开始,一旦可行,就将其扩展到双向数据绑定。你现在做错的是:

android:text="@={viewModel.toyBeingModified.toyName}"

这行代码意味着您将一个 ToyEntry 对象传递给 TextView 的 setText() 方法。这意味着 TextView 需要一个带有签名的方法:setText(entry: ToyEntry).

当然,这种方法(目前)还不存在。所以要使这个数据绑定工作,你必须通过创建一个 BindingAdapter:

来自己定义这个方法
@BindingAdapter("toyEntry")
fun setToyEntry(textView: TextView, toyEntry: ToyEntry) {
   // in here you define what to do with the textView. For example:
   textView.text = toyEntry.toyName
}
  • 您可以在任何文件中创建此 BindingAdapter,而无需将其放入 class。
  • 你可以给这个方法起任何你想要的名字
  • 此方法的第一个参数是 xml 中要将 toyEntry 绑定到
  • 的视图类型
  • 此方法的第二个参数 os 您通过 @{...}
  • 在 xml 中设置的对象

现在,当您像这样编写单向数据绑定时:binding:toyEntry="@{viewModel.toyBeingModified.toyName}"

  • binding 命名空间可以由 AndroidStudio 自动创建。您可以随意命名它(但不能 android,因为它已经定义)
  • toyEntry 是将 xml 这一行连接到上一步中的 BindingAdapter 的原因(它对应于您在注释 @BindingAdapter(...)[=50 中设置的相同字符串=]

现在,生成的代码知道您的绑定适配器并在计算此数据绑定时调用其方法 setToyEntry。您也可以删除行 android:text="@={viewModel.toyBeingModified.toyName}",因为它不再被使用。

从那里开始设置双向数据绑定。在这里,您还必须按照此处的说明创建 @InverseBindingAdapterhttps://developer.android.com/reference/android/databinding/InverseBindingAdapter

更多评论:根据您的 gradle 版本,您必须启用数据绑定并确保所有依赖项和 gradle 插件设置。 更多相关信息:https://developer.android.com/jetpack/androidx/releases/databinding?hl=en

事实上,当我们需要在它们发生变化时立即执行某些操作时,我们会使用 LiveData 或 Observable 字段,搜索栏就是一个很好的例子。但在这种情况下,我们不关心用户何时更改所选玩具的属性(我没有看到 UI 但我假设有一个保存按钮或类似的东西)。换句话说,当用户输入 bboboa 和最后的 boat.

时,我们不想做任何事情

我们只需要在视图模型设置为绑定时设置一次数据,让用户将其更改为任何内容,当我们想要进行保存过程时,我们希望我们的字段是用户输入的内容。 此外,如果您在绑定中使用 LiveData(只要设置了 lifecycleOwner),您就会向 LiveData 添加一个观察者,这可能是一些极客关注的问题。

TL;DR

当我们想要观察它时,我们使用 LiveData(您提供的示例中不需要)。这是一个选项而不是必须的。数据绑定可以 set/get 几乎所有内容的数据。