简体   繁体   English

如何将数据绑定变量作为 xml 属性传递给自定义视图

[英]How to pass data binding variable as xml attribute to custom view

I am trying to create a simple form layout that uses a ViewModel containing LiveData to bind to each form field so that the inputted data can persist through configuration changes and back navigation (in the case of a multi-step form).我正在尝试创建一个简单的表单布局,该布局使用包含 LiveData 的 ViewModel 绑定到每个表单字段,以便输入的数据可以通过配置更改和后退导航(在多步表单的情况下)持续存在。 Additionally, I have a custom form field view with its own layout xml to do some required processing.此外,我有一个自定义表单字段视图,它有自己的布局 xml 来做一些必要的处理。 However, I am having difficulty passing data through the form layout to bind to the custom view layout.但是,我很难通过表单布局传递数据以绑定到自定义视图布局。

Here is a simplified version of my ViewModel, Fragment, Field object and its layout:这是我的 ViewModel、Fragment、Field object 及其布局的简化版本:

class FormViewModel : ViewModel() {
    @Bindable val email = MutableLiveData<String>().apply { value = "" }
}
class FormFragment : BaseFragment() {

    private val formViewModel: FormViewModel by lazy {
        ViewModelProviders.of(requireActivity()).get(FormViewModel::class.java)
    }

    override fun onCreateView(inflater: LayoutInflater, 
                              container: ViewGroup?, 
                              savedInstanceState: Bundle?) : View? =
        (DataBindingUtil
            .inflate(inflater, R.layout.fragment_form, container, false) as FragmentFormBinding)
            .apply {
                lifecycleOwner = requireActivity()
                viewModel = formViewModel
            }
            .root
}
class Field @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0)
    : LinearLayout(context, attrs, defStyleAttr) {

    @Bindable var fieldData: MutableLiveData<String> = MutableLiveData<String>().apply { value = "" }

    init {
        val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
        (DataBindingUtil
            .inflate(inflater, R.layout.field, this, true) as FieldBinding)
            .apply { fieldData = this@Field.fieldData }
            .root
    }
}

field.xml字段.xml

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

    <data>
        <variable
                name="fieldData"
                type="androidx.lifecycle.MutableLiveData&lt;String>"/>
    </data>

    <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">
        <com.google.android.material.textfield.TextInputLayout
                android:id="@+id/inputField"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                tools:hint="Email">

            <com.google.android.material.textfield.TextInputEditText
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:hint="Email"
                    android:text="@={ fieldData }" />

        </com.google.android.material.textfield.TextInputLayout>
    </LinearLayout>
</layout>

What I would like to do is bind the live data from the ViewModel to a form field by passing it as an attribute to an instance of my custom form field in the form xml like so:我想做的是将 ViewModel 中的实时数据绑定到表单字段,方法是将其作为属性传递给我的自定义表单字段的实例,格式为 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="viewModel"
                type="packageName.FormViewModel"/>
    </data>

    <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:padding="32dp">

        <packageName.Field
                android:id="@+id/emailField"
                app:fieldData="@{ viewModel.email }"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="60dp" />

    </LinearLayout>
</layout>

