2012-03-10 14 views
56

Me gustaría ser capaz de utilizar la misma estirable para representar tanto:Modificar el color de un androide dibujable

Blue icon y Red icon

como el mismo estirable, y re-color de la dibujable basado en algunas valores programáticos, para que el usuario final pueda rediseñar la interfaz.

¿Cuál es la mejor manera de hacerlo? He intentado (y reutilizado los iconos de) this previous S.O. question pero no puedo representar el cambio como un simple cambio de matiz, ya que también varía en saturación y valor ..

Es mejor almacenar el icono como todo blanco en el área que quiero cambiar? o transparente? o algún otro color sólido?

¿Hay algún método que le permita determinar la matriz en función de la diferencia entre Color de red_icon y Color de blue_icon?

Respuesta

101

Así que después de un montón de intentos de prueba y error, leyendo diferentes artículos, y lo más importante, pasando por las demostraciones de API (ColorFilters.java, que se encuentra en com.example.android.apis.graphics) encontré la solución.

Para imágenes sólidas, he encontrado que es mejor usar el filtro de color PorterDuff.Mode.SRC_ATOP porque superpone el color en la parte superior de la imagen de origen, lo que le permite cambiar el color al color exacto que está buscando para.

Para las imágenes que son más complejas, como la de arriba, he encontrado que lo mejor que se puede hacer es colorear toda la imagen BLANCA (FFFFFF) para que cuando haga PorterDuff.Mode.MULTIPLY, termine con el colores correctos, y todo el negro (000000) en su imagen permanecerá negro.

El colorfilters.java muestra cómo se hace si el dibujo en un lienzo, pero si todo lo que necesita es para colorear un dibujable entonces esto va a trabajar:

COLOR2 = Color.parseColor("#FF"+getColor()); 
Mode mMode = Mode.SRC_ATOP; 
Drawable d = mCtx.getResources().getDrawable(R.drawable.image); 
d.setColorFilter(COLOR2,mMode) 

creé una actividad de demostración utilizando algunos de el código de demostración de API para cambiar entre cada modo de filtro de color para probarlos en diferentes situaciones y lo he encontrado invaluable, así que pensé que lo publicaría aquí.

public class ColorFilters extends GraphicsActivity { 

@Override 
protected void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 
    setContentView(new SampleView(this)); 

} 

private static class SampleView extends View { 
    private Activity mActivity; 
    private Drawable mDrawable; 
    private Drawable[] mDrawables; 
    private Paint mPaint; 
    private Paint mPaint2; 
    private float mPaintTextOffset; 
    private int[] mColors; 
    private PorterDuff.Mode[] mModes; 
    private int mModeIndex; 
    private Typeface futura_bold; 
    private AssetManager assets; 

    private static void addToTheRight(Drawable curr, Drawable prev) { 
     Rect r = prev.getBounds(); 
     int x = r.right + 12; 
     int center = (r.top + r.bottom) >> 1; 
     int h = curr.getIntrinsicHeight(); 
     int y = center - (h >> 1); 

     curr.setBounds(x, y, x + curr.getIntrinsicWidth(), y + h); 
    } 

