Comment: définir un élément de thème (style) pour un widget personnalisé

86

J'ai écrit un widget personnalisé pour un contrôle que nous utilisons largement dans notre application. La classe widget en dérive ImageButtonet l'étend de plusieurs manières simples. J'ai défini un style que je peux appliquer au widget tel qu'il est utilisé, mais je préférerais le configurer via un thème. Dans R.styleableje vois des attributs de style de widget comme imageButtonStyleet textViewStyle. Existe-t-il un moyen de créer quelque chose comme ça pour le widget personnalisé que j'ai écrit?

jsmith
la source

Réponses:

209

Oui, il y a un moyen:

Supposons que vous ayez une déclaration d'attributs pour votre widget (in attrs.xml):

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

Déclarez un attribut que vous utiliserez pour une référence de style (dans attrs.xml):

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

Déclarez un ensemble de valeurs d'attribut par défaut pour le widget (in styles.xml):

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

Déclarez un thème personnalisé (dans themes.xml):

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

Utilisez cet attribut comme troisième argument dans le constructeur de votre 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();
    }
}

Vous devez maintenant appliquer Theme.Customà toutes les activités qui utilisent CustomImageButton(dans AndroidManifest.xml):

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

C'est tout. Essaie maintenant CustomImageButtonde charger les valeurs d' customImageButtonStyleattribut par défaut à partir de l' attribut du thème actuel. Si aucun attribut de ce type n'est trouvé dans le thème ou la valeur de l'attribut est @nullalors le dernier argument à obtainStyledAttributessera utilisé: Widget.ImageButton.Customdans ce cas.

Vous pouvez modifier les noms de toutes les instances et de tous les fichiers (sauf AndroidManifest.xml), mais il serait préférable d'utiliser la convention de dénomination Android.

Michael
la source
4
Existe-t-il un moyen de faire exactement la même chose sans utiliser de thème? J'ai des widgets personnalisés, mais je veux que les utilisateurs puissent utiliser ces widgets avec n'importe quel thème qu'ils souhaitent. Le faire comme vous l'avez publié obligerait les utilisateurs à utiliser mon thème.
theDazzler
3
@theDazzler: Si vous parlez d'une bibliothèque que les développeurs peuvent utiliser, ils pourront créer leurs propres thèmes et spécifier le style du widget en utilisant un attribut de style dans le thème ( customImageButtonStyledans cette réponse). C'est ainsi que la barre d'action est personnalisée sur Android. Et si vous voulez dire changer un thème à l'exécution, alors ce n'est pas possible avec cette approche.
Michael
@Michael, Oui, je voulais dire une bibliothèque que les développeurs peuvent utiliser. Votre commentaire a résolu mon problème. Merci!
theDazzler
30
Le plus étonnant dans tout cela, c'est que quelqu'un chez Google pense que c'est correct.
Glenn Maynard
1
Est -ce que name="CustomTheme"dans l' declare-styleableattribut emballage sert à quelque chose ? Est-ce juste pour l'organisation, parce que je ne le vois pas utilisé nulle part. Puis-je simplement mettre tous mes attributs de style pour divers widgets dans un même wrapper?
Tenfour04
4

Un autre aspect en plus de l'excellente réponse de Michael est de remplacer les attributs personnalisés dans les thèmes. Supposons que vous ayez un certain nombre de vues personnalisées qui font toutes référence à l'attribut personnalisé "custom_background".

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

Dans un thème, vous définissez la valeur

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

Vous pouvez faire référence à l'attribut dans un style

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

Notez le point d'interrogation, également utilisé pour l'attribut android de référence comme dans

?android:attr/colorBackground

Comme la plupart d'entre vous l'ont remarqué, vous pouvez - et devriez probablement - utiliser des références @color au lieu de couleurs codées en dur.

Alors pourquoi ne pas simplement faire

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

Vous ne pouvez pas changer la définition de "my_background_color" à l'exécution, alors que vous pouvez facilement changer de thème.

mir
la source