自定义视图上的数据绑定“无法在空引用对象上引用 .setTag”

Posted

技术标签:

【中文标题】自定义视图上的数据绑定“无法在空引用对象上引用 .setTag”【英文标题】:DataBinding on Custom Views "Can't reference .setTag on null reference object" 【发布时间】:2019-05-13 20:43:27 【问题描述】:

我遇到了一个问题,我已经为此苦苦挣扎了至少 2 周,我感到非常震惊,以至于这么多年后我有点忘记了数据绑定是如何工作的,以及如何为“自定义视图”正确设置它.我决定在一个非常简单的项目上进行检查,以将其与我当前的项目隔离开来。一个非常简单的 HelloWorld 应用程序,它基本上使用数据绑定将 Hello World 输出到屏幕。该项目包含以下文件:

MainActivity.kt

class MainActivity : AppCompatActivity() 

    override fun onCreate(savedInstanceState: Bundle?) 
        super.onCreate(savedInstanceState)
        val binding = ActivityMainBinding.inflate(LayoutInflater.from(this))
        setContentView(binding.root)

        binding.message = "Hello World!"
    

activity_main.xml

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

    <data>
        <variable name="message" type="String" />
    </data>

    <android.support.constraint.ConstraintLayout
            android:layout_
            android:layout_
            tools:context=".MainActivity">

        <com.neonapps.android.sample.databinding.CustomView
            android:layout_
            android:layout_


            <!-- Please take note I am data binding on my custom view -->
            app:message="@message"



            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"/>

    </android.support.constraint.ConstraintLayout>

</layout>

现在这是问题中最重要的部分。这是一个自定义视图CustomView。我想将一个特定的数据'String'绑定到这个视图中,这样它就可以在这个CustomView上输出“Hello World”:

class CustomView(context : Context, attrs : AttributeSet, defStyleAttrs : Int, defStylRes : Int) : RelativeLayout(context)

    constructor(context : Context, attrs : AttributeSet) : this(context, attrs, 0, 0)

    constructor(context : Context, attrs : AttributeSet, defStyleAttrs : Int) : this(context, attrs, defStyleAttrs, 0)

    private var myMessage : String? = null
        set(value)
            value.let
                field = it
                binding.message = field
            
        

    private val binding : LayoutCustomViewBinding = LayoutCustomViewBinding.inflate(LayoutInflater.from(context), this, true)

    init 
        binding.message?.let
            binding.message = it
        
    

    fun setMessage(message : String?)
        myMessage = message
    


@BindingAdapter(value = ["message"])
fun setMessage(view : TextView, message : String?)

    message?.let
        view.text = it
    


@BindingAdapter(value = ["message"])
fun setMessage(view : CustomView, message : String?)

    message?.let
        view.message = it
    

这就是问题所在。这个CustomView 膨胀了一个可以绑定的视图

<?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"
    xmlns:tools="http://schemas.android.com/tools">
    <data>
        <variable name="message" type="String" />
    </data>

    <RelativeLayout
        android:layout_
        android:layout_>

        <TextView
            android:id="@+id/textview"
            android:layout_
            android:layout_
            app:message="@message"
            tools:text="Hello World"/>

    </RelativeLayout>

</layout>

所以我基本上将一个字符串 绑定 到这个自定义视图(它由(假定)其布局中的许多视图组成)一旦我从外部设置它,就像上面的 activity_main.xml 一样。

activity_main.kt

<layout
    ...>

    <data>
        ...
    </data>

    <android.support.constraint.ConstraintLayout
            ...>

        <com.neonapps.android.sample.databinding.CustomView
            ...


            <!-- Please take note I am data binding on my custom view -->
            app:message="@message"



            .../>

    </android.support.constraint.ConstraintLayout>

</layout>

一旦我构建了整个项目,一切似乎都运行良好。我现在运行该应用程序并收到以下错误:

