Come aggiungere uno stato del pulsante personalizzato


132

Ad esempio, il pulsante predefinito ha le seguenti dipendenze tra i suoi stati e le immagini di sfondo:

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

Come posso definire il mio stato personalizzato (smth like android:state_custom), in modo da poterlo utilizzare per modificare dinamicamente l'aspetto visivo del mio pulsante?


Volevo stati extra per una vista EditText per determinare quando due caselle di password corrispondono per mostrare un piccolo segno di spunta.
Nathan Schwermann,

Risposte:


275

La soluzione indicata da @ (Ted Hopp) funziona, ma necessita di una piccola correzione: nel selettore, gli stati degli elementi hanno bisogno di un prefisso "app:", altrimenti il ​​gonfiatore non riconoscerà correttamente lo spazio dei nomi e fallirà silenziosamente; almeno questo è quello che mi succede.

Consentitemi di segnalare qui l'intera soluzione, con alcuni dettagli:

Innanzitutto, crea il file "res / valori / 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>

Quindi definisci la tua classe personalizzata. Ad esempio, può essere una classe "FoodButton", derivata dalla classe "Button". Dovrai implementare un costruttore; implementare questo, che sembra essere quello utilizzato dal gonfiatore:

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

Oltre alla classe derivata:

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

Inoltre, le variabili di stato:

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

E un paio di setter:

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

Quindi sovrascrivere la funzione "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;
}

Finalmente il pezzo più delicato di questo puzzle; il selettore che definisce StateListDrawable che utilizzerai come sfondo per il tuo widget. Questo è il file "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>

Nota il prefisso "app:", mentre con gli stati Android standard avresti usato il prefisso "android:". Lo spazio dei nomi XML è cruciale per una corretta interpretazione da parte del gonfiatore e dipende dal tipo di progetto in cui si stanno aggiungendo gli attributi. Se si tratta di un'applicazione, sostituire com.mydomain.mypackage con il nome del pacchetto effettivo dell'applicazione (nome dell'applicazione escluso). Se si tratta di una libreria, è necessario utilizzare "http://schemas.android.com/apk/res-auto" (e utilizzare Strumenti R17 o successivi) o si otterranno errori di runtime.

Un paio di note:

  • Sembra che non sia necessario chiamare la funzione "refreshDrawableState", almeno la soluzione funziona bene così com'è, nel mio caso

  • Per utilizzare la tua classe personalizzata in un file xml di layout, dovrai specificare il nome completo (ad es. Com.mydomain.mypackage.FoodButton)

  • Puoi come weel confondere gli stati standard (es. Android: premuto, android: abilitato, android: selezionato) con stati personalizzati, al fine di rappresentare combinazioni di stati più complicate


3
Aggiornamento: se la classe personalizzata deriva da TextView, anziché da Button, la chiamata a refreshDrawableState sembra essere necessaria, altrimenti l'aspetto del widget non viene aggiornato. La chiamata deve essere piazzata nei setter. Non ho provato altre classi. Test eseguiti su un dispositivo froyo.
Giorgio Barchiesi,

17
Il refreshDrawableStateè sicuramente importante. Non sono assolutamente sicuro quando è davvero necessario. Ma nel mio caso era necessario per impostare lo stato a livello di codice. Immagino che sia chiamato automaticamente dalla classe View in onTouchEvent. Meglio aggiungerlo nel metodo setSelected.
Buergi,

1
GiorgioBarchiesi, ho due pulsanti personalizzati e quando provo a modificare lo stato di entrambi i pulsanti dall'evento onClick di un pulsante, verrà modificato solo il pulsante selezionato, penso che @buergi abbia ragione nel chiamare il metodo refreshDrawableState nel onClickEvent. Grazie ancora per il tuo meraviglioso tutorial :)
Bolton,

2
Ma come si possono usare stati personalizzati che non lo sono boolean? Oppure i selettori funzionano solo su valori booleani?
Peterdk,

2
Come funziona? Voglio dire, come viene aggiornato l'attributo per indicare vero / falso? Chi lo aggiorna? La fusione del drawablestate, solo se la variabile locale è vera, aggiorna lo stato o il valore dell'attributo? Quale codice aggiornerà esattamente R.attr.state_fried?
kAmol

10

Questo thread mostra come aggiungere stati personalizzati a pulsanti e simili. (Se non riesci a vedere i nuovi gruppi di Google nel tuo browser, c'è una copia del thread qui .)


+1 grazie mille, Ted! In questo momento l'origine del problema è passata, quindi non sono arrivato all'implementazione effettiva. Tuttavia, se il mio cliente dovesse tornare nuovamente su questo, proverò nel modo in cui mi hai indicato.
Vit Khudenko,

Sembra esattamente quello di cui ho bisogno, tuttavia i drawable dell'elenco di stati per i miei stati personalizzati non cambiano, mi devo perdere qualcosa ...
Nathan Schwermann,

Stai chiamando refreshDrawableState ()?
Ted Hopp,

I collegamenti sono morti.
Mitch,

@Mitch - Beh, è ​​un peccato. Vedrò se riesco a trovare alcuni link di sostituzione. In caso contrario, eliminerò questa risposta, poiché è praticamente inutile così com'è. Nel frattempo, la risposta accettata ha tutte le informazioni necessarie.
Ted Hopp,

6

Non dimenticare di chiamare refreshDrawableStateall'interno del thread dell'interfaccia utente:

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

Ci è voluto molto tempo per capire perché il mio pulsante non sta cambiando stato anche se tutto sembra a posto.


dove o quando devo pubblicare questo gestore?
Arthur Melo,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.