2011-05-16 12 views
10

Quiero crear botones personalizados para usar en un TabHost. He intentado simplemente usar el mismo recurso de imagen (png), pero he cambiado el filtro de color dependiendo del estado. Así que hice este bit para servir como la disposición para el botón personalizado:StateListDrawable para cambiar los filtros de color

<?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, añado las lengüetas de la siguiente manera:

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 de '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 ser completamente verde (0x0000FF00), y en el estado no seleccionado, debería ser azul (0x000000FF).

El problema es que los filtros de color parecen ser ignorados por completo. No puedo ver los colores cambian bajo ninguna circunstancia.

También he intentado conseguir el mismo resultado mediante el establecimiento de la propiedad android:tint en el <ImageView/>, pero al parecer no se puede utilizar una referencia a un <selector> allí, ya que arroja una NumberFormatException.

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

Respuesta

15

OK, nunca obtuve el código anterior para trabajar, así que esto es lo que terminé haciendo.

En primer lugar, una 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; 
    } 

} 

que 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 añadir las pestañas de la siguiente manera:

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

Estos trabajos para mí, compatible con Android 1.6.

+0

supongo que deseo hacer una imageView que podría tener el nuevo "StateDrawable" en xml, ¿cómo podría hacerlo? –

+6

Excelente respuesta, pero puedo sugerir una mejora. Existe el método 'StateSet.stateSetMatches', que comprueba si el estado dibuja coincide con el estado deseado o no (en lugar de iterar manualmente sobre el conjunto) –

10

No se pudo resolver aplicando un filtro de color directamente al dibujante tampoco. 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 crea un mapa de bits extraíble del nuevo mapa de bits y listo. 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); 
+0

¡Su solución funcionó para mí! – Merlevede

9

Ésta es mi clase, cortado a apoyar 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 a la estirable, se asocia a él mientras agrega un estado al selector Drawable.

Lo que es importante aquí es que

  • es necesario crear una nueva dibujable del estado constante o tendrá que modificar el estado constante y por lo tanto cualquier instancia de ese estirable alrededor de su actividad.
  • necesita usar mi método addState personalizado, tiene el mismo nombre que el método de marco addState, pero he agregado un argumento adicional (ColorFilter). ¡Este método NO existe en la superclase del marco!

El código (sucio, pero el trabajo 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 en esto: https://code.google.com/p/android/issues/detail?id=60183

ACTUALIZACIÓN: el error se ha corregido en el marco, ya Lollipop, creo. Creo que la solución cometer 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 debería seguir funcionando después de Lollipop, que simplemente no utilizar la corrección por parte de Google.

+0

Esto funcionó muy bien, sin embargo, sería conveniente especificar que no anuló addState pero creó una nueva función con el mismo nombre pero con tres parámetros; addState (estado, dibujable, filtro de color) – user1354603

+0

tiene razón, agregué una nota, gracias –

+0

Gracias, por cierto, creo que tiene razón acerca de que este error se corrigió en lollipop. Estoy usando su solución para dispositivos pre-lollipop y un RippleDrawable para dispositivos más nuevos. – user1354603

1

Aquí está mi variación del código de Mopper. La idea es que ImageView obtenga un filtro de color cuando el usuario lo toque, 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)); 
1

Aquí está mi variación de código @Malachiasz, esto le permite elegir cualquier combinación de estados y colores de aplicar a la base de 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; 
    } 
} 
Cuestiones relacionadas