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 .

Я не вижу, что я делаю неправильно, поэтому любая помощь будет оценена по достоинству.

Хорошо, я никогда не получал вышеуказанный код, чтобы работать, поэтому вот что я в итоге сделал.

Во-первых, я подклассифицировал 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.

Не удалось решить проблему, применив фильтр colorfilter непосредственно к чертежу. То, что сработало для меня, – это получить изображение в виде растрового изображения, создать пустую вторую с теми же мерами, определить холст для второго, применить этот цветной фильтр к объекту рисования и нарисовать первый растровый рисунок на втором. Наконец, создайте BitmapDrawable из нового растрового изображения, и все готово. Вот код

  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, он связан с ним при добавлении состояния в селектор Drawable.

Важно то, что

  • Вам нужно создать новый, вытягиваемый из постоянного состояния, или вы измените состояние константы и, таким образом, любой экземпляр этого объекта, который можно использовать для вашей деятельности.
  • Вам нужно использовать свой собственный метод addState, он имеет то же имя метода framework 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.

Вот мой вариант кода Моппера. Идея заключается в том, что 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)); 

Вот мой вариант кода @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; } }