How to get an enum which is created in attrs.xml in code

I created a custom View (find it here) with an declare-styleable attribute of type enum. In xml I can now choose one of the enum entries for my custom attribute. Now I want to create an method to set this value programmatically, but I can not access the enum.

attr.xml

<declare-styleable name="IconView">
    <attr name="icon" format="enum">
        <enum name="enum_name_one" value="0"/>
        ....
        <enum name="enum_name_n" value="666"/>
   </attr>
</declare-styleable>     

layout.xml

<com.xyz.views.IconView
    android:id="@+id/heart_icon"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:icon="enum_name_x"/>

What I need is something like: mCustomView.setIcon(R.id.enum_name_x); But I can not find the enum or I even have no idea how I can get the enum or the names of the enum.


Solution 1:

There does not seem to be an automated way to get a Java enum from an attribute enum - in Java you can get the numeric value you specified - the string is for use in XML files (as you show).

You could do this in your view constructor:

TypedArray a = context.getTheme().obtainStyledAttributes(
                attrs,
                R.styleable.IconView,
                0, 0);

    // Gets you the 'value' number - 0 or 666 in your example
    if (a.hasValue(R.styleable.IconView_icon)) {
        int value = a.getInt(R.styleable.IconView_icon, 0));
    }

    a.recycle();
}

If you want the value into an enum you would need to either map the value into a Java enum yourself, e.g.:

private enum Format {
    enum_name_one(0), enum_name_n(666);
    int id;

    Format(int id) {
        this.id = id;
    }

    static Format fromId(int id) {
        for (Format f : values()) {
            if (f.id == id) return f;
        }
        throw new IllegalArgumentException();
    }
}

Then in the first code block you could use:

Format format = Format.fromId(a.getInt(R.styleable.IconView_icon, 0))); 

(though throwing an exception at this point may not be a great idea, probably better to choose a sensible default value)

Solution 2:

It's simple let's show everybody an example just to show how easy it is:

attr.xml:

<declare-styleable name="MyMotionLayout">
    <attr name="motionOrientation" format="enum">
        <enum name="RIGHT_TO_LEFT" value="0"/>
        <enum name="LEFT_TO_RIGHT" value="1"/>
        <enum name="TOP_TO_BOTTOM" value="2"/>
        <enum name="BOTTOM_TO_TOP" value="3"/>
    </attr>
</declare-styleable>

Custom layout:

public enum Direction {RIGHT_TO_LEFT, LEFT_TO_RIGHT, TOP_TO_BOTTOM, BOTTOM_TO_TOP}
Direction direction;
...
    TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.MyMotionLayout);
    Direction direction = Direction.values()[ta.getInt(R.styleable.MyMotionLayout_motionOrientation,0)];

now use direction like any other enumeration variable.

Solution 3:

Let me add a solution written in kotlin. Add inline extension function:

inline fun <reified T : Enum<T>> TypedArray.getEnum(index: Int, default: T) =
    getInt(index, -1).let { if (it >= 0) enumValues<T>()[it] else default 
}

Now getting enum is simple:

val a: TypedArray = obtainStyledAttributes(...)
val yourEnum: YourEnum = a.getEnum(R.styleable.YourView_someAttr, YourEnum.DEFAULT)
a.recycle()

Solution 4:

Well for sanity's sake. Make sure your ordinals are the same in your declared styleable as in your Enum declaration and access it as an array.

TypedArray a = context.getTheme().obtainStyledAttributes(
                   attrs,
                   R.styleable.IconView,
                   0, 0);

int ordinal = a.getInt(R.styleable.IconView_icon, 0);

if (ordinal >= 0 && ordinal < MyEnum.values().length) {
      enumValue = MyEnum.values()[ordinal];
}

Solution 5:

I know it's been a while since the question was posted, but I had the same issue recently. I hacked a little something together that uses Square's JavaPoet and some stuff in the build.gradle that automatically creates a Java enum class from the attrs.xml on project build.

There's a little demo and a readme with an explanation at https://github.com/afterecho/create_enum_from_xml

Hope it helps.