使用数据绑定的问题:val 与 var 以及使用 invalidateAll()

qeh*_*h63 7 android kotlin android-databinding

这实际上是2个问题。

  1. 我注意到如果在Person数据类中我将name参数设置为val而不是var. 代码将因以下错误而中断:
error: cannot find symbol
import com.example.android.aboutme.databinding.ActivityMainBindingImpl;
                                              ^
  symbol:   class ActivityMainBindingImpl
  location: package com.example.android.aboutme.databinding
Run Code Online (Sandbox Code Playgroud)

为什么会发生?

  1. 为什么我需要调用invalidateAll()doneClick()?文档说它“使所有绑定表达式无效并请求新的重新绑定以刷新 UI”。数据绑定的目的不是以更新数据立即更新视图的方式连接数据和视图吗?

主要活动:

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding
    val person = Person("Bob")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        binding.person = person

        binding.apply {
            btnDone.setOnClickListener { doneClick(it) }
        }
    }

    private fun doneClick(view: View) {
        binding.apply {
            person?.nickname = etNickname.text.toString()
            invalidateAll()
            etNickname.visibility = View.GONE
            tvNickname.visibility = View.VISIBLE
            btnDone.visibility = View.GONE
        }

        hideKeybord(view)
    }

    private fun hideKeybord(view: View) {
        val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
        imm.hideSoftInputFromWindow(view.windowToken, 0)
    }
}
Run Code Online (Sandbox Code Playgroud)

人:

class Person(var name: String, var nickname: String? = null)
Run Code Online (Sandbox Code Playgroud)

活动_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>

        <variable
            name="person"
            type="com.example.android.aboutme.Person" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:paddingStart="@dimen/padding"
        android:paddingEnd="@dimen/padding">

        <TextView
            android:id="@+id/tv_name"
            style="@style/NameStyle"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@={person.name}"
            android:textAlignment="center" />

        <EditText
            android:id="@+id/et_nickname"
            style="@style/NameStyle"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:ems="10"
            android:hint="@string/what_is_your_nickname"
            android:inputType="textPersonName"
            android:textAlignment="center" />

        <Button
            android:id="@+id/btn_done"
            style="@style/Widget.AppCompat.Button.Colored"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="@dimen/layout_margin"
            android:fontFamily="@font/roboto"
            android:text="@string/done" />

        <TextView
            android:id="@+id/tv_nickname"
            style="@style/NameStyle"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@={person.nickname}"
            android:textAlignment="center"
            android:visibility="gone" />

        <ImageView
            android:id="@+id/star_image"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/layout_margin"
            android:contentDescription="@string/yellow_star"
            app:srcCompat="@android:drawable/btn_star_big_on" />

        <ScrollView
            android:id="@+id/bio_scroll"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginTop="@dimen/layout_margin">

            <TextView
                android:id="@+id/bio_text"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:lineSpacingMultiplier="@dimen/line_spacing_multiplier"
                android:text="@string/bio"
                android:textAppearance="@style/NameStyle" />

        </ScrollView>
    </LinearLayout>
</layout>
Run Code Online (Sandbox Code Playgroud)

dom*_*der 16

问题一:

我注意到,如果在 Person 数据类中将 name 参数设置为 val 而不是 var,则数据绑定不起作用。

为什么会发生这种情况?

因为您正在使用双向数据绑定

在你的布局中有这样的:

<TextView
    android:id="@+id/tv_name"
    style="@style/NameStyle"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@={person.name}"
    android:textAlignment="center" />
Run Code Online (Sandbox Code Playgroud)

@=具体来说, in告诉android:text="@={person.name}"数据绑定“我想将 的TextView文本设置为人员的name,并且我想nameTextView文本更改时更新人员的文本”。

当您使用@=数据绑定时,将为您分配的属性查找设置器。在本例中,它正在寻找类name上属性的设置器Person。在 Kotlin 中,这意味着拥有一个名为namea 的属性var

如果您不打算在更改name时更新人员的属性TextView(我假设您不这样做,您通常会使用EditText),然后将该行更改为@( android:text="@{person.name}")。然后您可以创建name一个val,因为您只是从中读取数据以进行数据绑定。


问题2:

为什么需要在doneClick()中调用invalidateAll()?

其实你不...

文档说它“使所有绑定表达式无效并请求新的重新绑定以刷新 UI”。数据绑定的目的不就是以数据更新立即更新视图的方式连接数据和视图吗?

是的,但是:数据绑定并不神奇。如果 UI 要更新,则必须告诉它这样做,并且更改数据并不会神奇地告诉数据绑定它必须更新。必须有某种东西告诉数据绑定 a) 是时候更新了,b) 需要更新什么。

所以你现在所采用的invalidateAll()是霰弹枪方法。您更新了一个nickname字段,然后对数据绑定大喊“嘿,更新所有内容!”,因此它会根据当前状态重新绑定所有Person视图,其中当然包括“昵称”,以便视图得到更新。

想要做的是更新绑定的字段nickname,因为这是发生更改的一件事,并且最好您希望在nickname更改时自动执行此操作。为此,您需要观察场的状态nickname并对其变化做出反应。

您可以通过以下几种方式来做到这一点:

  1. 使用实时数据

在这种方法中,您希望将模型的字段绑定为LiveData对象 ( val nickname = MutableLiveData<String>()),然后将 a 添加LifeCycleOwner到绑定中,以便它可以观察LiveData对象。

数据绑定已设置为使用LiveData,因此您的 xml 不需要更改。但现在属性是可观察的,当您更新Person( person?.nickname?.value = "New Nickname") 上的名称时,数据绑定将自动收到通知,并将更新关联视图的状态。

您不必打电话invalidateAll()

  1. 使用可观察字段

这在概念上与#1 相同,但这是在之前LiveData引入的。现在,您可以认为此方法已弃用并使用该LiveData方法,但为了完整起见,我将提及它。

同样,您不是使用常规类型的属性,而是String将该属性包装在可观察的数据结构 ( val nickname = ObservableString()) 中,该结构将在值发生更改时通知数据绑定。同样,数据绑定已设置为可以处理此问题,因此您无需更改 XML。

  1. 使用可观察对象

使用此选项,您可以使您的Person类(或最好是ViewModel)扩展Observable并管理在字段更改时通知您自己的数据绑定。如果您在更新某些字段时必须执行特殊逻辑,并且简单的“设置和通知”还不够,那么您会走这条路线。此选项要复杂得多,我将其作为练习留给读者阅读文档以了解此选项的工作原理。对于绝大多数情况,您应该能够使用选项 #1 来完成您需要的操作。


这条线的分手想法:

person?.nickname = etNickname.text.toString()
Run Code Online (Sandbox Code Playgroud)

如果您正确设置了数据绑定,则不需要这样做。:) 如果您设置etNickname为使用双向绑定并使其person.nickname可正确观察,则该属性将在更改时person.nickname自动更新为文本值etNickname

这就是数据绑定的美妙之处。

希望有帮助!


Ron*_*uay 0

  1. Val = 不可变 Var = 可变

Kotlin 中的完整答案 Val 和 Var

  1. 这是因为属性没有内置机制来通知 UI 它们已发生更改。所以你必须手动调用它。此问题的解决方案是使用 LiveData 或 MutableLiveData。