【问题标题】:problems using databinding: val vs var and the use invalidateAll()使用数据绑定的问题:val vs var 和使用 invalidateAll()
【发布时间】:2019-11-22 12:13:07
【问题描述】:

这实际上是 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

为什么会这样?

  1. 为什么我需要在doneClick() 中调用invalidateAll()?文档说它“使所有绑定表达式无效并请求新的重新绑定以刷新 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)
    }
}

人:

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

activity_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>

【问题讨论】:

  • 1) 您没有在 Person 构造函数中传递任何昵称值。 2)在事件发生后使所有明显用于更新视图的内容失效,但主要与网络调用或任何数据库相关操作后的更新有关,因此可以获得最新的更新数据以供查看
  • @PJain 1. 这不是问题所在,我可以让昵称具有默认值“foo”,但它仍然会在namenickname 崩溃为val 而不是@987654335 @.
  • @qeh63 您发布的错误消息不是真正的错误消息。这只是实际问题的副作用。遍历整个 logcat 以找到真正的错误消息。如果您看不到问题,则可能显示的错误消息不足,然后 -> stackoverflow.com/a/35707088/1993204

标签: android kotlin android-databinding


【解决方案1】:

问题 1:

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

为什么会这样?

因为您使用的是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}" 中的 @= 特别是告诉数据绑定“我想将TextView 的文本设置为人的name我想更新人的nameTextView 文字变化”。

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

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


问题 2:

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

其实你没有……

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

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

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

想要做的是更新绑定到nickname的字段,因为这是改变的一件事,最好是您想要做当nickname 改变时它会自动发生。为此,您需要观察nickname 字段的状态并它的变化做出反应。

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

  1. Use LiveData

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

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

您不必致电invalidateAll()

  1. Use Observable Fields

这在概念上与 #1 相同,但这是在引入 LiveData 之前出现的。现在你可以考虑使用 LiveData 方法,但为了完整起见,我会提到它。

再次重申,您可以将该属性包装在 observable 数据结构 (val nickname = ObservableString()) 中,而不是使用类型为 String 的常规属性,该数据结构将在值发生更改时通知数据绑定。同样,数据绑定已设置为与此一起使用,因此您不必更改 XML。

  1. 使用Observable Objects

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


关于这一行的分手想法:

person?.nickname = etNickname.text.toString()

如果您正确设置了数据绑定,这应该是不必要的。 :) 如果您将etNickname 设置为使用双向绑定并使person.nickname 正确可观察,person.nickname 属性将在其更改时自动更新为etNickname 中的文本值! p>

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

希望有帮助!

【讨论】:

  • 很好,很好的解释
  • 这可能是一篇中等文章!很好的解释。谢谢
【解决方案2】:
  1. Val = 不可变 Var = 可变的

完整答案 Val and Var in Kotlin

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

【讨论】:

    猜你喜欢
    • 2011-05-25
    • 2016-11-08
    • 2013-02-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-09
    • 2013-12-27
    • 1970-01-01
    相关资源
    最近更新 更多