Comment ajouter un état de bouton personnalisé

133

Par exemple, le bouton par défaut a les dépendances suivantes entre ses états et les images d'arrière-plan:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_window_focused="false" android:state_enabled="true"
        android:drawable="@drawable/btn_default_normal" />
    <item android:state_window_focused="false" android:state_enabled="false"
        android:drawable="@drawable/btn_default_normal_disable" />
    <item android:state_pressed="true" 
        android:drawable="@drawable/btn_default_pressed" />
    <item android:state_focused="true" android:state_enabled="true"
        android:drawable="@drawable/btn_default_selected" />
    <item android:state_enabled="true"
        android:drawable="@drawable/btn_default_normal" />
    <item android:state_focused="true"
        android:drawable="@drawable/btn_default_normal_disable_focused" />
    <item
        android:drawable="@drawable/btn_default_normal_disable" />
</selector>

Comment puis-je définir mon propre état personnalisé (smth like android:state_custom), afin que je puisse l'utiliser pour modifier dynamiquement l'apparence visuelle de mon bouton?

Vit Khudenko
la source
Je voulais des états supplémentaires pour une vue EditText afin de déterminer quand deux boîtes de mot de passe correspondent pour afficher une petite coche.
Nathan Schwermann

Réponses:

276

La solution indiquée par @ (Ted Hopp) fonctionne, mais nécessite une petite correction: dans le sélecteur, les états de l'élément ont besoin d'un préfixe "app:", sinon le gonfleur ne reconnaîtra pas correctement l'espace de noms, et échouera silencieusement; du moins c'est ce qui m'arrive.

Permettez-moi de vous rapporter ici toute la solution, avec quelques détails supplémentaires:

Commencez par créer le fichier "res / values ​​/ attrs.xml":

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="food">
        <attr name="state_fried" format="boolean" />
        <attr name="state_baked" format="boolean" />
    </declare-styleable>
</resources>

Définissez ensuite votre classe personnalisée. Par exemple, il peut s'agir d'une classe "FoodButton", dérivée de la classe "Button". Vous devrez implémenter un constructeur; implémentez celui-ci, qui semble être celui utilisé par le gonfleur:

public FoodButton(Context context, AttributeSet attrs) {
    super(context, attrs);
}

En plus de la classe dérivée:

private static final int[] STATE_FRIED = {R.attr.state_fried};
private static final int[] STATE_BAKED = {R.attr.state_baked};

En outre, vos variables d'état:

private boolean mIsFried = false;
private boolean mIsBaked = false;

Et quelques setters:

public void setFried(boolean isFried) {mIsFried = isFried;}
public void setBaked(boolean isBaked) {mIsBaked = isBaked;}

Remplacez ensuite la fonction "onCreateDrawableState":

@Override
protected int[] onCreateDrawableState(int extraSpace) {
    final int[] drawableState = super.onCreateDrawableState(extraSpace + 2);
    if (mIsFried) {
        mergeDrawableStates(drawableState, STATE_FRIED);
    }
    if (mIsBaked) {
        mergeDrawableStates(drawableState, STATE_BAKED);
    }
    return drawableState;
}

Enfin, la pièce la plus délicate de ce puzzle; le sélecteur définissant le StateListDrawable que vous utiliserez comme arrière-plan de votre widget. Il s'agit du fichier "res / drawable / food_button.xml":

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res/com.mydomain.mypackage">
<item
    app:state_baked="true"
    app:state_fried="false"
    android:drawable="@drawable/item_baked" />
<item
    app:state_baked="false"
    app:state_fried="true"
    android:drawable="@drawable/item_fried" />
<item
    app:state_baked="true"
    app:state_fried="true"
    android:drawable="@drawable/item_overcooked" />
<item
    app:state_baked="false"
    app:state_fried="false"
    android:drawable="@drawable/item_raw" />
</selector>

