StateListDrawable для переключения цветофильтров
Я хочу создать пользовательские кнопки для использования в TabHost. Я пытался использовать один и тот же ресурс изображения (png), но менял цветовой фильтр в зависимости от состояния. Поэтому я сделал этот бит, чтобы он служил макетом для пользовательской кнопки:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_height="fill_parent">
<ImageView android:id="@+id/tab_icon"
android:layout_centerInParent="true" android:layout_alignParentTop="true"
android:layout_centerHorizontal="true" android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView android:id="@+id/tab_text" android:layout_below="@id/tab_icon"
android:layout_centerHorizontal="true" android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
В своей деятельности я добавляю вкладки так:
tabHost.addTab(tabHost.newTabSpec(TAB_NAME_NEWS).setIndicator(buildTab(R.drawable.tab_icon_news, R.string.news))
.setContent(newsIntent));
И это метод buildTab:
private final static int[] SELECTED = new int[] { android.R.attr.state_selected };
private final static int[] IDLE = new int[] { -android.R.attr.state_selected };
private View buildTab(int icon, int label) {
LayoutInflater inflater = LayoutInflater.from(this);
View view = inflater.inflate(R.layout.tab_button, null);
StateListDrawable drawable = new StateListDrawable();
Drawable selected = getResources().getDrawable(icon);
selected.mutate();
selected.setBounds(0, 0, selected.getIntrinsicWidth(), selected.getIntrinsicHeight());
selected.setColorFilter(new LightingColorFilter(0xFFFFFFFF, 0x0000FF00));
drawable.addState(SELECTED, selected);
Drawable idle = getResources().getDrawable(icon);
idle.mutate();
idle.setColorFilter(new LightingColorFilter(0xFFFFFFFF, 0x000000FF));
drawable.addState(IDLE, idle);
((ImageView) view.findViewById(R.id.tab_icon)).setImageDrawable(drawable);
((TextView) view.findViewById(R.id.tab_text)).setText(getString(label));
return view;
}
В выбранном состоянии изображение должно быть полностью зеленым (0x0000FF00
), а в невыбранном состоянии он должен быть синим (0x000000FF
).
Проблема в том, что цветовые фильтры, похоже, полностью игнорируются. Я не вижу изменения цвета ни при каких обстоятельствах.
Я также пытался получить тот же результат, установив android:tint
собственность на <ImageView/>
, но, очевидно, вы не можете использовать ссылку на <selector>
там, так как он бросает NumberFormatException
,
Я не вижу, что я делаю неправильно, поэтому любая помощь будет оценена.
5 ответов
Хорошо, я никогда не получал вышеуказанный код для работы, так что вот что я в итоге сделал.
Сначала я создал подкласс LayerDrawable:
public class StateDrawable extends LayerDrawable {
public StateDrawable(Drawable[] layers) {
super(layers);
}
@Override
protected boolean onStateChange(int[] states) {
for (int state : states) {
if (state == android.R.attr.state_selected) {
super.setColorFilter(Color.argb(255, 255, 195, 0), PorterDuff.Mode.SRC_ATOP);
} else {
super.setColorFilter(Color.GRAY, PorterDuff.Mode.SRC_ATOP);
}
}
return super.onStateChange(states);
}
@Override
public boolean isStateful() {
return true;
}
}
Я изменил buildTab()
метод к следующему:
private View buildTab(int icon, int label) {
LayoutInflater inflater = LayoutInflater.from(this);
View view = inflater.inflate(R.layout.tab_button, null);
((ImageView) view.findViewById(R.id.tab_icon)).setImageDrawable(new StateDrawable(new Drawable[] { getResources()
.getDrawable(icon) }));
((TextView) view.findViewById(R.id.tab_text)).setText(getString(label));
return view;
}
Я все еще добавляю вкладки вот так:
Intent fooIntent = new Intent().setClass(this, FooActivity.class);
tabHost.addTab(tabHost.newTabSpec(TAB_NAME_INFO).setIndicator(buildTab(R.drawable.tab_icon_info, R.string.info)).setContent(infoIntent));
Это работает для меня, совместимо с Android 1.6.
Не удалось решить эту проблему с помощью применения цветового фильтра непосредственно к объекту рисования. Для меня работало получить изображение как растровое изображение, создать пустое второе изображение с теми же показателями, определить холст для второго, применить этот цветовой фильтр к объекту рисования и нарисовать первое растровое изображение на втором. Наконец, создайте BitmapDrawable из нового Bitmap, и все готово. Вот код
ImageButton imageButton = (ImageButton)findViewById(R.id.aga);
Bitmap one = BitmapFactory.decodeResource(getResources(), R.drawable.pen_circle);
Bitmap oneCopy = Bitmap.createBitmap(one.getWidth(), one.getHeight(), Config.ARGB_8888);
Canvas c = new Canvas(oneCopy);
Paint p = new Paint();
p.setColorFilter(new LightingColorFilter(Color.CYAN, 1));
c.drawBitmap(one, 0, 0, p);
StateListDrawable states = new StateListDrawable();
states.addState(new int[] {android.R.attr.state_pressed}, new BitmapDrawable(oneCopy));
states.addState(new int[] { }, imageButton.getDrawable());
imageButton.setImageDrawable(states);
Это мой класс, взломанный для поддержки ColorFilter:
Использование:
final Drawable icon = getResources().getDrawable(iconResId);
final Drawable filteredIcon = // this is important
icon.getConstantState().newDrawable();
final FilterableStateListDrawable selectorDrawable =
new FilterableStateListDrawable();
selectorDrawable.addState(ICON_STATE_SELECTED, filteredIcon,
new PorterDuffColorFilter(mIconOverlayColor, PorterDuff.Mode.SRC_ATOP));
selectorDrawable.addState(ICON_STATE_DEFAULT, icon);
Как вы видите, ColorFilter не применяется непосредственно к рисованному объекту, он ассоциируется с ним при добавлении состояния в селектор Drawable.
Здесь важно то, что
- вам нужно создать новый объект рисования из константного состояния, или вы измените состояние константы и, следовательно, любой экземпляр этого объекта в вашей деятельности.
- вам нужно использовать мой пользовательский метод addState, он имеет то же имя, что и метод фреймворка addState, но я добавил дополнительный аргумент (ColorFilter). Этот метод НЕ существует в суперклассе фреймворка!
Код (грязный, но у меня работает):
/**
* This is an extension to {@link android.graphics.drawable.StateListDrawable} that workaround a bug not allowing
* to set a {@link android.graphics.ColorFilter} to the drawable in one of the states., it add a method
* {@link #addState(int[], android.graphics.drawable.Drawable, android.graphics.ColorFilter)} for that purpose.
*/
public class FilterableStateListDrawable extends StateListDrawable {
private int currIdx = -1;
private int childrenCount = 0;
private SparseArray<ColorFilter> filterMap;
public FilterableStateListDrawable() {
super();
filterMap = new SparseArray<ColorFilter>();
}
@Override
public void addState(int[] stateSet, Drawable drawable) {
super.addState(stateSet, drawable);
childrenCount++;
}
/**
* Same as {@link #addState(int[], android.graphics.drawable.Drawable)}, but allow to set a colorFilter associated to this Drawable.
*
* @param stateSet - An array of resource Ids to associate with the image.
* Switch to this image by calling setState().
* @param drawable -The image to show.
* @param colorFilter - The {@link android.graphics.ColorFilter} to apply to this state
*/
public void addState(int[] stateSet, Drawable drawable, ColorFilter colorFilter) {
// this is a new custom method, does not exist in parent class
int currChild = childrenCount;
addState(stateSet, drawable);
filterMap.put(currChild, colorFilter);
}
@Override
public boolean selectDrawable(int idx) {
if (currIdx != idx) {
setColorFilter(getColorFilterForIdx(idx));
}
boolean result = super.selectDrawable(idx);
// check if the drawable has been actually changed to the one I expect
if (getCurrent() != null) {
currIdx = result ? idx : currIdx;
if (!result) {
// it has not been changed, meaning, back to previous filter
setColorFilter(getColorFilterForIdx(currIdx));
}
} else if (getCurrent() == null) {
currIdx = -1;
setColorFilter(null);
}
return result;
}
private ColorFilter getColorFilterForIdx(int idx) {
return filterMap != null ? filterMap.get(idx) : null;
}
}
Я открыл ошибку об этом: https://code.google.com/p/android/issues/detail?id=60183
ОБНОВЛЕНИЕ: ошибка была исправлена в рамках, начиная с Lollipop, я думаю. Я думаю, что фиксация исправления заключается в следующем: https://android.googlesource.com/platform/frameworks/base/+/729427d%5E!/
или на Github: https://github.com/android/platform_frameworks_base/commit/729427d451bc4d4d268335b8dc1ff6404bc1c91e
Мой обходной путь должен все еще работать после Lollipop, он просто не использует исправление от Google.
Вот мой вариант кода @Malachiasz, он позволяет вам выбрать любую комбинацию состояний и цветов для применения к основному рисунку.
public class ColorFilteredStateDrawable extends StateListDrawable {
private final int[][] states;
private final int[] colors;
public ColorFilteredStateDrawable(Drawable drawable, int[][] states, int[] colors) {
super();
drawable.mutate();
this.states = states;
this.colors = colors;
for (int i = 0; i < states.length; i++) {
addState(states[i], drawable);
}
}
@Override
protected boolean onStateChange(int[] states) {
if (this.states != null) {
for (int i = 0; i < this.states.length; i++) {
if (StateSet.stateSetMatches(this.states[i], states)) {
super.setColorFilter(this.colors[i], PorterDuff.Mode.MULTIPLY);
return super.onStateChange(states);
}
}
super.clearColorFilter();
}
return super.onStateChange(states);
}
@Override
public boolean isStateful() {
return true;
}
}
Вот мой вариант кода Моппера. Идея состоит в том, что ImageView получает цветной фильтр, когда пользователь прикасается к нему, и цветной фильтр удаляется, когда пользователь перестает прикасаться к нему.
class PressedEffectStateListDrawable extends StateListDrawable {
private int selectionColor;
public PressedEffectStateListDrawable(Drawable drawable, int selectionColor) {
super();
this.selectionColor = selectionColor;
addState(new int[] { android.R.attr.state_pressed }, drawable);
addState(new int[] {}, drawable);
}
@Override
protected boolean onStateChange(int[] states) {
boolean isStatePressedInArray = false;
for (int state : states) {
if (state == android.R.attr.state_pressed) {
isStatePressedInArray = true;
}
}
if (isStatePressedInArray) {
super.setColorFilter(selectionColor, PorterDuff.Mode.MULTIPLY);
} else {
super.clearColorFilter();
}
return super.onStateChange(states);
}
@Override
public boolean isStateful() {
return true;
}
}
использование:
Drawable drawable = new FastBitmapDrawable(bm);
imageView.setImageDrawable(new PressedEffectStateListDrawable(drawable, 0xFF33b5e5));