具有双向数据绑定的自定义视图

Custom View with Two-Way Databinding

我有一个自定义视图,它扩展了 LinearLayout 并扩充了一个包含几个视图的布局。

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    xmlns:android="http://schemas.android.com/apk/res/android">

    <EditText
        android:id="@+id/voice_edittext"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="wrap_content"
        android:hint="Add answer here"
        />

    <ImageButton
        android:id="@+id/microphone_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_mic_black_24dp"
        />
    <ImageButton
        android:id="@+id/delete_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_cancel_black_24dp"
        android:visibility="gone"
        />
    </LinearLayout>

我现在想在使用此视图时双向绑定 Edittext 的文本值,如下所示:

<com.sunilson.quizcreator.presentation.views.EditTextWithVoiceInput
                android:id="@+id/form_question"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="10dp"
                android:layout_marginTop="10dp"
                app:editTextValue="@={viewModel.observableText}"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

为此我创建了一些绑定适配器

@BindingAdapter("editTextValueAttrChanged")
fun setListener(editTextWithVoiceInput: EditTextWithVoiceInput, listener: InverseBindingListener) {
    editTextWithVoiceInput.voice_edittext.addTextChangedListener(object : TextWatcher {
        override fun afterTextChanged(p0: Editable?) {
            listener.onChange()
        }

        override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
        override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
    })
}

@BindingAdapter("editTextValue")
fun setTextValue(editTextWithVoiceInput: EditTextWithVoiceInput, value: String?) {
    if (value != editTextWithVoiceInput.voice_edittext.text.toString()) editTextWithVoiceInput.voice_edittext.setText(value)
}

@InverseBindingAdapter(attribute = "editTextValue")
fun getTextValue(editTextWithVoiceInput: EditTextWithVoiceInput): String? {
    return editTextWithVoiceInput.voice_edittext.text.toString()
}

这是视图的代码:

class EditTextWithVoiceInput(context: Context, attributeSet: AttributeSet) : LinearLayout(context, attributeSet) {

    init {
        val inflater = context?.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
        val view = inflater.inflate(R.layout.voice_edittext, this, true)

        view.microphone_button.setOnTouchListener { p0, p1 ->
            ...
        }
    }
}

现在的问题是,当包含视图的片段启动时,我得到这个错误:

E/AndroidRuntime: FATAL EXCEPTION: main
              Process: com.sunilson.quizcreator, PID: 12627
              java.lang.NullPointerException: Attempt to invoke virtual method 'void com.sunilson.quizcreator.presentation.views.EditTextWithVoiceInput.setTag(java.lang.Object)' on a null object reference
                  at com.sunilson.quizcreator.databinding.FragmentAddQuestionBinding.<init>(FragmentAddQuestionBinding.java:112)
                  at android.databinding.DataBinderMapperImpl.getDataBinder(DataBinderMapperImpl.java:15)
                  at android.databinding.DataBindingUtil.bind(DataBindingUtil.java:199)
                  at android.databinding.DataBindingUtil.inflate(DataBindingUtil.java:130)
                  at android.databinding.DataBindingUtil.inflate(DataBindingUtil.java:95)
                  at com.sunilson.quizcreator.presentation.SingleActivity.fragments.AddQuestionFragment.AddQuestionFragment.onCreateView(AddQuestionFragment.kt:34)
                  at android.support.v4.app.Fragment.performCreateView(Fragment.java:2425)
                  at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1460)
                  at android.support.v4.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManager.java:1784)
                  at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1852)
                  at android.support.v4.app.BackStackRecord.executeOps(BackStackRecord.java:802)
                  at android.support.v4.app.FragmentManagerImpl.executeOps(FragmentManager.java:2623)
                  at android.support.v4.app.FragmentManagerImpl.executeOpsTogether(FragmentManager.java:2410)
                  at android.support.v4.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManager.java:2365)
                  at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:2272)
                  at android.support.v4.app.FragmentManagerImpl.run(FragmentManager.java:733)
                  at android.os.Handler.handleCallback(Handler.java:789)
                  at android.os.Handler.dispatchMessage(Handler.java:98)
                  at android.os.Looper.loop(Looper.java:180)
                  at android.app.ActivityThread.main(ActivityThread.java:6944)
                  at java.lang.reflect.Method.invoke(Native Method)
                  at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
                  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:835)

我在这里错过了什么?

好的,所以我的问题是我的自定义视图错误地实现了构造函数。当我在 XML 中膨胀此视图时,我需要传递属性集的构造函数将该属性集传递给超级构造函数,但我没有这样做。没有它,我的视图就没有属性,也无法通过它的 ID 等找到。

现在我有两个构造函数,具体取决于我是从 XML 还是代码膨胀:

constructor(context: Context, optional: Boolean) : super(context) {
        ...
    }

    constructor(context: Context, attributeSet: AttributeSet) : super(context, attributeSet) {
        ...
    }