2012-03-10 4 views
56

mi piacerebbe essere in grado di utilizzare lo stesso drawable per rappresentare sia:Modifica del colore di un androide drawable

Blue icon e Red icon

come lo stesso drawable, e ri-colore drawable sulla base di alcuni valori programmatici, in modo che l'utente finale possa ri-tema l'interfaccia.

Qual è il modo migliore per farlo? Ho provato (e riutilizzato le icone da) this previous S.O. question ma non posso rappresentare la modifica come un semplice cambio di tonalità, poiché varia anche in saturazione e valore.

È meglio memorizzare l'icona come tutto bianco nell'area voglio cambiare? o trasparente? o qualche altro colore solido?

C'è qualche metodo che ti permette di capire la matrice in base alla differenza tra Colore di red_icon e Colore di blue_icon?

risposta

101

Quindi dopo un sacco di tentativi ed errori, leggendo diversi articoli e, soprattutto, passando attraverso le demo API (ColorFilters.java - trovato in com.example.android.apis.graphics) ho trovato la soluzione.

Per immagini solide, ho trovato che è meglio utilizzare il filtro colore PorterDuff.Mode.SRC_ATOP perché sovrapporrà il colore sopra l'immagine di origine, consentendo di cambiare il colore al colore esatto che si sta cercando per.

Per immagini più complesse, come quella precedente, ho trovato che la cosa migliore da fare è colorare l'intera immagine WHITE (FFFFFF) in modo che quando si esegue PorterDuff.Mode.MULTIPLY, si finisce con i colori corretti e tutto il nero (000000) nella tua immagine rimarrà nero.

La colorfilters.java vi mostra come si fa se il disegno su una tela, ma se tutto ciò che serve è quello di colorare un drawable allora questo lavoro:

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

ho creato un'attività demo utilizzando alcune delle il codice demo API per passare da una modalità di filtro a ciascun colore per provarli per situazioni diverse e aver trovato che sia inestimabile, quindi ho pensato di pubblicarlo qui.

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

Le due altre dipendenze, GraphicsActivity.java e PictureLayout.java, possono essere copiati direttamente dalle API di attività Demo se volete provarlo.

+0

Se il mio drawable ha una forma con un colore solido di FFFFFFFF (ARGB) e desidera dipingerlo con un colore #CCCCCCCC (ad es. con un alfa), come posso farlo? Ho provato a utilizzare la modalità SRC_ATOP, ma sembra che l'alfa mi dia risultati diversi – dowjones123

+3

@steve_gregory Che cosa intendi per "colorare l'intera immagine WHITE (FFFFFF)"? – fahmy

+5

Non sono abbastanza chiaro su come si fa quello che hai citato, "colora l'intera immagine WHITE (FFFFFF) in modo che quando si esegue PorterDuff.Mode.MULTIPLY, si finisce con i colori corretti, e tutto il nero (000000) nella tua immagine rimarrà nero. " Puoi dare un esempio di questo per caso? – Silmarilos

18

Questo è veramente facile da fare su Lollipop. Crea un xml disegnabile e fai riferimento al tuo png e imposta la tinta in questo modo:

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

Ho una lista dei livelli con alcuni elementi. uno di questi è un drawable. come posso impostare la tinta in un oggetto? – Kenji

+0

Dovrai inserire il drawable indirettamente. Nell'esempio, lo src sarebbe detto drawable. Quindi nella lista dei livelli si farebbe riferimento a questo drawable bitmap invece di fare riferimento direttamente al drawable originale. . – MinceMan

10

La tua risposta è molto bella. Anche se, questa soluzione è pratica anche se si sta utilizzando un TextView e un disegnabile embed:

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

Ecco qualcosa di meglio, secondo me, che la risposta accettata.È derivato da questa discussione StackOverflow: utilizzo Understanding the Use of ColorMatrix and ColorMatrixColorFilter to Modify a Drawable's Hue

Esempio:

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

Copiare la classe nel progetto:

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

Impossibile risolvere il metodo getHsv() nel tuo post. Solo un avviso. – Silmarilos

+0

@ Silmarilos, grazie. Ho aggiornato la risposta. –

2

Nel caso in cui si desidera applicare il filtro colore per la tua immagine in ImageView puoi implementarla anche in modo più semplice. Basta usare l'attributo android:tint in ImageView in xml.

Esempio:

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

Testato su Android 4.1.2 e 6.0.1

+0

Una buona soluzione. Tuttavia, richiede l'API 21 e sto puntando all'API 16, quindi cerco un altro modo. – jk7

+0

@ jk7 Perché è richiesta l'API 21? –

+1

È necessario impostare il colore di sfondo o la tinta a livello di codice con un metodo come setTintList() o setImageTintList() per un ImageView o un pulsante. Sono quei metodi che richiedono l'API 21, quindi ho fatto ricorso a DrawableCompat.setTintList(). – jk7

1

Questo è quello che ho fatto dopo aver guardato nella documentazione

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

e lo ha chiamato

yourdrawable.setColorFilter(getDrawableFilter());