    public SampleView(Activity activity) { 
     super(activity); 
     mActivity = activity; 
     Context context = activity; 
     setFocusable(true); 

     /**1. GET DRAWABLE, SET BOUNDS */ 
     assets = context.getAssets(); 
     mDrawable = context.getResources().getDrawable(R.drawable.roundrect_gray_button_bg_nine); 
     mDrawable.setBounds(0, 0, mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight()); 

     mDrawable.setDither(true); 

     int[] resIDs = new int[] { 
      R.drawable.roundrect_gray_button_bg, 
      R.drawable.order_button_white, 
      R.drawable.yellowbar 
     }; 
     mDrawables = new Drawable[resIDs.length]; 
     Drawable prev = mDrawable; 
     for (int i = 0; i < resIDs.length; i++) { 
      mDrawables[i] = context.getResources().getDrawable(resIDs[i]); 
      mDrawables[i].setDither(true); 
      addToTheRight(mDrawables[i], prev); 
      prev = mDrawables[i]; 
     } 

     /**2. SET Paint for writing text on buttons */ 
     mPaint = new Paint(); 
     mPaint.setAntiAlias(true); 
     mPaint.setTextSize(16); 
     mPaint.setTextAlign(Paint.Align.CENTER); 

     mPaint2 = new Paint(mPaint); 
     /** Calculating size based on font */ 
     futura_bold = Typeface.createFromAsset(assets, 
       "fonts/futurastd-bold.otf"); 
     //Determine size and offset to write text in label based on font size. 
     mPaint.setTypeface(futura_bold); 
     Paint.FontMetrics fm = mPaint.getFontMetrics(); 
     mPaintTextOffset = (fm.descent + fm.ascent) * 0.5f; 

     mColors = new int[] { 
      0, 
      0xFFA60017,//WE USE THESE 
      0xFFC6D405, 
      0xFF4B5B98, 
      0xFF656565, 
      0xFF8888FF, 
      0xFF4444FF, 
     }; 

     mModes = new PorterDuff.Mode[] { 
      PorterDuff.Mode.DARKEN, 
      PorterDuff.Mode.DST, 
      PorterDuff.Mode.DST_ATOP, 
      PorterDuff.Mode.DST_IN, 
      PorterDuff.Mode.DST_OUT, 
      PorterDuff.Mode.DST_OVER, 
      PorterDuff.Mode.LIGHTEN, 
      PorterDuff.Mode.MULTIPLY, 
      PorterDuff.Mode.SCREEN, 
      PorterDuff.Mode.SRC, 
      PorterDuff.Mode.SRC_ATOP, 
      PorterDuff.Mode.SRC_IN, 
      PorterDuff.Mode.SRC_OUT, 
      PorterDuff.Mode.SRC_OVER, 
      PorterDuff.Mode.XOR 
     }; 
     mModeIndex = 0; 

     updateTitle(); 
    } 

    private void swapPaintColors() { 
     if (mPaint.getColor() == 0xFF000000) { 
      mPaint.setColor(0xFFFFFFFF); 
      mPaint2.setColor(0xFF000000); 
     } else { 
      mPaint.setColor(0xFF000000); 
      mPaint2.setColor(0xFFFFFFFF); 
     } 
     mPaint2.setAlpha(0); 
    } 

    private void updateTitle() { 
     mActivity.setTitle(mModes[mModeIndex].toString()); 
    } 

    private void drawSample(Canvas canvas, ColorFilter filter) { 
     /** Create a rect around bounds, ensure size offset */ 
     Rect r = mDrawable.getBounds(); 
     float x = (r.left + r.right) * 0.5f; 
     float y = (r.top + r.bottom) * 0.5f - mPaintTextOffset; 

     /**Set color filter to selected color 
     * create canvas (filled with this color) 
     * Write text using paint (new color) 
     */ 
     mDrawable.setColorFilter(filter); 
     mDrawable.draw(canvas); 
     /** If the text doesn't fit in the button, make the text size smaller until it does*/ 
     final float size = mPaint.measureText("Label"); 
     if((int) size > (r.right-r.left)) { 
      float ts = mPaint.getTextSize(); 
      Log.w("DEBUG","Text size was"+ts); 
      mPaint.setTextSize(ts-2); 
     } 
     canvas.drawText("Sausage Burrito", x, y, mPaint); 
     /** Write the text and draw it onto the drawable*/ 

     for (Drawable dr : mDrawables) { 
      dr.setColorFilter(filter); 
      dr.draw(canvas); 
     } 
    } 

    @Override protected void onDraw(Canvas canvas) { 
     canvas.drawColor(0xFFCCCCCC);    

     canvas.translate(8, 12); 
     for (int color : mColors) { 
      ColorFilter filter; 
      if (color == 0) { 
       filter = null; 
      } else { 
       filter = new PorterDuffColorFilter(color, 
                mModes[mModeIndex]); 
      } 
      drawSample(canvas, filter); 
      canvas.translate(0, 55); 
     } 
    } 

