StateListDrawable para cambiar filtros de color

Quiero crear botones personalizados para usar en un TabHost. No he estado tratando de utilizar el mismo recurso de imagen (png), pero tienen el colorfilter cambiar dependiendo del estado. Así que hice este pedacito para servir como la disposición para el botón de encargo:

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

En mi actividad, agrego las pestañas como esta:

 tabHost.addTab(tabHost.newTabSpec(TAB_NAME_NEWS).setIndicator(buildTab(R.drawable.tab_icon_news, R.string.news)) .setContent(newsIntent)); 

Y este es el método '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; } 

En el estado seleccionado, la imagen debe estar completamente verde ( 0x0000FF00 ), y en el estado no seleccionado, debe ser azul ( 0x000000FF ).

El problema es que los filtros de color parecen ser completamente ignorados. No puedo ver los colores cambiar en ninguna circunstancia.

También he intentado obtener el mismo resultado estableciendo la propiedad android:tint en el <ImageView/> , pero aparentemente no se puede usar una referencia a un <selector> allí, ya que lanza una NumberFormatException .

No veo lo que estoy haciendo mal, por lo que cualquier ayuda sería apreciada.

OK, nunca conseguí el código anterior para trabajar, así que aquí está lo que terminé haciendo.

Primero, subclase 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; } } 

Cambié el método buildTab() a lo siguiente:

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

Todavía agrego las pestañas como esta:

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

Esto funciona para mí, compatible con Android 1.6.

No se pudo resolver con la aplicación de un filtro de color directamente en el dibujable bien. Lo que funcionó para mí fue obtener la imagen como un mapa de bits, crear un segundo vacío con las mismas medidas, definir un lienzo para el segundo, aplicar ese filtro de color a un objeto de pintura y dibujar el primer mapa de bits en el segundo. Finalmente crear un BitmapDrawable desde el nuevo mapa de bits y ya está. Aquí está el código

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

Esta es mi clase, hackeada para apoyar a ColorFilter:

Uso:

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

Como se ve el ColorFilter no se aplica directamente al dibujable, se le asocia al agregar un estado al selector Drawable.

Lo importante aquí es que

  • Usted necesita crear un nuevo drawable del estado constante o usted modificará el estado constante y así cualquier instancia de eso dibujable alrededor de su actividad.
  • Necesitas usar mi método addState personalizado, tiene el mismo nombre del método framework addState pero he añadido un argumento adicional (ColorFilter). ¡Este método NO existe en la superclase de framework!

El código (sucio, pero funciona para mí):

 /** * 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; } } 

He abierto un error al respecto: https://code.google.com/p/android/issues/detail?id=60183

ACTUALIZACIÓN: el error ha sido arreglado en el marco, ya que Lollipop creo. Creo que la corrección es la siguiente: https://android.googlesource.com/platform/frameworks/base/+/729427d%5E!/

O en Github: https://github.com/android/platform_frameworks_base/commit/729427d451bc4d4d268335b8dc1ff6404bc1c91e

Mi solución aún debe funcionar después de Lollipop, simplemente no utilizar la corrección de Google.

Aquí está mi variación del código de Mopper. La idea es que ImageView obtiene el filtro de color cuando el usuario lo toca, y el filtro de color se elimina cuando el usuario deja de tocarlo.

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

uso:

 Drawable drawable = new FastBitmapDrawable(bm); imageView.setImageDrawable(new PressedEffectStateListDrawable(drawable, 0xFF33b5e5)); 

Aquí está mi variación del código de @Malachiasz, esto le permite escoger cualquier combinación de estados y colores para aplicar a la base dibujable.

 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; } } 
  • ¿Cómo repetir una tarea después de una cantidad fija de tiempo en android?
  • Android - MediaPlayer + PowerManager.PARTIAL_WAKE_LOCK
  • Java / Android: java.lang.OutOfMemoryError mientras se construye un objeto JSON
  • NumberFormatException en número válido Cadena
  • ¿Está bien para guardar una matriz JSON en SharedPreferences?
  • TabLayout se bloquea después de actualizar la biblioteca de soporte a 23.2.1
  • Ejecutar un solo archivo java con el método principal estándar (String args) - Android Studio
  • Cómo obtener nombres de paquetes en ejecución para Android L
  • Dagger 2 dependencias de componentes
  • Agregar objeto a ArrayList en índice especificado
  • ValueEventListener vs ChildEventListener para RecyclerView en Android
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.