However, when I try this I get the following error:但是,当我尝试此操作时,出现以下错误:

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: packageName, PID: 27543
    java.lang.RuntimeException: Failed to call observer method
        at androidx.lifecycle.ClassesInfoCache$MethodReference.invokeCallback(ClassesInfoCache.java:226)
        at androidx.lifecycle.ClassesInfoCache$CallbackInfo.invokeMethodsForEvent(ClassesInfoCache.java:194)
        at androidx.lifecycle.ClassesInfoCache$CallbackInfo.invokeCallbacks(ClassesInfoCache.java:185)
        at androidx.lifecycle.ReflectiveGenericLifecycleObserver.onStateChanged(ReflectiveGenericLifecycleObserver.java:37)
        at androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.java:361)
        at androidx.lifecycle.LifecycleRegistry.addObserver(LifecycleRegistry.java:188)
        at androidx.databinding.ViewDataBinding.setLifecycleOwner(ViewDataBinding.java:394)
        at packageName.FormFragment.onCreateView(FormFragment.kt:24)
        at androidx.fragment.app.Fragment.performCreateView(Fragment.java:2669)
        at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1321)
        at androidx.fragment.app.FragmentManager.addAddedFragments(FragmentManager.java:2515)
        at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:2290)
        at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:2246)
        at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:2143)
        at androidx.fragment.app.FragmentManager$3.run(FragmentManager.java:417)
        at android.os.Handler.handleCallback(Handler.java:739)
        at android.os.Handler.dispatchMessage(Handler.java:95)
        at android.os.Looper.loop(Looper.java:135)
        at android.app.ActivityThread.main(ActivityThread.java:5221)
        at java.lang.reflect.Method.invoke(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:372)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)
     Caused by: java.lang.IllegalArgumentException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter <set-?>
        at packageName.Field.setFieldData(Field.kt)
        at packageName.databinding.FragmentFormBindingImpl.executeBindings(FragmentFormBindingImpl.java:127)
        at androidx.databinding.ViewDataBinding.executeBindingsInternal(ViewDataBinding.java:472)
        at androidx.databinding.ViewDataBinding.executePendingBindings(ViewDataBinding.java:444)
        at androidx.databinding.ViewDataBinding$OnStartListener.onStart(ViewDataBinding.java:1685)
        at java.lang.reflect.Method.invoke(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:372)
        at androidx.lifecycle.ClassesInfoCache$MethodReference.invokeCallback(ClassesInfoCache.java:216)
        at androidx.lifecycle.ClassesInfoCache$CallbackInfo.invokeMethodsForEvent(ClassesInfoCache.java:194) 
        at androidx.lifecycle.ClassesInfoCache$CallbackInfo.invokeCallbacks(ClassesInfoCache.java:185) 
        at androidx.lifecycle.ReflectiveGenericLifecycleObserver.onStateChanged(ReflectiveGenericLifecycleObserver.java:37) 
        at androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.java:361) 
        at androidx.lifecycle.LifecycleRegistry.addObserver(LifecycleRegistry.java:188) 
        at androidx.databinding.ViewDataBinding.setLifecycleOwner(ViewDataBinding.java:394) 
        at packageName.FormFragment.onCreateView(FormFragment.kt:24) 
        at androidx.fragment.app.Fragment.performCreateView(Fragment.java:2669) 
        at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1321) 
        at androidx.fragment.app.FragmentManager.addAddedFragments(FragmentManager.java:2515) 
        at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:2290) 
        at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:2246) 
        at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:2143) 
        at androidx.fragment.app.FragmentManager$3.run(FragmentManager.java:417) 
        at android.os.Handler.handleCallback(Handler.java:739) 
        at android.os.Handler.dispatchMessage(Handler.java:95) 
        at android.os.Looper.loop(Looper.java:135) 
        at android.app.ActivityThread.main(ActivityThread.java:5221) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at java.lang.reflect.Method.invoke(Method.java:372) 
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694) 

As an alternative this can easily be achieved by including the field layout instead like the following but in this case I cannot do any of the setup I would like to do in the Field object作为替代方案,这可以通过包含字段布局而不是如下所示轻松实现,但在这种情况下,我无法在字段 object 中进行任何我想做的设置

<?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="viewModel"
                type="packageName.FormViewModel"/>
    </data>

    <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:padding="32dp">

        <include
                layout="@layout/field"
                android:id="@+id/emailField"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:fieldData="@{ viewModel.email }" />
    </LinearLayout>
</layout>

I'm guessing has to do with the order in which the xml is inflated and data binding variable generated but I am having difficulty debugging我猜想与 xml 的膨胀顺序和生成数据绑定变量的顺序有关,但我很难调试

EDIT: I just noticed I am not setting the lifecycle owner in the Field data binding but the issue arises even if I am not inflating the layout in the Field class编辑:我只是注意到我没有在字段数据绑定中设置生命周期所有者,但是即使我没有在字段 class 中膨胀布局,也会出现问题

Try using @BindingAdapter("...") instead of the @Bindable annotation.尝试使用@BindingAdapter("...")而不是@Bindable注释。

I made a sample repo that reflects and solves your problem, maybe have a look.我制作了一个示例 repo,它反映并解决了您的问题,也许看看。

https://github.com/phamtdat/BindingAdapterSample https://github.com/phamtdat/BindingAdapterSample

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 Android - 在 XML 复合视图组件中创建新的视图属性并传递给其子级 - 布局中的数据绑定 - Android - Make new view attribute and pass to its children in XML compound view component - data binding in layout 如何通过xml为自定义视图传递重力属性? - How do you pass the gravity attribute via xml for a custom view? Android - 如何通过数据绑定从 XML 传递视图的 object - Android - How to pass object of a view from XML with data binding Android XML Data Binding pass view by id - Android XML Data Binding pass view by id Android自定义视图:如何通过LiveData和数据绑定更新自定义枚举属性 - Android custom view: How to update custom enum attribute via LiveData & data binding 如何将XML文件传递到布局自定义属性? - How to pass XML file to layout custom attribute? 如何在Android Studio的数据绑定中将值传递给android.view.View.OnClickListener的变量? - How to pass a value to the variable of android.view.View.OnClickListener in data binding in Android Studio? 数据绑定:如何将xml中的Context传递给方法? - Data binding: How pass Context in xml to method? 如何使用 xml 中的视图引用将参数传递给使用数据绑定的函数? - How to use a view's reference in xml to pass arguments to a function using data binding? 如何将自定义视图数据更新为数据绑定? - How to update custom view data to data binding?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM