Как установить атрибут enum по значению вместо имени в Android Layout?

У меня есть один пользовательский вид с другим. Иерархия:

MyOuterView
->MyInnerView

MyInnerView имеет атрибут enum, например:

<attr name="myAttr" format="enum">
    <enum name="foo" value="0"/>
    <enum name="bar" value="1"/>
</attr>

Поэтому я могу создать экземпляр компонента в MyOuterView XML, например:

<com.example.MyInnerView
....
app:myAttr="foo"/>

Что, конечно, работает. MyOuterView предлагает аргумент для самой настройки. Основываясь на этом аргументе, я хочу установить аргумент MyInnerView.

Желаемое поведение заключается в том, что я могу работать с Data Binding, например:

<com.example.MyInnerView
....
app:myAttr="@{data.getMyAttr()}"/>

где getMyAttr() выглядит так:

public int getMyAttr() {
    return myAttr; // returns 0 or 1
}

Результатом является компиляция.

****/ошибка привязки данных **** msg: Не удается найти установщик для атрибута 'app: myAttr' с типом параметра int на com.example.MyInnerView

Поэтому, очевидно, я не могу установить перечисление по значению, но только по имени. Любая идея, помимо создания MyInnerView программно? Обратите внимание, что я не могу изменить MyInnerView.

Ответ 1

Я не могу установить перечисление по значению, но только по имени

Это не совсем верно. Вы не сможете установить значение с помощью DataBinding даже по имени, дело в том, что значение атрибута, определенного в xml, передается в View через конструктор. В View может быть как атрибут, так и сеттер, но это необязательно. Например, вы задали значение для android:text TextView, для этого значения установлено значение com.android.internal.R.styleable.TextView_text который затем извлекается из AttributeSet в конструкторе. Если вы используете DataBinding, вызывается setText(), но это две совершенно разные вещи, эта функция одна и та же, но они не связаны в коде.

Учитывая, что вы не можете изменять MyInnerView и что нет setter, вы можете вызвать myAttr, ваш единственный вариант - передать его через конструктор. DataBinding просто не представляется возможным, даже с BinderAdapter вы не смогли бы установить значение в AttributeSet, поскольку View уже инициализируется в этой точке.

Вариант 1 - темы

Определить новый атрибут

<attr name="MyInnerViewAttrValue" format="integer" />

Затем разрешите этот атрибут со стилем, например, вы могли бы

<style name="AppTheme.Foo">
    <item name="MyInnerViewAttrValue">0</item>
</style>

<style name="AppTheme.Bar">
    <item name="MyInnerViewAttrValue">1</item>
</style>

Установите его в макет xml

<com.example.MyInnerView
    ...
    app:myAttr="?attr/MyInnerViewAttrValue" />

А затем вызовите setTheme(int) в Activity перед созданием представлений.

Вариант 2 - пользовательский BindingAdapter

Если вы хотите установить значение DataBinding (потому что у вас был сеттер или потому, что вы хотели взломать MyInnerView с отражением), вам нужно будет создать собственный BindingAdapter, например

@BindingAdapter("myAttrValue")
public static void setMyAttr(MyInnerView myInnerView, int value) {
    switch (value) {
        case 0:
            myInnerView.foo();
            break;
        default:
            myInnerView.bar();
    }
}

Затем в макете xml

<com.example.lelloman.dummy.MyInnerView
    ...
    myAttrValue="@{model.attr}" />

И дайте значение int из вашей ViewModel

public class MyInnerViewModel {

    public int getAttr() {
        return 1234;
    }
}

Ответ 2

К сожалению, вы можете использовать имя атрибута, например app:myAttr="@{...}" только если для этого есть установщик, и этот сеттер сопоставляется с именем атрибута или может быть разрешен его именем. В вашем случае, если этот атрибут используется только в конструкторе представлений, а в представлении нет общедоступного метода для изменения - у вас все еще есть несколько возможных решений: - расширить представление и реализовать логику для настройки вида, как этот атрибут (если возможно) - сделайте BindingAdapter и используйте Reflection (если возможно). - создать свой собственный вид путем копирования и вставки)