How to set enum attribute by value instead of name in Android Layout?

Michael Troger :

I have one custom view holding another one. Hierarchy:

MyOuterView
->MyInnerView

MyInnerView has an enum attribute like:

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

So I can instantiate the component in MyOuterView XML like:

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

Which works of course. The MyOuterView offers an argument for customization itself. Based on this argument I want to set the argument of the MyInnerView.

The wished behavior is that I can work with Data Binding like:

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

where getMyAttr() looks like:

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

The result is a compile issue.

****/ data binding error ****msg:Cannot find the setter for attribute 'app:myAttr' with parameter type int on com.example.MyInnerView

So obviously I can't set the enum by value but only by name. Any idea besides creating the MyInnerView programmatically? Please note that I can't change MyInnerView.

lelloman :

I can't set the enum by value but only by name

That is not entirely true. You wouldn't be able to set the value with DataBinding even by name, the thing is that the value of an attribute defined in an xml is passed to the View through the constructor. A View could have both an attribute and a setter but that's not necessarily the case. For instance, is you set a value for android:text of a TextView, that value is set to com.android.internal.R.styleable.TextView_text which is then retrieved from the AttributeSet in the constructor. If you use DataBinding, setText() is called, but these are 2 completely different things, the feature is the same but they're not related in the code.

Given that you can't modify MyInnerView and that there is no setter you can call for myAttr, your only option is to pass it through the constructor. DataBinding is simply not feasible, even with a BinderAdapter you wouldn't be able to set the value in the AttributeSet because the View is already instantiated at that point.

Option 1 - themes

Define a new attribute

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

Then resolve this attribute with a style, for instance you could have

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

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

Set it in the layout xml

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

And then call setTheme(int) in the Activity before the views are instantiated.

Option 2 - custom BindingAdapter

If you wanted to set your value DataBinding (because you had a setter, or because you wanted to hack MyInnerView with reflection) then you would need to create a custom BindingAdapter, for instance

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

Then in the layout xml

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

And give the int value from your ViewModel

public class MyInnerViewModel {

    public int getAttr() {
        return 1234;
    }
}

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=429396&siteId=1