Использование класса DrawableCompat для применения tintList

Решено попробовать новый класс DrawableCompat. Следуя инструкциям надежного источника, я вызываю:

Button b = (Button) findViewById(R.id.button);
Drawable d = b.getBackground();
d = DrawableCompat.wrap(d);
DrawableCompat.setTintList(d, getResources().getColorStateList(...));

Удивительно, но этот не работает: мой фон кнопки получает цвет, который я определяю для не нажатого, не сфокусированного состояния, но он не изменяется при нажатии/на фокусировке. p >

Мне удалось добиться совершенно другого результата,

Button b = (Button) findViewById(R.id.button);
AppCompatButton b2 = (AppCompatButton) b; //direct casting to AppCompatButton throws annoying warning
b2.setSupportBackgroundTintList(getResources().getColorStateList(...));

который работает и еще более компактен, но, тем не менее, я хотел использовать DrawableCompat. Не могли бы вы рассказать мне, почему?

Ответ 1

d = DrawableCompat.wrap(d); создает новый экземпляр, если он еще не был DrawableWrapper, поэтому вы нажимаете этот новый экземпляр, но оригинал, который хранится в кнопке, остается тем же.

Весь код будет выглядеть примерно так:

Button b = (Button) findViewById(R.id.button);
Drawable d = b.getBackground();
d = DrawableCompat.wrap(d);
DrawableCompat.setTintList(d, getResources().getColorStateList(...));
b.setBackground(d); // or setBackgroundDrawable on older platforms

Итак, я бы пошел со вторым подходом, который вы описали, потому что он абстрагирует тяжелую работу от вас.

EDIT:

Просто погрузился в код appcompat и обнаружил, что AppCompatButton tints iself, а не drawable, в отличие от Lollipop native (но только если фон включен в белый список, например, по умолчанию appcompat button drawable). Поэтому сначала нужно очистить оттенок от самой кнопки.

Button b = (Button) findViewById(R.id.button);

if (b instanceof AppCompatButton) {
    ((AppCompatButton)b).setSupportBackgroundTintList(null);
}

Drawable d = b.getBackground();
d = DrawableCompat.wrap(d);
DrawableCompat.setTintList(d, getResources().getColorStateList(...));
b.setBackground(d); // or setBackgroundDrawable on older platforms

ИЗМЕНИТЬ 2:

Приведенный выше код будет бросать NullPointerException при попытке reset списка оттенков кнопок. В настоящее время я регистрирую отчет об ошибке.

Тем временем я предлагаю вам надуть кнопку с настраиваемым фоном (без белого списка для тонирования с помощью appcompat) напрямую или с фоном @null и разрешением фона кнопки по умолчанию на

TypedArray ta = context.obtainStyledAttributes(null, new int[]{android.R.attr.background}, R.attr.buttonStyle, R.style.Widget_AppCompat_Button);
Drawable d = ta.getDrawable(0);
ta.recycle();

Окончательное решение

Так как все это выглядит довольно уродливо, самым простым (и только работающим, и надежным, но все же, как все-таки) решением для вас является следующее:

Button b = (Button) findViewById(R.id.button);
ColorStateList c = getResources().getColorStateList(...);
Drawable d = b.getBackground();
if (b instanceof AppCompatButton) {
    // appcompat button replaces tint of its drawable background
    ((AppCompatButton)b).setSupportBackgroundTintList(c);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    // Lollipop button replaces tint of its drawable background
    // however it is not equal to d.setTintList(c)
    b.setBackgroundTintList(c);
} else {
    // this should only happen if 
    // * manually creating a Button instead of AppCompatButton
    // * LayoutInflater did not translate a Button to AppCompatButton
    d = DrawableCompat.wrap(d);
    DrawableCompat.setTintList(d, c);
    b.setBackgroundDrawable(d);
}

Вы должны поместить это чудовище в класс утилиты.