Attempt to invoke virtual method 'void ******.databinding.CustomView.setTag(java.lang.Object)' on a null object reference
        at com.neonapps.android.sample.databinding.databinding.ActivityMainBindingImpl.<init>(ActivityMainBindingImpl.java:37)
        at com.neonapps.android.sample.databinding.databinding.ActivityMainBindingImpl.<init>(ActivityMainBindingImpl.java:29)
        at com.neonapps.android.sample.databinding.DataBinderMapperImpl.getDataBinder(DataBinderMapperImpl.java:44)
        at android.databinding.MergedDataBinderMapper.getDataBinder(MergedDataBinderMapper.java:74)
        at android.databinding.DataBindingUtil.bind(DataBindingUtil.java:199)
        at android.databinding.DataBindingUtil.inflate(DataBindingUtil.java:130)
        at com.neonapps.android.sample.databinding.databinding.ActivityMainBinding.inflate(ActivityMainBinding.java:49)
        at com.neonapps.android.sample.databinding.databinding.ActivityMainBinding.inflate(ActivityMainBinding.java:43)
        at *****.MainActivity.onCreate(MainActivity.kt:12)
        at android.app.Activity.performCreate(Activity.java:6904)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1136)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3266)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3415) 
        at android.app.ActivityThread.access$1100(ActivityThread.java:229) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1821) 
        at android.os.Handler.dispatchMessage(Handler.java:102) 
        at android.os.Looper.loop(Looper.java:148) 
        at android.app.ActivityThread.main(ActivityThread.java:7406) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120)

 我的应用崩溃了,突然间,我陷入了恐慌。我只是不再了解 DataBinding 的基础知识。当我只是对视图进行数据绑定但我对自己的自定义视图上的所有数据绑定都没有运气时,它工作得很好。让我发疯的一件事是这会在自动生成的代码上崩溃。我完全不知道它是如何生成一个引用它生成的 null 而没有为其分配引用的代码。我投降了,我很想念一些东西。

我确实错过了一些东西,我似乎无法发现它。我一直在交叉引用 DataBinding 库文档,但对我来说没有任何用处。

我试过这段代码

Android Studio: 3.4 Canary 7
Kotlin: 1.3.11

Android Studio: 3.2.1
Kotlin: 1.2.71

首先我认为这可能是 Kotlin/Build config/gradle 相关的问题,直到我在稳定的环境中构建这个项目并且无论如何它们的行为都是一样的。

这是我的诅咒。任何减轻我痛苦的帮助将不胜感激!

【问题讨论】:

我知道这听起来可能是个愚蠢的答案,但请复制您的 custom_view.xml 的所有代码,然后将其删除。 Rebuild。现在,创建具有相同名称的新 xml 文件,粘贴代码并尝试再次运行。这是我昨天在 Fragments xml 中遇到的确切问题。此外,如果您支持多个方向或密度,请确保所有布局文件夹中的所有布局文件都具有数据绑定 &lt;layout&gt; 标签。 先试试这个。 setContentView() 喜欢activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main); @AadityaBrahmbhatt 我做了,但行为仍然相同。 【参考方案1】:

这是我的诅咒。任何减轻我痛苦的帮助将不胜感激!

我会试试的。

删除您编写的两个 BindingAdapter 并将您的类重写为:

class CustomView : RelativeLayout 

    constructor(context: Context, attrs: AttributeSet) : this(context, attrs, 0, 0)
    constructor(context: Context, attrs: AttributeSet, defStyleAttrs: Int) :
            this(context, attrs, defStyleAttrs, 0)
    constructor(context: Context, attrs: AttributeSet, defStyleAttrs: Int, defStylRes: Int) :
            super(context, attrs, defStyleAttrs, defStylRes)

    private val binding: LayoutCustomViewBinding = LayoutCustomViewBinding.inflate(LayoutInflater.from(context), this, true)

    var message: String? = null
        set(value) 
            binding.message = value
        

将 CustomView 布局中的 app:message="@message" 部分替换为 android:text="@message"

<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>
        <variable name="message" type="String" />
    </data>

    <RelativeLayout
        android:layout_
        android:layout_>

        <TextView
            android:id="@+id/textview"
            android:layout_
            android:layout_
            android:text="@message"
            tools:text="Hello World"/>

    </RelativeLayout>

</layout>

解释:

您将不需要您编写的 BindingAdapters,因为数据绑定库会在编译期间自动检测从 Kotlin 类中的消息字段生成的 setMessage() 方法。 TextView 也是如此,数据绑定库会检测到有一个 setText() 方法并将其用于android:text="@message"

【讨论】:

谢谢!!!但我想挑战一下自己,因为这个特定的项目是为了学习目的。 'ápp:message' 是我想分配给我的自定义视图的实际对象,但这样做只会导致这种奇怪的崩溃,这并不能说明太多。 我明白了,我认为 setTag-crash 可能是由 BindingAdapters 引起的,【参考方案2】:

这是一个迟到的答案,但可能会帮助其他面临类似问题的人。我在开发自定义视图时遇到了同样的问题,该自定义视图正在为手机/平板电脑膨胀自己的布局并将它们添加到视图层次结构中。对我来说,问题在于使用生成的 Binding 对象来扩展此自定义视图中的布局。这一切都与ViewDataBinding.java 的函数有关,该函数递归地遍历视图层次结构并获取每个子对象的绑定对象。

    private static void mapBindings(DataBindingComponent bindingComponent, View view,
            Object[] bindings, IncludedLayouts includes, SparseIntArray viewsWithIds,
            boolean isRoot) 
        final int indexInIncludes;
        final ViewDataBinding existingBinding = getBinding(view);
        if (existingBinding != null) 
            return;
        
        ...
    

