Como obter um enum criado em attrs.xml no código

108

Eu criei um modo de exibição personalizado (encontre-o aqui ) com um atributo declarável de estilo do tipo enum. Em xml, agora posso escolher uma das entradas de enum para meu atributo personalizado. Agora, quero criar um método para definir esse valor programaticamente, mas não consigo acessar o 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"/>

O que eu preciso é algo como: mCustomView.setIcon(R.id.enum_name_x); Mas não consigo encontrar o enum ou nem mesmo tenho ideia de como posso obter o enum ou os nomes do enum.

Informatic0re
fonte

Respostas:

100

Não parece haver uma maneira automatizada de obter um enum Java de um enum de atributo - em Java, você pode obter o valor numérico especificado - a string é para uso em arquivos XML (como você mostra).

Você pode fazer isso em seu construtor de visualização:

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

Se você quiser o valor em um enum, precisará mapear o valor em um enum Java você mesmo, por exemplo:

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

Então, no primeiro bloco de código, você pode usar:

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

(embora lançar uma exceção neste ponto possa não ser uma boa ideia, provavelmente é melhor escolher um valor padrão razoável)

Andy Mell
fonte
38

É simples, vamos mostrar a todos um exemplo apenas para mostrar como é fácil:

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>

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

agora use direction como qualquer outra variável de enumeração.

Steve Moretz
fonte
Em conclusão, apenas use isso para obter Enum attr: TypedArray.getInt (R.styleable.name_your_define, defaultValue)
CalvinChe
@CalvinChe ,, que retorna um int. Steve Moretz tem. Me sinto idiota por não ter visto, mas são 4h30 . Hora de dormir ...
n00dles
Sim, tão simples quanto isso. A resposta foi apenas um exemplo para ilustrar melhor.
steve moretz
2
Mas isso apenas cria duas definições paralelas de símbolos. Isso funciona apenas enquanto as definições são idênticas - ou seja, é frágil. O OP esperava acesso ao código a um enum gerado a partir do XML. Parece que isso deveria ser possível.
Steve White
@SteveWhite, uma vez que você não tem acesso ao xml, não há como automatizá-lo e deixá-lo depender de uma definição. Esta é a maneira mais limpa (possível) de obtê-lo. Se você pudesse analisar o xml e acessá-lo de uma maneira que poderia ser ótimo, mas você não pode. (A menos que você possa, mas não no código, você pode escrever um plugin para ler o xml e extrair os valores para o seu java para que seja sincronizado, então sim, dessa forma não será frágil porque é automatizado.)
steve moretz
13

Bem, pelo bem da sanidade. Certifique-se de que seus ordinais sejam os mesmos em seu estilo declarado e em sua declaração Enum e acesse-os como uma 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
fonte
3
Acho que depender de enum ordinals aqui está destinado a criar código não confiável. Uma coisa será atualizada e a outra não e então você terá problemas.
tir38 de
1
então, qual é a melhor maneira de fazer isso?
Jonathan
6

Deixe-me adicionar uma solução escrita em kotlin. Adicionar função de extensão inline:

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

Agora, obter enum é simples:

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

Sei que já faz um tempo que a pergunta foi postada, mas tive o mesmo problema recentemente. Eu hackeei uma coisinha que usa JavaPoet do Square e algumas coisas no build.gradle que cria automaticamente uma classe de enum Java a partir do attrs.xml na construção do projeto.

Há uma pequena demonstração e um leia-me com uma explicação em https://github.com/afterecho/create_enum_from_xml

Espero que ajude.

Darren
fonte