    @Override 
    public boolean onTouchEvent(MotionEvent event) { 
     float x = event.getX(); 
     float y = event.getY(); 
     switch (event.getAction()) { 
      case MotionEvent.ACTION_DOWN: 
       break; 
      case MotionEvent.ACTION_MOVE: 
       break; 
      case MotionEvent.ACTION_UP: 
       // update mode every other time we change paint colors 
       if (mPaint.getColor() == 0xFFFFFFFF) { 
        mModeIndex = (mModeIndex + 1) % mModes.length; 
        updateTitle(); 
       } 
       swapPaintColors(); 
       invalidate(); 
       break; 
      } 
     return true; 
     } 
    } 
} 

Las dos otras dependencias, GraphicsActivity.java y PictureLayout.java, se pueden copiar directamente de la actividad Demos API si desea probarlo.

+0

Si mi dibujo tiene una forma con un color sólido de FFFFFFFF (ARGB) y quiero pintarlo con un color #CCCCCCCC (es decir, con un alfa), ¿cómo puedo hacerlo? Intenté usar el modo SRC_ATOP, pero parece que el alfa me está dando resultados diferentes – dowjones123

+3

@steve_gregory ¿Qué quiere decir con "colorear toda la imagen WHITE (FFFFFF)"? – fahmy

+5

No tengo muy claro cómo hacer lo que mencionaste, "colorea toda la imagen BLANCA (FFFFFF) para que cuando uses PorterDuff.Mode.MULTIPLY, termines con los colores correctos y todo el negro (000000)) en su imagen se mantendrá negro ". ¿Puedes dar un ejemplo de esto por casualidad? – Silmarilos

18

Esto es realmente fácil de hacer en Lollipop. Cree un xml dibujable y haga referencia a su png y establezca el tinte de esta manera:

<?xml version="1.0" encoding="utf-8"?> 
<bitmap 
    xmlns:android="http://schemas.android.com/apk/res/android" 
    android:src="@drawable/ic_back" 
    android:tint="@color/red_tint"/> 
+0

Tengo una lista de capas con algunos elementos. uno de ellos es un dibujable. ¿Cómo puedo configurar el tinte en un artículo? – Kenji

+0

Tendrá que traer el dibujable indirectamente. En el ejemplo, se dice que el src es dibujable. Luego, en su lista de capas, haría referencia a este mapa de bits dibujable en lugar de hacer referencia directamente al dibujo original. . – MinceMan

10

Su respuesta es muy buena. Si bien, esta solución es práctica también si estás utilizando un Textview y una dibujable incrustar:

int colorARGB = R.color.your_color; 
Drawable[] textviewDrawables = drawerItem.getCompoundDrawables(); 
// Left Drawable 
textviewDrawables[0].setColorFilter(colorARGB, PorterDuff.Mode.SRC_ATOP); 
1

Aquí es algo mejor, en mi humilde opinión, que la respuesta aceptada.Se deriva de este hilo StackOverflow: el uso Understanding the Use of ColorMatrix and ColorMatrixColorFilter to Modify a Drawable's Hue

Ejemplo:

ImageView imageView = ...; 
Drawable drawable = imageView.getDrawable(); 
ColorFilter colorFilter = ColorFilterGenerator.from(drawable).to(Color.RED); 
imageView.setColorFilter(colorFilter); 

Copiar la clase en su proyecto:

import android.graphics.Bitmap; 
import android.graphics.Canvas; 
import android.graphics.Color; 
import android.graphics.ColorFilter; 
import android.graphics.ColorMatrix; 
import android.graphics.ColorMatrixColorFilter; 
import android.graphics.drawable.BitmapDrawable; 
import android.graphics.drawable.Drawable; 
import android.graphics.drawable.PictureDrawable; 
import android.widget.ImageView; 

