android - 使用数据绑定(bind)的问题 : val vs var and the use invalidateAll()

标签 android kotlin android-databinding

这实际上是2个问题。

  • 我注意到如果在 Person 中,数据绑定(bind)不起作用。我设置的数据类name参数为 val而不是 var .代码将中断并出现以下错误:

  • error: cannot find symbol
    import com.example.android.aboutme.databinding.ActivityMainBindingImpl;
                                                  ^
      symbol:   class ActivityMainBindingImpl
      location: package com.example.android.aboutme.databinding
    


    为什么会这样?
  • 为什么我需要调用invalidateAll()doneClick() ?文档说它“使所有绑定(bind)表达式无效并请求新的重新绑定(bind)以刷新 UI”。数据绑定(bind)的目的不就是以这样一种方式连接数据和 View ,即对数据的更新会立即更新 View 吗?

  • 主要 Activity :
    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)
        }
    }
    

    人:
    class Person(var name: String, var nickname: String? = null)
    

    Activity 主.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>
    

    最佳答案

    问题一:

    I noticed that databinding doesn't work if in the Person data class I set the name parameter to be val instead of var.


    Why does it happen?


    因为您使用的是 two-way databinding .
    在您的布局中,您有以下内容:
    <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" />
    
    @=android:text="@={person.name}" ,具体来说,告诉数据绑定(bind)“我想将 TextView 的文本设置为该人的 name 值,并且我想在 name 文本更改时更新该人的 TextView”。
    当您使用 @=数据绑定(bind)将为您正在分配的属性寻找一个 setter 。在这种情况下,它正在寻找 name 的 setter。 Person 上的属性类(class)。在 Kotlin 中,这意味着拥有一个名为 name 的属性。那是一个 var .
    如果您不打算更新此人的 name TextView 时的属性更改(我假设您没有,您通常会使用 EditText ),然后将该行更改为 @ (android:text="@{person.name}")。然后你可以制作name一个 val因为你只是从它读取数据绑定(bind)。

    问题二:

    Why do I need to call invalidateAll() in doneClick()?


    你其实不...

    The documentation says that it "Invalidates all binding expressions and requests a new rebind to refresh UI". Isn't the purpose of databinding to connect data and views in such a way that an update to the data immediately updates the views?


    是的,但是:数据绑定(bind)不是魔术。如果 UI 要更新,则必须告诉它这样做,并且更改数据不会神奇地告诉数据绑定(bind)它必须更新。有些东西必须告诉数据绑定(bind) a) 是时候更新了,b) 它需要更新什么。
    所以你现在拥有invalidateAll()是霰弹枪的方法。您更新了 nickname字段,然后您对数据绑定(bind)大喊“嘿,更新所有内容!”,因此它会根据 Person 的当前状态重新绑定(bind)所有 View 其中当然包括“昵称”,以便更新 View 。
    您要做的是仅更新绑定(bind)到 nickname 的字段。因为这是改变的一件事,最好是在 nickname 时自动完成。变化。为此,您需要观察 nickname 的状态。场并对其变化使用react。
    您可以通过以下几种方式做到这一点:
  • Use LiveData

  • 在这种方法中,您要绑定(bind)的模型字段为 LiveData对象( val nickname = MutableLiveData<String>() ),然后添加 LifeCycleOwner到绑定(bind),以便它可以观察 LiveData对象。
    数据绑定(bind)设置为使用 LiveData所以你的xml不需要改变。但是现在属性是可观察到的,当您在 Person 上更新名称时( person?.nickname?.value = "New Nickname" ) 数据绑定(bind)将被自动通知并更新相关 View 的状态。
    您不必调用 invalidateAll() .
  • Use Observable Fields

  • 这在概念上与 #1 相同,但在 LiveData 之前出现。被介绍了。现在你可以考虑弃用它并使用 LiveData方法,但为了完整起见,我会提到它。
    再次,而不是拥有 String 类型的常规属性。您将该属性包装在一个可观察的数据结构( val nickname = ObservableString() )中,当值发生更改时,它将通知数据绑定(bind)。同样,数据绑定(bind)已设置为与此一起使用,因此您不必更改 XML。
  • 使用Observable Objects

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

    在这条线上分手的想法:
    person?.nickname = etNickname.text.toString()
    
    如果您正确设置了数据绑定(bind),则不需要这样做。 :)
    如果您设置 etNickname使用双向绑定(bind)并制作 person.nickname可以正确观察到,person.nickname属性将自动更新为 etNickname 中的文本值当它改变时!
    这就是数据绑定(bind)的美妙之处。
    希望有帮助!

    关于android - 使用数据绑定(bind)的问题 : val vs var and the use invalidateAll(),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57011839/

    相关文章:

    Android sqlite联合表,查找行属于哪个表

    Android Kotlin Kotlinx 模块级别 View 绑定(bind)

    android - 从资源字符串数组创建 ArrayList<String>

    android - 使用数据绑定(bind)时导航栏出现 2 次

    android - Gradle 找不到 com.android.databinding :dataBinder:1. 0-rc0

    Android如何处理mvvm架构中的recyclerview点击

    Android:来自方向/旋转角度的轴向量?

    java - 没有 UI 的 Android 后台服务没有应答

    android - 文本末尾的额外换行符

    android - 如何在 kotlin 中连接/附加 2 个字符串?