return 语句导致映射过早退出递归函数,并且我的主绑定类的视图为空,因此崩溃。

这是我的自定义视图供参考:

class CustomContainerView : LinearLayout 

    constructor(context: Context) : super(context)

    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs,
            defStyleAttr)

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int,
                defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes)

    var binding: CustomContainerViewBinding? = null
    val customViewRoot: View

    init 
        orientation = VERTICAL
        customViewRoot= LayoutInflater.from(context)
                .inflate(R.layout.custom_container_view, this, false)
        addView(customViewRoot)
        if (!isInEditMode) 
            binding = CustomContainerViewBinding.bind(customViewRoot)
        
    

    private fun relocate(child: View) 
        removeView(child)
        val params = child.layoutParams ?: ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.WRAP_CONTENT)
        findViewById<ViewGroup>(R.id.contentContainer)
                .addView(child, params)
    

    override fun onViewAdded(child: View?) 
        child?.let 
            if (it != customViewRoot) 
                relocate(it)
            
        
    

所以对我来说罪魁祸首是这条线:

 if (!isInEditMode) 
   binding = CustomContainerViewBinding.bind(customViewRoot)
 

为了解决这个问题,我只是从 init 块中删除了上面的代码并将其移至延迟加载 getter。

class CustomContainerView : LinearLayout 

    constructor(context: Context) : super(context)

    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs,
            defStyleAttr)

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int,
                defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes)

    private var customContainerBinding: CustomContainerViewBinding? = null
    val customViewRoot: View

    init 
        orientation = VERTICAL
        customViewRoot= LayoutInflater.from(context)
                .inflate(R.layout.custom_container_view, this, false)
        addView(customViewRoot)
    

    private fun relocate(child: View) 
        removeView(child)
        val params = child.layoutParams ?: ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.WRAP_CONTENT)
        findViewById<ViewGroup>(R.id.contentContainer)
                .addView(child, params)
    

    override fun onViewAdded(child: View?) 
        child?.let 
            if (it != customViewRoot) 
                relocate(it)
            
        
    

   fun getCustomContainerBinding(): CustomContainerViewBinding? 
        if (customContainerBinding == null) 
            customContainerBinding = CustomContainerViewBinding.bind(customViewRoot)
        
        return customContainerBinding
    

对于 OP 的问题可能会发生同样的事情,因为看起来您正在依赖 Binding 类来扩充您的布局。

 private val binding : LayoutCustomViewBinding = LayoutCustomViewBinding.inflate(LayoutInflater.from(context), this, true)

所以要修复它,只需按照常规方法膨胀LayoutInflater.inflate() 来创建您的视图,然后创建一个可以延迟绑定到 Binding 对象的 getter 函数。

class CustomView(context : Context, attrs : AttributeSet, defStyleAttrs : Int, defStylRes : Int) : RelativeLayout(context)

    constructor(context : Context, attrs : AttributeSet) : this(context, attrs, 0, 0)

    constructor(context : Context, attrs : AttributeSet, defStyleAttrs : Int) : this(context, attrs, defStyleAttrs, 0)

    private var myMessage : String? = null
        set(value)
            value.let
                field = it
                binding?.message = field
            
        

    private val rootView : View

    private val binding : LayoutCustomViewBinding?
        get() = LayoutCustomViewBinding.bind(rootView)

    init 
        rootView = LayoutInflater.from(context)
                .inflate(R.layout.layout_custom_view, this, true)
    

    fun setMessage(message : String?)
        myMessage = message
    


@BindingAdapter(value = ["message"])
fun setMessage(view : TextView, message : String?)

    message?.let
        view.text = it
    


@BindingAdapter(value = ["message"])
fun setMessage(view : CustomView, message : String?)

    message?.let
        view.message = it
    

【讨论】:

【参考方案3】:

必须有所有的构造函数。 必须调用两个超级构造函数。 像这样:

public CustomView(Context context) 
        super(context);
    

    public CustomView(Context context, @Nullable AttributeSet attrs) 
        this(context,attrs, 0);

    

    public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) 
        this(context, attrs, defStyleAttr, 0);

    

    public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) 
        super(context, attrs, defStyleAttr, defStyleRes);
    

【讨论】:

以上是关于自定义视图上的数据绑定“无法在空引用对象上引用 .setTag”的主要内容,如果未能解决你的问题,请参考以下文章

RxSwift 不将数据绑定到自定义集合视图单元格

android数据绑定与自定义视图

Android 自定义视图中的数据绑定

将自定义视图绑定到点

如何解决:使用自定义视图实现双向数据绑定时“找不到属性'android:text'的getter”?

将数据绑定 DataGridView 上的单元格类型更改为自定义 DataGridViewCell 类型