/** 
* Creates a {@link ColorMatrixColorFilter} to adjust the hue, saturation, brightness, or 
* contrast of an {@link Bitmap}, {@link Drawable}, or {@link ImageView}. 
* <p/> 
* Example usage: 
* <br/> 
* {@code imageView.setColorFilter(ColorFilterGenerator.from(Color.BLUE).to(Color.RED));} 
* 
* @author Jared Rummler <[email protected]> 
*/ 
public class ColorFilterGenerator { 

    // Based off answer from StackOverflow 
    // See: https://stackoverflow.com/a/15119089/1048340 

    private ColorFilterGenerator() { 
    throw new AssertionError(); 
    } 

    public static From from(Drawable drawable) { 
    return new From(drawableToBitmap(drawable)); 
    } 

    public static From from(Bitmap bitmap) { 
    return new From(bitmap); 
    } 

    public static From from(int color) { 
    return new From(color); 
    } 

    // -------------------------------------------------------------------------------------------- 

    private static final double DELTA_INDEX[] = { 
     0, 0.01, 0.02, 0.04, 0.05, 0.06, 0.07, 0.08, 0.1, 0.11, 0.12, 0.14, 0.15, 0.16, 0.17, 0.18, 
     0.20, 0.21, 0.22, 0.24, 0.25, 0.27, 0.28, 0.30, 0.32, 0.34, 0.36, 0.38, 0.40, 0.42, 0.44, 
     0.46, 0.48, 0.5, 0.53, 0.56, 0.59, 0.62, 0.65, 0.68, 0.71, 0.74, 0.77, 0.80, 0.83, 0.86, 0.89, 
     0.92, 0.95, 0.98, 1.0, 1.06, 1.12, 1.18, 1.24, 1.30, 1.36, 1.42, 1.48, 1.54, 1.60, 1.66, 1.72, 
     1.78, 1.84, 1.90, 1.96, 2.0, 2.12, 2.25, 2.37, 2.50, 2.62, 2.75, 2.87, 3.0, 3.2, 3.4, 3.6, 
     3.8, 4.0, 4.3, 4.7, 4.9, 5.0, 5.5, 6.0, 6.5, 6.8, 7.0, 7.3, 7.5, 7.8, 8.0, 8.4, 8.7, 9.0, 9.4, 
     9.6, 9.8, 10.0 
    }; 

    public static void adjustHue(ColorMatrix cm, float value) { 
    value = cleanValue(value, 180f)/180f * (float) Math.PI; 
    if (value == 0) { 
     return; 
    } 

    float cosVal = (float) Math.cos(value); 
    float sinVal = (float) Math.sin(value); 
    float lumR = 0.213f; 
    float lumG = 0.715f; 
    float lumB = 0.072f; 
    float[] mat = new float[]{ 
     lumR + cosVal * (1 - lumR) + sinVal * (-lumR), 
     lumG + cosVal * (-lumG) + sinVal * (-lumG), 
     lumB + cosVal * (-lumB) + sinVal * (1 - lumB), 0, 0, 
     lumR + cosVal * (-lumR) + sinVal * (0.143f), 
     lumG + cosVal * (1 - lumG) + sinVal * (0.140f), 
     lumB + cosVal * (-lumB) + sinVal * (-0.283f), 0, 0, 
     lumR + cosVal * (-lumR) + sinVal * (-(1 - lumR)), 
     lumG + cosVal * (-lumG) + sinVal * (lumG), 
     lumB + cosVal * (1 - lumB) + sinVal * (lumB), 0, 0, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 0f, 
     0f, 1f 
    }; 
    cm.postConcat(new ColorMatrix(mat)); 
    } 

    public static void adjustBrightness(ColorMatrix cm, float value) { 
    value = cleanValue(value, 100); 
    if (value == 0) { 
     return; 
    } 

    float[] mat = new float[]{ 
     1, 0, 0, 0, value, 0, 1, 0, 0, value, 0, 0, 1, 0, value, 0, 0, 0, 1, 0, 0, 0, 0, 0, 
     1 
    }; 
    cm.postConcat(new ColorMatrix(mat)); 
    } 

