Можете ли вы использовать BindingAdapter на ViewStub?

Я хочу создать BindingAdapter "inflateWhen" и прикрепить его к заготовке представления, чтобы он раздувался, когда логическое значение истинно. Однако BindingAdapter продолжает пытаться работать с корневым представлением заглушки представления, что приводит к сбою его компиляции. Есть ли способ сделать это как адаптер привязки, а не делать это программно в действии?

Вот что у меня есть до сих пор:

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

Это то, что у меня есть, но при подключении к viewstub, например

<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);

Похоже, он пытается применить привязку к расширенному представлению, но это не то, что мне нужно.


person wshelor    schedule 26.06.2019    source источник


Ответы (1)


Обновление от 08.10.2020:

Я написал статью на Medium, где привожу пример того, как переключаться между макетами на лету в зависимости от состояния экрана с помощью ViewStub и DataBinding:

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

Старый принятый ответ:

Я также столкнулся с проблемой написания @BindingAdapter для ViewStub, чтобы контролировать инфляцию макета с помощью привязки данных вместо прямой ссылки на ViewStub и вызова inflate()

Попутно я провел небольшое расследование и изучил следующие вещи:

  • ViewStub должен иметь атрибут android:id, чтобы избежать ошибок сборки, таких как java.lang.IllegalStateException: target.id не должен быть нулевым;
  • любой настраиваемый атрибут, объявленный для ViewStub в XML, привязка данных пытается установить в качестве переменной макет, который будет увеличен вместо заглушки;
  • ... поэтому любой адаптер привязки написан для ViewStub никогда не будет использоваться при привязке данных
  • есть только один, но довольно хитрый @BindingAdapter, который работает: androidx.databinding.adapters.ViewStubBindingAdapter и позволяет устанавливать ViewStub.OnInflateListener через XML-атрибут android:onInflate
  • первым аргументом ViewStubBindingAdapter является ViewStubProxy, а не View или ViewStub!;
  • любой другой адаптер, написанный аналогичным образом, не работает - привязка данных пытается установить переменную для будущего макета вместо использования адаптера.
  • НО разрешено переопределять существующий 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}"
    />
...

Таким образом, теперь инфляция будет происходить только тогда, когда состояние равно State.Empty, а привязка данных установит переменную viewModel в увеличенный макет @layout/no_data.

Не очень изящное, но рабочее решение.

person Max Diland    schedule 31.07.2020