So fügen Sie einen benutzerdefinierten Schaltflächenstatus hinzu

132

Beispielsweise weist die Standardschaltfläche die folgenden Abhängigkeiten zwischen ihren Status und Hintergrundbildern auf:

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

Wie kann ich meinen eigenen benutzerdefinierten Status definieren (smth like android:state_custom), um dann das visuelle Erscheinungsbild meiner Schaltfläche dynamisch zu ändern?

Vit Khudenko
quelle
Ich wollte zusätzliche Status für eine EditText-Ansicht, um festzustellen, wann zwei Kennwortfelder übereinstimmen, um ein kleines Häkchen anzuzeigen.
Nathan Schwermann

Antworten:

275

Die durch @ (Ted Hopp) angegebene Lösung funktioniert, muss jedoch etwas korrigiert werden: In der Auswahl benötigen die Elementzustände ein Präfix "app:", da der Inflater den Namespace sonst nicht richtig erkennt und stillschweigend fehlschlägt. Zumindest passiert mir das.

Gestatten Sie mir, hier die gesamte Lösung mit einigen weiteren Details zu melden:

Erstellen Sie zunächst die Datei "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>

Definieren Sie dann Ihre benutzerdefinierte Klasse. Beispielsweise kann es sich um eine Klasse "FoodButton" handeln, die von der Klasse "Button" abgeleitet ist. Sie müssen einen Konstruktor implementieren. Implementieren Sie dieses, das vom Inflater verwendet zu werden scheint:

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

Über der abgeleiteten Klasse:

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

Auch Ihre Zustandsvariablen:

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

Und ein paar Setter:

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

Überschreiben Sie dann die Funktion "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;
}

Schließlich das heikelste Stück dieses Puzzles; Der Selektor, der die StateListDrawable definiert, die Sie als Hintergrund für Ihr Widget verwenden. Dies ist die Datei "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>

Beachten Sie das Präfix "app:", während Sie bei Standard-Android-Status das Präfix "android:" verwendet hätten. Der XML-Namespace ist entscheidend für eine korrekte Interpretation durch den Inflater und hängt von der Art des Projekts ab, in dem Sie Attribute hinzufügen. Wenn es sich um eine Anwendung handelt, ersetzen Sie com.mydomain.mypackage durch den tatsächlichen Paketnamen Ihrer Anwendung (Anwendungsname ausgeschlossen). Wenn es sich um eine Bibliothek handelt, müssen Sie "http://schemas.android.com/apk/res-auto" verwenden (und Tools R17 oder höher verwenden). Andernfalls werden Laufzeitfehler angezeigt.

Ein paar Anmerkungen:

  • Es scheint, dass Sie die Funktion "refreshDrawableState" nicht aufrufen müssen, zumindest funktioniert die Lösung in meinem Fall so wie sie ist

  • Um Ihre benutzerdefinierte Klasse in einer Layout-XML-Datei verwenden zu können, müssen Sie den vollständig qualifizierten Namen angeben (z. B. com.mydomain.mypackage.FoodButton).

  • Sie können Standardzustände (z. B. Android: gedrückt, Android: aktiviert, Android: ausgewählt) mit benutzerdefinierten Zuständen verwechseln, um kompliziertere Zustandskombinationen darzustellen

Giorgio Barchiesi
quelle
3
Aktualisieren: Wenn die benutzerdefinierte Klasse von TextView und nicht von Button abgeleitet ist, scheint der Aufruf von refreshDrawableState erforderlich zu sein, andernfalls wird das Erscheinungsbild des Widgets nicht aktualisiert. Der Anruf wird in den Setzern getätigt. Ich habe andere Klassen nicht ausprobiert. Tests auf einem Froyo-Gerät durchgeführt.
Giorgio Barchiesi
17
Das refreshDrawableStateist definitiv wichtig. Ich bin mir nicht ganz sicher, wann es wirklich gebraucht wird. In meinem Fall wurde es jedoch benötigt, um den Status programmgesteuert festzulegen. Ich denke, es wird möglicherweise automatisch von der View-Klasse im onTouchEvent aufgerufen. Ich füge es besser in die setSelected-Methode ein.
Buergi
1
GiorgioBarchiesi, ich habe zwei benutzerdefinierte Schaltflächen, und wenn ich versuche, den Status beider Schaltflächen aus dem onClick-Ereignis einer Schaltfläche zu ändern, wird nur die angeklickte Schaltfläche geändert. Ich denke, @buergi hat Recht, dass die refreshDrawableState-Methode in der aufgerufen wird onClickEvent. Nochmals vielen Dank für Ihr wunderbares Tutorial :)
Bolton
2
Aber wie können Sie benutzerdefinierte Zustände verwenden, die es nicht sind boolean? Oder arbeiten Selektoren nur mit Booleschen Werten?
Peterdk
2
Wie funktioniert es? Ich meine, wie wird das Attribut aktualisiert, um wahr / falsch anzugeben? Wer aktualisiert es? Aktualisiert das Zusammenführen von drawablestate den Status oder den Wert des Attributs nur, wenn die lokale Variable true ist? Welcher Code aktualisiert R.attr.state_fried genau?
kAmol
10

Dieser Thread zeigt, wie Sie Schaltflächen und dergleichen benutzerdefinierte Status hinzufügen. (Wenn Sie nicht können die neuen Google - Gruppen in Ihrem Browser sehen, gibt es eine Kopie des Themas hier .)

Ted Hopp
quelle
+1 vielen Dank, Ted! Im Moment ist der Ursprung des Problems verschwunden, sodass ich nicht zur eigentlichen Implementierung gekommen bin. Sollte mein Kunde jedoch noch einmal darauf zurückkommen, werde ich versuchen, wie Sie mich darauf hingewiesen haben.
Vit Khudenko
Sieht genau so aus, wie ich es brauche, aber die Zustandslisten-Drawables für meine benutzerdefinierten Zustände ändern sich nicht. Ich muss etwas vermissen ...
Nathan Schwermann
Rufen Sie refreshDrawableState () auf?
Ted Hopp
Die Links sind tot.
Mitch
@Mitch - Nun, das ist schade. Ich werde sehen, ob ich Ersatzlinks finden kann. Wenn nicht, werde ich diese Antwort löschen, da sie im Grunde genommen unbrauchbar ist. In der Zwischenzeit enthält die akzeptierte Antwort alle erforderlichen Informationen.
Ted Hopp
6

Bitte vergessen Sie nicht, refreshDrawableStateinnerhalb des UI-Threads aufzurufen :

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

Ich habe viel Zeit gebraucht, um herauszufinden, warum mein Knopf seinen Zustand nicht ändert, obwohl alles richtig aussieht.

Nishant Soni
quelle
Wo oder wann soll ich diesen Handler posten?
Arthur Melo