可以在 ViewStub 上使用 BindingAdapter 吗?

Can you use a BindingAdapter on a ViewStub?

我想创建一个 "inflateWhen" BindingAdapter 并将其附加到 viewstub 以使其在布尔值为真时膨胀。但是,BindingAdapter 不断尝试对 viewstub 的根视图进行操作,导致编译失败。有什么方法可以作为绑定适配器来执行此操作,而不必在 activity 中以编程方式执行此操作?

这是我目前的情况:

@BindingAdapter("inflateWhen")
fun inflateWhen(viewstub: ViewStub, inflate: Boolean) {
    if (inflate) {
        viewstub.inflate()
    } else {
        viewstub.visibility = View.GONE
    }
}

这就是我所拥有的,但是当附加到像

这样的视图存根时
<ViewStub
    android:id="@+id/activity_footer"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:inflateWhen="@{viewmodel.userid != 0}" /> 

编译失败。错误是:

ActivityMyAccountSectionedBindingImpl.java:1087: error: cannot find symbol
            if (this.pageFooter.isInflated()) this.pageFooter.getBinding().setVariable(BR.inflateWhen, viewmodelRatingInt0);

看起来它正在尝试将绑定应用于膨胀视图,但这不是我想要的。

08.10.2020 更新:

我在 Medium 上写了一篇文章,其中提供了一个示例,说明如何使用 ViewStub 和 DataBinding 根据屏幕状态动态切换布局:

https://medium.com/@mxdiland/viewstub-databinding-handle-screen-states-easily-2f1c01098b87

旧的接受答案:

我也遇到了为 ViewStub 编写 @BindingAdapter 以使用数据绑定而不是直接引用 ViewStub 并调用 [=16] 来控制布局 inflation 的问题=]

一路上,我做了一些调查,研究了以下内容:

  • ViewStub 必须具有 android:id 属性以避免构建错误,如 java.lang.IllegalStateException: target.id must not be null;
  • 在 XML 中为 ViewStub 声明的任何自定义属性,数据绑定尝试将其设置为布局的变量,而不是存根;
  • ... 这就是为什么为 ViewStub 编写的任何绑定适配器永远不会被数据绑定
  • 使用的原因
  • 只有一个非常棘手的 @BindingAdapter 有效:androidx.databinding.adapters.ViewStubBindingAdapter 并允许通过 XML 属性 android:onInflate
  • 设置 ViewStub.OnInflateListener
  • ViewStubBindingAdapter 的第一个参数是 ViewStubProxy 而不是 ViewViewStub!;
  • 以类似方式编写的任何不同适配器都不起作用 - 数据绑定尝试将变量设置为未来的布局,而不是使用适配器
  • 但是可以覆盖现有的 androidx.databinding.adapters.ViewStubBindingAdapter 并实现一些所需的逻辑。

因为此适配器是唯一一个使用数据绑定与 ViewStub 交互的选项,所以我决定覆盖该适配器并且不用于其预期目的

我们的想法是提供特定的 ViewStub.OnInflateListener ,它将是监听器本身,同时也是一个信号,表明 ViewStub.inflate() 应该被调用:

class ViewStubInflationProvoker(
    listener: ViewStub.OnInflateListener = ViewStub.OnInflateListener { _, _ ->  }
) : ViewStub.OnInflateListener by listener {
    companion object {
        @JvmStatic
        fun provideIf(clause: Boolean): ViewStubInflationProvoker? {
            return if (clause) {
                ViewStubInflationProvoker()
            } else {
                null
            }
        }
    }
}

和覆盖绑定适配器:

@BindingAdapter("android:onInflate")
fun setOnInflateListener(
    viewStubProxy: ViewStubProxy,
    listener: ViewStub.OnInflateListener?
) {
    viewStubProxy.setOnInflateListener(listener)

    if (viewStubProxy.isInflated) {
        viewStubProxy.root.visibility = View.GONE.takeIf { listener == null } ?: View.VISIBLE
        return
    }

    if (listener is ViewStubInflationProvoker) {
        viewStubProxy.viewStub?.inflate()
    }
}

和XML部分

...
<ViewStub
    android:id="@+id/no_data_stub"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout="@layout/no_data"
    android:onInflate="@{ViewStubInflationProvoker.provideIf(viewModel.state == State.Empty.INSTANCE)}"
    app:viewModel="@{viewModel.noDataViewModel}"
    />
...

所以现在 inflation 只会在状态为 State.Empty 时发生并且数据绑定会将 viewModel 变量设置为膨胀的 @layout/no_data 布局.

不是很优雅但可行的解决方案。