Como: Definir item de tema (estilo) para widget personalizado

86

Eu escrevi um widget customizado para um controle que usamos amplamente em nosso aplicativo. A classe widget deriva ImageButtone estende-a de algumas maneiras simples. Eu defini um estilo que posso aplicar ao widget conforme ele é usado, mas prefiro configurar por meio de um tema. Em R.styleablevejo atributos de estilo de widget como imageButtonStylee textViewStyle. Existe alguma maneira de criar algo assim para o widget personalizado que escrevi?

Smith
fonte

Respostas:

209

Sim, há uma maneira:

Suponha que você tenha uma declaração de atributos para seu widget (in attrs.xml):

<declare-styleable name="CustomImageButton">
    <attr name="customAttr" format="string"/>
</declare-styleable>

Declare um atributo que você usará como referência de estilo (in attrs.xml):

<declare-styleable name="CustomTheme">
    <attr name="customImageButtonStyle" format="reference"/>
</declare-styleable>

Declare um conjunto de valores de atributo padrão para o widget (in styles.xml):

<style name="Widget.ImageButton.Custom" parent="android:style/Widget.ImageButton">
    <item name="customAttr">some value</item>
</style>

Declare um tema personalizado (in themes.xml):

<style name="Theme.Custom" parent="@android:style/Theme">
    <item name="customImageButtonStyle">@style/Widget.ImageButton.Custom</item>
</style>

Use este atributo como o terceiro argumento no construtor de seu widget (in CustomImageButton.java):

public class CustomImageButton extends ImageButton {
    private String customAttr;

    public CustomImageButton( Context context ) {
        this( context, null );
    }

    public CustomImageButton( Context context, AttributeSet attrs ) {
        this( context, attrs, R.attr.customImageButtonStyle );
    }

    public CustomImageButton( Context context, AttributeSet attrs,
            int defStyle ) {
        super( context, attrs, defStyle );

        final TypedArray array = context.obtainStyledAttributes( attrs,
            R.styleable.CustomImageButton, defStyle,
            R.style.Widget_ImageButton_Custom ); // see below
        this.customAttr =
            array.getString( R.styleable.CustomImageButton_customAttr, "" );
        array.recycle();
    }
}

Agora você deve aplicar Theme.Customa todas as atividades que usa CustomImageButton(em AndroidManifest.xml):

<activity android:name=".MyActivity" android:theme="@style/Theme.Custom"/>

Isso é tudo. Agora CustomImageButtontenta carregar os valores de customImageButtonStyleatributo padrão do atributo do tema atual. Se nenhum atributo for encontrado no tema ou no valor do atributo, @nullo argumento final obtainStyledAttributesserá usado: Widget.ImageButton.Customneste caso.

Você pode alterar os nomes de todas as instâncias e todos os arquivos (exceto AndroidManifest.xml), mas seria melhor usar a convenção de nomenclatura do Android.

Michael
fonte
4
Existe uma maneira de fazer exatamente a mesma coisa sem usar um tema? Tenho alguns widgets personalizados, mas quero que os usuários possam usar esses widgets com qualquer tema que desejem. Fazer da maneira que você postou exigiria que os usuários usassem meu tema.
theDazzler
3
@theDazzler: Se você quer dizer uma biblioteca que os desenvolvedores podem usar, eles serão capazes de criar seus próprios temas e especificar o estilo do widget usando um atributo de estilo no tema ( customImageButtonStylenesta resposta). É assim que a barra de ação é personalizada no Android. E se você quer dizer mudar um tema em tempo de execução, então não é possível com esta abordagem.
Michael
@Michael, Sim, eu quis dizer uma biblioteca que os desenvolvedores podem usar. Seu comentário resolveu meu problema. Obrigado!
theDazzler
30
O mais incrível de tudo isso é que alguém no Google acha que está tudo bem.
Glenn Maynard
1
Será que name="CustomTheme"no declare-styleableatributo invólucro servir a qualquer propósito? É apenas para organização, porque não vejo isso sendo usado em lugar nenhum. Posso colocar todos os meus atributos de estilo para vários widgets em um invólucro?
Tenfour04 de
4

Outro aspecto além da excelente resposta de Michael é substituir os atributos personalizados nos temas. Suponha que você tenha várias visualizações personalizadas, todas referindo-se ao atributo personalizado "custom_background".

<declare-styleable name="MyCustomStylables">
    <attr name="custom_background" format="color"/>
</declare-styleable>

Em um tema você define qual é o valor

<style name="MyColorfulTheme" parent="AppTheme">
    <item name="custom_background">#ff0000</item>
</style>

ou

<style name="MyBoringTheme" parent="AppTheme">
    <item name="custom_background">#ffffff</item>
</style>

Você pode se referir ao atributo em um estilo

<style name="MyDefaultLabelStyle" parent="AppTheme">
    <item name="android:background">?background_label</item>
</style>

Observe o ponto de interrogação, também usado para o atributo android de referência como em

?android:attr/colorBackground

Como a maioria de vocês notou, você pode - e provavelmente deve - usar referências @color em vez de cores codificadas.

Então porque não apenas fazer

<item name="android:background">@color/my_background_color</item>

Você não pode alterar a definição de "my_background_color" em tempo de execução, mas pode facilmente alternar os temas.

mir
fonte