Cómo obtener una enumeración que se crea en attrs.xml en código

108

Creé una Vista personalizada (encuéntrela aquí ) con un atributo declare-styleable de tipo enum. En xml, ahora puedo elegir una de las entradas de enumeración para mi atributo personalizado. Ahora quiero crear un método para establecer este valor mediante programación, pero no puedo acceder a la enumeración.

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

Lo que necesito es algo como: mCustomView.setIcon(R.id.enum_name_x); Pero no puedo encontrar la enumeración o incluso no tengo idea de cómo puedo obtener la enumeración o los nombres de la enumeración.

Informatic0re
fuente

Respuestas:

100

No parece haber una forma automatizada de obtener una enumeración de Java a partir de una enumeración de atributo; en Java puede obtener el valor numérico que especificó, la cadena es para usar en archivos XML (como muestra).

Puedes hacer esto en tu constructor de vistas:

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

Si desea el valor en una enumeración, deberá asignar el valor a una enumeración de Java usted mismo, por ejemplo:

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

Luego, en el primer bloque de código, podría usar:

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

(aunque lanzar una excepción en este punto puede no ser una gran idea, probablemente sea mejor elegir un valor predeterminado razonable)

Andy Mell
fuente
38

Es simple, mostremos a todos un ejemplo solo para mostrar lo fácil que es:

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>

Diseño personalizado:

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

ahora use la dirección como cualquier otra variable de enumeración.

Steve Moretz
fuente
En conclusión, solo use esto para obtener Enum attr: TypedArray.getInt (R.styleable.name_your_define, defaultValue)
CalvinChe
@CalvinChe ,, que devuelve un int. Steve Moretz lo tiene. Me siento tonto por no verlo, pero son las 4:30 AM . Hora de dormir ...
n00dles
Sí, así de simple. La respuesta fue solo un ejemplo para ilustrarlo mejor.
Steve Moretz
2
Pero esto solo hace dos definiciones paralelas de símbolos. Esto solo funciona mientras las definiciones sean idénticas, es decir, es frágil. El OP esperaba acceso de código a una enumeración generada a partir del XML. Parece que eso debería ser posible.
Steve White
@SteveWhite ya que no tiene acceso a xml, entonces no hay forma de automatizarlo y dejar que dependa de una definición.Esta es la forma más limpia (posible) de lograrlo.Si pudiera analizar el xml y acceder a él de una manera que podría ser genial, pero no puede. (A menos que pueda, pero no en el código, puede escribir un complemento para leer el xml y extraer los valores a su java para que se sincronice, así que sí, de esa manera no será frágil. porque está automatizado.)
Steve Moretz
13

Bueno, por el bien de la cordura. Asegúrese de que sus ordinales sean los mismos en su estilo declarado que en su declaración Enum y acceda a él como una matriz.

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];
}
Dave Thomas
fuente
3
Creo que confiar en los ordinales de enumeración aquí está destinado a crear un código no confiable. Una cosa se actualizará y la otra no y luego tendrás problemas.
tir38
1
entonces, ¿cuál es una mejor manera de hacerlo?
Jonathan
6

Permítanme agregar una solución escrita en kotlin. Agregue la función de extensión en línea:

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

Ahora obtener enum es simple:

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

Sé que ha pasado un tiempo desde que se publicó la pregunta, pero tuve el mismo problema recientemente. Hackeé un poco de algo que usa JavaPoet de Square y algunas cosas en build.gradle que crea automáticamente una clase de enumeración de Java desde attrs.xml en la construcción del proyecto.

Hay una pequeña demostración y un archivo Léame con una explicación en https://github.com/afterecho/create_enum_from_xml

Espero eso ayude.

Darren
fuente