Notez le préfixe "app:", alors qu'avec les états Android standard, vous auriez utilisé le préfixe "android:". L'espace de noms XML est crucial pour une interprétation correcte par le gonfleur et dépend du type de projet dans lequel vous ajoutez des attributs. S'il s'agit d'une application, remplacez com.mydomain.mypackage par le nom réel du package de votre application (nom de l'application exclu). S'il s'agit d'une bibliothèque, vous devez utiliser "http://schemas.android.com/apk/res-auto" (et utiliser Tools R17 ou version ultérieure) ou vous obtiendrez des erreurs d'exécution.

Quelques notes:

  • Il semble que vous n'ayez pas besoin d'appeler la fonction "refreshDrawableState", au moins la solution fonctionne bien telle quelle, dans mon cas

  • Afin d'utiliser votre classe personnalisée dans un fichier xml de mise en page, vous devrez spécifier le nom complet (par exemple com.mydomain.mypackage.FoodButton)

  • Vous pouvez comme weel mélanger des états standard (par exemple android: pressé, android: activé, android: sélectionné) avec des états personnalisés, afin de représenter des combinaisons d'états plus compliquées

Giorgio Barchiesi
la source
3
Mise à jour: si la classe personnalisée dérive de TextView, plutôt que Button, l'appel à refreshDrawableState semble être nécessaire, sinon l'apparence du widget n'est pas mise à jour. L'appel doit être placé dans les setters. Je n'ai pas essayé d'autres cours. Tests réalisés sur un appareil froyo.
Giorgio Barchiesi
17
Le refreshDrawableStateest vraiment important. Je ne sais pas vraiment quand c'est vraiment nécessaire. Mais dans mon cas, il était nécessaire lors de la définition de l'état par programme. Je suppose qu'il est peut-être appelé automatiquement à partir de la classe View dans le onTouchEvent. Je ferais mieux de l'ajouter dans la méthode setSelected.
buergi
1
GiorgioBarchiesi, j'ai deux boutons personnalisés, et lorsque j'essaie de changer l'état des deux boutons de l'événement onClick d'un bouton, seul le bouton cliqué sera changé, je pense que @buergi a raison que la méthode refreshDrawableState est appelée dans le onClickEvent. Merci encore pour votre merveilleux tutoriel :)
Bolton
2
Mais comment pouvez-vous utiliser des états personnalisés qui ne le sont pas boolean? Ou les sélecteurs fonctionnent-ils uniquement sur des booléens?
Peterdk
2
Comment ça marche? Je veux dire, comment l'attribut est mis à jour pour indiquer vrai / faux? Qui le met à jour? La fusion de drawablestate, uniquement si la variable locale est vraie, met à jour l'état ou la valeur de l'attribut? Quel code exactement mettra à jour R.attr.state_fried?
kAmol
10

Ce fil montre comment ajouter des états personnalisés aux boutons et autres. (Si vous ne pouvez pas voir les nouveaux groupes Google dans votre navigateur, il y a une copie du fil ici .)

Ted Hopp
la source
+1 merci beaucoup, Ted! À l'heure actuelle, l'origine du problème a disparu, je ne suis donc pas arrivé à la mise en œuvre réelle. Cependant, si mon client y revient à nouveau, j'essaierai de la manière que vous m'avez indiquée.
Vit Khudenko
Ressemble exactement à ce dont j'ai besoin, mais les tableaux de liste d'états pour mes états personnalisés ne changent
pas.Je
Appelez-vous refreshDrawableState ()?
Ted Hopp
Les liens sont morts.
Mitch le
@Mitch - Eh bien, c'est dommage. Je vais voir si je peux trouver des liens de remplacement. Sinon, je supprimerai cette réponse, car elle est fondamentalement inutile telle quelle. Pendant ce temps, la réponse acceptée contient toutes les informations nécessaires.
Ted Hopp le
7

N'oubliez pas d'appeler refreshDrawableStatedans le fil de l'interface utilisateur:

mHandler.post(new Runnable() {
    @Override
    public void run() {
        refreshDrawableState();
    }
});

Il m'a fallu beaucoup de temps pour comprendre pourquoi mon bouton ne change pas d'état même si tout semble correct.

Nishant Soni
la source
où et quand dois-je publier ce gestionnaire?
Arthur Melo