我正在尝试创建一个简单的表单布局,该布局使用包含 LiveData 的 ViewModel 绑定(bind)到每个表单字段,以便输入的数据可以通过配置更改和后退导航(在多步表单的情况下)持续存在。
此外,我有一个自定义表单字段 View ,它有自己的布局 xml 来做一些必要的处理。但是,我很难通过表单布局传递数据以绑定(bind)到自定义 View 布局。
这是我的 ViewModel、Fragment、Field 对象及其布局的简化版本:
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
}
}
字段.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<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>
我想做的是将 ViewModel 中的实时数据绑定(bind)到表单字段,方法是将其作为属性传递给表单 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>
但是,当我尝试此操作时,出现以下错误:
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)
作为替代方案,这可以通过包含字段布局而不是如下所示轻松实现,但在这种情况下,我无法在 Field 对象中进行任何我想做的设置
<?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>
我猜这与 xml 膨胀和数据绑定(bind)变量生成的顺序有关,但我很难调试
编辑:我只是注意到我没有在 Field 数据绑定(bind)中设置生命周期所有者,但是即使我没有在 Field 类中膨胀布局,也会出现问题
最佳答案
尝试使用 @BindingAdapter("...") 而不是 @可绑定(bind) 注解。
我制作了一个示例 repo,它反射(reflect)并解决了您的问题,也许看看。
https://github.com/phamtdat/BindingAdapterSample
关于android - 如何将数据绑定(bind)变量作为 xml 属性传递给自定义 View ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58719919/