    public static void adjustContrast(ColorMatrix cm, int value) { 
    value = (int) cleanValue(value, 100); 
    if (value == 0) { 
     return; 
    } 
    float x; 
    if (value < 0) { 
     x = 127 + value/100 * 127; 
    } else { 
     x = value % 1; 
     if (x == 0) { 
     x = (float) DELTA_INDEX[value]; 
     } else { 
     x = (float) DELTA_INDEX[(value << 0)] * (1 - x) 
      + (float) DELTA_INDEX[(value << 0) + 1] * x; 
     } 
     x = x * 127 + 127; 
    } 

    float[] mat = new float[]{ 
     x/127, 0, 0, 0, 0.5f * (127 - x), 0, x/127, 0, 0, 0.5f * (127 - x), 0, 0, 
     x/127, 0, 0.5f * (127 - x), 0, 0, 0, 1, 0, 0, 0, 0, 0, 1 
    }; 
    cm.postConcat(new ColorMatrix(mat)); 

    } 

    public static void adjustSaturation(ColorMatrix cm, float value) { 
    value = cleanValue(value, 100); 
    if (value == 0) { 
     return; 
    } 

    float x = 1 + ((value > 0) ? 3 * value/100 : value/100); 
    float lumR = 0.3086f; 
    float lumG = 0.6094f; 
    float lumB = 0.0820f; 

    float[] mat = new float[]{ 
     lumR * (1 - x) + x, lumG * (1 - x), lumB * (1 - x), 0, 0, lumR * (1 - x), 
     lumG * (1 - x) + x, lumB * (1 - x), 0, 0, lumR * (1 - x), lumG * (1 - x), 
     lumB * (1 - x) + x, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1 
    }; 
    cm.postConcat(new ColorMatrix(mat)); 
    } 

    // -------------------------------------------------------------------------------------------- 

    private static float cleanValue(float p_val, float p_limit) { 
    return Math.min(p_limit, Math.max(-p_limit, p_val)); 
    } 

    private static float[] getHsv(int color) { 
    float[] hsv = new float[3]; 
    Color.RGBToHSV(Color.red(color), Color.green(color), Color.blue(color), hsv); 
    return hsv; 
    } 

