Jak dodać stan przycisku niestandardowego


133

Na przykład przycisk domyślny ma następujące zależności między swoimi stanami a obrazami tła:

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

Jak mogę zdefiniować własny stan niestandardowy (coś podobnego android:state_custom), aby móc go użyć do dynamicznej zmiany wyglądu przycisku?


Chciałem mieć dodatkowe stany dla widoku EditText, aby określić, kiedy pasują dwa pola haseł, aby wyświetlić mały znacznik wyboru.
— Nathan Schwermann

Odpowiedzi:


277

Rozwiązanie wskazane przez @ (Ted Hopp) działa, ale wymaga niewielkiej korekty: w selektorze stany pozycji wymagają prefiksu „app:”, w przeciwnym razie inflater nie rozpozna poprawnie przestrzeni nazw i po cichu zawiedzie; przynajmniej tak się ze mną dzieje.

Pozwólcie, że opiszę tutaj całe rozwiązanie, z kilkoma szczegółami:

Najpierw utwórz plik „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>

Następnie zdefiniuj swoją klasę niestandardową. Na przykład może to być klasa „FoodButton” pochodząca z klasy „Button”. Będziesz musiał zaimplementować konstruktora; wdrożyć ten, który wydaje się być tym używanym przez inflatera:

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

Oprócz klasy pochodnej:

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

Ponadto zmienne stanu:

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

I kilka seterów:

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

Następnie zastąp funkcję „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;
}

Wreszcie najdelikatniejszy element tej układanki; selektor definiujący StateListDrawable, którego będziesz używać jako tła dla widgetu. To jest plik „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>

Zwróć uwagę na prefiks „app:”, podczas gdy w standardowych stanach Androida użyłbyś prefiksu „android:”. Przestrzeń nazw XML jest kluczowa dla poprawnej interpretacji przez inflater i zależy od typu projektu, w którym dodajesz atrybuty. Jeśli jest to aplikacja, zamień com.mydomain.mypackage na rzeczywistą nazwę pakietu aplikacji (bez nazwy aplikacji). Jeśli jest to biblioteka, musisz użyć „http://schemas.android.com/apk/res-auto” (i używać narzędzi w wersji 17 lub nowszej), w przeciwnym razie wystąpią błędy w czasie wykonywania.

Kilka uwag:

  • Wygląda na to, że nie musisz wywoływać funkcji "refreshDrawableState", przynajmniej rozwiązanie działa dobrze tak jak w moim przypadku

  • Aby użyć własnej klasy w pliku xml układu, musisz określić w pełni kwalifikowaną nazwę (np. Com.mydomain.mypackage.FoodButton)

  • Możesz dowolnie mieszać stany standardowe (np. Android: wciśnięty, android: włączony, android: wybrany) ze stanami niestandardowymi, aby przedstawić bardziej skomplikowane kombinacje stanów


3
Aktualizacja: jeśli klasa niestandardowa pochodzi od TextView, a nie od Button, wywołanie refreshDrawableState wydaje się konieczne, w przeciwnym razie wygląd widżetu nie zostanie zaktualizowany. Wezwanie powinno być skierowane do rozgrywających. Nie próbowałem innych klas. Testy wykonane na urządzeniu froyo.
— Giorgio Barchiesi

17
To refreshDrawableStatejest zdecydowanie ważne. Nie jestem absolutnie pewien, kiedy jest to naprawdę potrzebne. Ale w moim przypadku było to potrzebne przy programowym ustawianiu stanu. Myślę, że jest to prawdopodobnie wywoływane automatycznie z klasy View w zdarzeniu onTouchEvent. Lepiej dodam to w metodzie setSelected.
— buergi

1
GiorgioBarchiesi, mam dwa niestandardowe przyciski, a kiedy spróbuję zmienić stan obu przycisków ze zdarzenia onClick jednego przycisku, tylko kliknięty przycisk zostanie zmieniony, myślę, że @buergi ma rację, że metoda refreshDrawableState zostaje wywołana w onClickEvent. Jeszcze raz dziękuję za wspaniały tutorial :)
— Bolton,

2
Ale jak można używać stanów niestandardowych, które nie są boolean? A może selektory działają tylko na logicznych?
— Peterdk

2
Jak to działa? Mam na myśli, w jaki sposób atrybut jest aktualizowany, aby wskazywał na prawdę / fałsz? Kto to aktualizuje? Czy scalenie drawablestate, tylko jeśli zmienna lokalna jest prawdziwa, aktualizuje stan lub wartość atrybutu? Który kod dokładnie będzie aktualizował R.attr.state_fried?
— kAmol

10

W tym wątku pokazano, jak dodawać niestandardowe stany do przycisków i tym podobnych. (Jeśli nie widać nowych grup Google w swojej przeglądarce, nie kopię wątku tutaj .)


+1 wielkie dzięki, Ted! W tej chwili źródło problemu zniknęło, więc nie doszedłem do właściwej implementacji. Jednak jeśli mój klient wróci do tego ponownie, spróbuję tak, jak mi wskazałeś.
— Vit Khudenko,

Wygląda dokładnie tak, jak potrzebuję, jednak lista stanów do rysowania dla moich niestandardowych stanów się nie zmienia. Muszę czegoś brakować ...
— Nathan Schwermann,

Czy wywołujesz refreshDrawableState ()?
— Ted Hopp,

Linki są martwe.
— Mitch

1
@Mitch - Cóż, szkoda. Zobaczę, czy znajdę jakieś linki zastępcze. Jeśli nie, usunę tę odpowiedź, ponieważ jest w zasadzie bezużyteczna. W międzyczasie zaakceptowana odpowiedź zawiera wszystkie potrzebne informacje.
— Ted Hopp

7

Nie zapomnij zadzwonić refreshDrawableStatew wątku UI:

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

Zajęło mi dużo czasu, aby dowiedzieć się, dlaczego mój przycisk nie zmienia swojego stanu, mimo że wszystko wygląda dobrze.


gdzie i kiedy powinienem wysłać tego handlera?
— Arthur Melo,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.