Как добавить пользовательское состояние кнопки

133

Например, кнопка по умолчанию имеет следующие зависимости между ее состояниями и фоновыми изображениями:

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

Как я могу определить свое собственное состояние (что-то вроде android:state_custom), чтобы я мог использовать его для динамического изменения внешнего вида моей кнопки?

Вит Худенко
источник
Мне нужны были дополнительные состояния для представления EditText, чтобы определить, когда два поля пароля совпадают, чтобы показать небольшую галочку.
Натан Шверманн,

Ответы:

276

Решение, обозначенное @ (Ted Hopp), работает, но требует небольшой поправки: в селекторе состояниям элементов требуется префикс «app:», иначе надувной модуль не распознает пространство имен правильно и не выполнит никаких действий; по крайней мере, это то, что происходит со мной.

Позвольте мне сообщить здесь все решение с некоторыми подробностями:

Сначала создайте файл 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>

Затем определите свой собственный класс. Например, это может быть класс «FoodButton», производный от класса «Button». Вам нужно будет реализовать конструктор; реализовать этот, который, похоже, используется надувным устройством:

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

Вверху производного класса:

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

Также ваши переменные состояния:

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

И пара сеттеров:

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

Затем переопределите функцию 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;
}

Наконец, самая тонкая часть этой головоломки; селектор, определяющий StateListDrawable, который вы будете использовать в качестве фона для своего виджета. Это файл "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>

Обратите внимание на префикс «app:», тогда как в стандартных состояниях Android вы бы использовали префикс «android:». Пространство имен XML имеет решающее значение для правильной интерпретации инфлятором и зависит от типа проекта, в который вы добавляете атрибуты. Если это приложение, замените com.mydomain.mypackage фактическим именем пакета вашего приложения (за исключением имени приложения). Если это библиотека, вы должны использовать «http://schemas.android.com/apk/res-auto» (и использовать Tools R17 или новее), иначе вы получите ошибки во время выполнения.

Пара замечаний:

  • Похоже, вам не нужно вызывать функцию «refreshDrawableState», по крайней мере, решение работает хорошо, в моем случае

  • Чтобы использовать свой собственный класс в XML-файле макета, вам нужно будет указать полное имя (например, com.mydomain.mypackage.FoodButton)

  • Вы можете смешивать стандартные состояния (например, android: нажат, android: включен, android: selected) с настраиваемыми состояниями, чтобы представить более сложные комбинации состояний.

Джорджио Баркиези
источник
3
Обновление: если настраиваемый класс является производным от TextView, а не от Button, вызов refreshDrawableState кажется необходимым, иначе внешний вид виджета не обновляется. Заявка должна быть размещена в сеттерах. Другие классы не пробовала. Испытания выполнены на устройстве Froyo.
Джорджио Барчиеси
17
Это refreshDrawableStateопределенно важно. Я не совсем уверен, когда это действительно нужно. Но в моем случае это понадобилось при программной настройке состояния. Я предполагаю, что он, возможно, автоматически вызывается из класса View в onTouchEvent. Лучше я добавлю это в метод setSelected.
buergi
1
GiorgioBarchiesi, у меня есть две пользовательские кнопки, и когда я пытаюсь изменить состояние обеих кнопок из события onClick одной кнопки, изменится только нажатая кнопка, я думаю, что @buergi прав, что метод refreshDrawableState вызывается в onClickEvent. Еще раз спасибо за ваш замечательный урок :)
Bolton
2
Но как можно использовать настраиваемые состояния, которых нет boolean? Или селекторы работают только с логическими значениями?
Peterdk
2
Как это работает? Я имею в виду, как атрибут обновляется до состояния true / false? Кто его обновляет? Обновляет ли слияние drawablestate, только если локальная переменная истинна, состояние или значение атрибута? Какой именно код будет обновлять R.attr.state_fried?
kAmol
10

В этом потоке показано, как добавлять пользовательские состояния к кнопкам и т.п. (Если вы не можете увидеть новые группы Google в вашем браузере, есть копия нити здесь .)

Тед Хопп
источник
+1 Большое спасибо, Тед! Сейчас проблема исчезла, поэтому я не дошел до фактической реализации. Однако, если мой клиент вернется к этому снова, я попробую сделать так, как вы мне указали.
Вит Худенко
Выглядит именно так, как мне нужно, однако чертежи списка состояний для моих настраиваемых состояний не меняются, я, должно быть, что-то
упускаю
Вы вызываете refreshDrawableState ()?
Тед Хопп,
Ссылки мертвы.
Митч
@ Митч - Что ж, жаль. Я посмотрю, смогу ли я найти какие-нибудь ссылки для замены. Если нет, я удалю этот ответ, поскольку он в принципе бесполезен. Между тем, в принятом ответе есть вся необходимая информация.
Тед Хопп,
7

Пожалуйста, не забудьте позвонить refreshDrawableStateв поток пользовательского интерфейса:

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

Мне потребовалось много времени, чтобы понять, почему моя кнопка не меняет свое состояние, хотя все выглядит правильно.

Нишант Сони
источник
где и когда я должен разместить этот обработчик?
Артур Мело