    /** 
    * Converts a {@link Drawable} to a {@link Bitmap} 
    * 
    * @param drawable 
    *  The {@link Drawable} to convert 
    * @return The converted {@link Bitmap}. 
    */ 
    private static Bitmap drawableToBitmap(Drawable drawable) { 
    if (drawable instanceof BitmapDrawable) { 
     return ((BitmapDrawable) drawable).getBitmap(); 
    } else if (drawable instanceof PictureDrawable) { 
     PictureDrawable pictureDrawable = (PictureDrawable) drawable; 
     Bitmap bitmap = Bitmap.createBitmap(pictureDrawable.getIntrinsicWidth(), 
      pictureDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); 
     Canvas canvas = new Canvas(bitmap); 
     canvas.drawPicture(pictureDrawable.getPicture()); 
     return bitmap; 
    } 
    int width = drawable.getIntrinsicWidth(); 
    width = width > 0 ? width : 1; 
    int height = drawable.getIntrinsicHeight(); 
    height = height > 0 ? height : 1; 
    Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 
    Canvas canvas = new Canvas(bitmap); 
    drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); 
    drawable.draw(canvas); 
    return bitmap; 
    } 

    /** 
    * Calculate the average red, green, blue color values of a bitmap 
    * 
    * @param bitmap 
    *  a {@link Bitmap} 
    * @return 
    */ 
    private static int[] getAverageColorRGB(Bitmap bitmap) { 
    int width = bitmap.getWidth(); 
    int height = bitmap.getHeight(); 
    int size = width * height; 
    int[] pixels = new int[size]; 
    int r, g, b; 
    r = g = b = 0; 
    bitmap.getPixels(pixels, 0, width, 0, 0, width, height); 
    for (int i = 0; i < size; i++) { 
     int pixelColor = pixels[i]; 
     if (pixelColor == Color.TRANSPARENT) { 
     size--; 
     continue; 
     } 
     r += Color.red(pixelColor); 
     g += Color.green(pixelColor); 
     b += Color.blue(pixelColor); 
    } 
    r /= size; 
    g /= size; 
    b /= size; 
    return new int[]{ 
     r, g, b 
    }; 
    } 

    /** 
    * Calculate the average color value of a bitmap 
    * 
    * @param bitmap 
    *  a {@link Bitmap} 
    * @return 
    */ 
    private static int getAverageColor(Bitmap bitmap) { 
    int[] rgb = getAverageColorRGB(bitmap); 
    return Color.argb(255, rgb[0], rgb[1], rgb[2]); 
    } 

    // Builder 
    // -------------------------------------------------------------------------------------------- 

    public static final class Builder { 

    int hue; 

    int contrast; 

    int brightness; 

    int saturation; 

    public Builder setHue(int hue) { 
     this.hue = hue; 
     return this; 
    } 

    public Builder setContrast(int contrast) { 
     this.contrast = contrast; 
     return this; 
    } 

    public Builder setBrightness(int brightness) { 
     this.brightness = brightness; 
     return this; 
    } 

    public Builder setSaturation(int saturation) { 
     this.saturation = saturation; 
     return this; 
    } 

    public ColorFilter build() { 
     ColorMatrix cm = new ColorMatrix(); 
     adjustHue(cm, hue); 
     adjustContrast(cm, contrast); 
     adjustBrightness(cm, brightness); 
     adjustSaturation(cm, saturation); 
     return new ColorMatrixColorFilter(cm); 
    } 
    } 

    public static final class From { 

    final int oldColor; 

    private From(Bitmap bitmap) { 
     oldColor = getAverageColor(bitmap); 
    } 

    private From(int oldColor) { 
     this.oldColor = oldColor; 
    } 

    public ColorFilter to(int newColor) { 
     float[] hsv1 = getHsv(oldColor); 
     float[] hsv2 = getHsv(newColor); 
     int hue = (int) (hsv2[0] - hsv1[0]); 
     int saturation = (int) (hsv2[1] - hsv1[1]); 
     int brightness = (int) (hsv2[2] - hsv1[2]); 
     return new ColorFilterGenerator.Builder() 
      .setHue(hue) 
      .setSaturation(saturation) 
      .setBrightness(brightness) 
      .build(); 
    } 
    } 

} 
+0

No se puede resolver el método getHsv() en su publicación. Solo un aviso. – Silmarilos

+0

@ Silmarilos, gracias. Actualicé la respuesta. –

2

En caso si usted desea aplicar el filtro de color para su imagen en ImageView puede implementarla incluso de una manera más fácil. Simplemente use el atributo android:tint en ImageView en xml.

Ejemplo:

<ImageView 
    android:layout_width="wrap_content" 
    android:layout_height="wrap_content" 
    android:src="@drawable/your_drawable" 
    android:tint="@color/your_color" /> 

Probado en Android 4.1.2 y 6.0.1

+0

Una buena solución. Sin embargo, requiere API 21 y estoy apuntando a API 16, por lo que busco otra forma. – jk7

+0

@ jk7 ¿Por qué se requiere API 21? –

+1

Necesito establecer el color de fondo o el tinte programáticamente mediante un método como setTintList() o setImageTintList() para un ImageView o botón. Son esos métodos los que requieren API 21, así que recurrí al uso de DrawableCompat.setTintList(). – jk7

1

Esto es lo que hice después de mirar en la documentación

public PorterDuffColorFilter getDrawableFilter(){ 
     return new PorterDuffColorFilter(ContextCompat.getColor(this, R.color.color_black), PorterDuff.Mode.SRC_ATOP); 
    } 

y lo llamó

yourdrawable.setColorFilter(getDrawableFilter()); 
Cuestiones relacionadas