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


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();
    }
});

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


где и когда я должен разместить этот обработчик?
— Артур Мело
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.