为什么 data bing 不使用 LiveData 或 Observable 字段?
Why doesn't data bing use LiveData or Observable fields?
在我看来,单向或双向数据 bing 使用 LiveData
或 Observable 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}"
,因为它不再被使用。
从那里开始设置双向数据绑定。在这里,您还必须按照此处的说明创建 @InverseBindingAdapter
:https://developer.android.com/reference/android/databinding/InverseBindingAdapter
更多评论:根据您的 gradle 版本,您必须启用数据绑定并确保所有依赖项和 gradle 插件设置。
更多相关信息:https://developer.android.com/jetpack/androidx/releases/databinding?hl=en
事实上,当我们需要在它们发生变化时立即执行某些操作时,我们会使用 LiveData 或 Observable 字段,搜索栏就是一个很好的例子。但在这种情况下,我们不关心用户何时更改所选玩具的属性(我没有看到 UI 但我假设有一个保存按钮或类似的东西)。换句话说,当用户输入 b
、bo
、boa
和最后的 boat
.
时,我们不想做任何事情
我们只需要在视图模型设置为绑定时设置一次数据,让用户将其更改为任何内容,当我们想要进行保存过程时,我们希望我们的字段是用户输入的内容。
此外,如果您在绑定中使用 LiveData(只要设置了 lifecycleOwner),您就会向 LiveData 添加一个观察者,这可能是一些极客关注的问题。
TL;DR
当我们想要观察它时,我们使用 LiveData(您提供的示例中不需要)。这是一个选项而不是必须的。数据绑定可以 set/get 几乎所有内容的数据。
在我看来,单向或双向数据 bing 使用 LiveData
或 Observable 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}"
,因为它不再被使用。
从那里开始设置双向数据绑定。在这里,您还必须按照此处的说明创建 @InverseBindingAdapter
:https://developer.android.com/reference/android/databinding/InverseBindingAdapter
更多评论:根据您的 gradle 版本,您必须启用数据绑定并确保所有依赖项和 gradle 插件设置。 更多相关信息:https://developer.android.com/jetpack/androidx/releases/databinding?hl=en
事实上,当我们需要在它们发生变化时立即执行某些操作时,我们会使用 LiveData 或 Observable 字段,搜索栏就是一个很好的例子。但在这种情况下,我们不关心用户何时更改所选玩具的属性(我没有看到 UI 但我假设有一个保存按钮或类似的东西)。换句话说,当用户输入 b
、bo
、boa
和最后的 boat
.
我们只需要在视图模型设置为绑定时设置一次数据,让用户将其更改为任何内容,当我们想要进行保存过程时,我们希望我们的字段是用户输入的内容。 此外,如果您在绑定中使用 LiveData(只要设置了 lifecycleOwner),您就会向 LiveData 添加一个观察者,这可能是一些极客关注的问题。
TL;DR
当我们想要观察它时,我们使用 LiveData(您提供的示例中不需要)。这是一个选项而不是必须的。数据绑定可以 set/get 几乎所有内容的数据。