代码之家  ›  专栏  ›  技术社区  ›  Neon Warge user547852

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

  •  0
  • Neon Warge user547852  · 技术社区  · 6 年前

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

    主要活动。千吨

    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!"
        }
    }
    

    <?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_width="match_parent"
                android:layout_height="match_parent"
                tools:context=".MainActivity">
    
            <com.neonapps.android.sample.databinding.CustomView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
    
    
                <!-- 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

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

    这是陷阱。这 自定义视图 :

    <?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_width="match_parent"
            android:layout_height="match_parent">
    
            <TextView
                android:id="@+id/textview"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                app:message="@{message}"
                tools:text="Hello World"/>
    
        </RelativeLayout>
    
    </layout>
    

    所以我基本上 结合 activity_main.xml 在上面

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

    我的应用程序崩溃了,突然,我陷入了恐慌。我只是不知道数据绑定的基础知识了。当我只是在视图上进行数据绑定时,它工作得很好,但在我自己的自定义视图上进行数据绑定时,我一点运气都没有。让我抓狂的一件事是,这会导致自动生成的代码崩溃。我完全不知道它是如何在没有为其分配引用的情况下,在生成的null上生成引用代码的。我投降了,我错过了一些东西。

    我肯定错过了什么,我似乎无法发现它。我一直在交叉引用数据绑定库文档,但没有任何有用的内容。

    Android Studio: 3.4 Canary 7
    Kotlin: 1.3.11
    
    Android Studio: 3.2.1
    Kotlin: 1.2.71
    

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

    1 回复  |  直到 6 年前
        1
  •  1
  •   janosch    6 年前

    这是我的诅咒。如果能帮我减轻痛苦,我将不胜感激!

    我试试看。

    删除您编写的两个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
            }
    }
    

    更换 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_width="match_parent"
            android:layout_height="match_parent">
    
            <TextView
                android:id="@+id/textview"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@{message}"
                tools:text="Hello World"/>
    
        </RelativeLayout>
    
    </layout>
    

    android:text=“@{message}” .

        2
  •  0
  •   Dharman Sebastian    3 年前

    这是一个有点晚的答案,但可能有助于其他面临类似问题的人。我在开发customview时遇到了同样的问题,customview为手机/平板电脑扩展了自己的布局,并将其添加到视图层次结构中。对我来说,问题是使用生成的绑定对象来扩展这个自定义视图中的布局。这一切都与 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语句导致映射过早退出递归函数,我的主绑定类的视图为null,因此崩溃。

    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块中删除上面的代码,并将其移动到延迟负载获取程序。

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

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

    所以要解决这个问题,只需遵循正常的通货膨胀方法 LayoutInflater.inflate()

    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
  •  0
  •   Dale.Che    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);
        }