Can you use a BindingAdapter on a ViewStub?

 ̄綄美尐妖づ 提交于 2021-01-28 08:08:37

问题


I want to create an "inflateWhen" BindingAdapter and attach it to a viewstub to have it inflate when a boolean value is true. However, the BindingAdapter keeps trying to operate on the root view of the viewstub, causing it to fail to compile. Is there any way to do this as a bindingadapter rather than having to do it programmatically in the activity?

Here's what I have so far:

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

This is what I have, but when attached to a viewstub like

<ViewStub
    android:id="@+id/activity_footer"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:inflateWhen="@{viewmodel.userid != 0}" /> 

it fails to compile. The error is:

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

Looks like it's trying to apply the binding to the inflated view, but that's not what I want here.


回答1:


08.10.2020 Update:

I have written an article on Medium where I provide an example of how to switch between layouts on the fly depending on the screen state using ViewStub and DataBinding:

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

Old accepted answer:

I also faced the problem to write @BindingAdapter for the ViewStub to control layout inflation using databinding instead of direct referencing to the ViewStub and calling inflate()

Along the way, I did some investigation and studied the following things:

  • ViewStub must have android:id attribute to avoid build errors like java.lang.IllegalStateException: target.id must not be null;
  • any custom attribute declared for ViewStub in an XML, databinding tries to set as a variable to the layout which will be inflated instead of the stub;
  • ... that's why any binding adapter is written for ViewStub will never be used by databinding
  • there is only one but pretty tricky @BindingAdapter which works: androidx.databinding.adapters.ViewStubBindingAdapter and allows setting ViewStub.OnInflateListener trough XML attribute android:onInflate
  • the ViewStubBindingAdapter's first argument is ViewStubProxy not View or ViewStub!;
  • any different adapter written similarly does not work - databinding tries to set variable to the future layout instead of using the adapter
  • BUT it is allowed to override existing androidx.databinding.adapters.ViewStubBindingAdapter and implement some desired logic.

Because this adapter is one and the only option to interact with ViewStub using databinding I decided to override the adapter and use not for its intended purpose

The idea is to provide specific ViewStub.OnInflateListener which will be the listener itself and at the same time will be a signal that ViewStub.inflate() should be called:

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

and overriding binding adapter:

@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()
    }
}

and XML part

...
<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}"
    />
...

So now the inflation will happen only when the state is State.Empty and databinding will set viewModel variable to the inflated @layout/no_data layout.

Not really graceful but working solution.



来源:https://stackoverflow.com/questions/56779550/can-you-use-a-bindingadapter-on-a-viewstub

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!