Im trying to change the color of a TextView Drawable in Xamarin.
In Java you can do it like this:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TextView txt = (TextView) findViewById(R.id.my_textview);
setTextViewDrawableColor(txt, R.color.my_color);
}
private void setTextViewDrawableColor(TextView textView, int color) {
for (Drawable drawable : textView.getCompoundDrawables()) {
if (drawable != null) {
drawable.setColorFilter(new PorterDuffColorFilter(getColor(color), PorterDuff.Mode.SRC_IN));
}
}
}
How i can do something like this in Xamarin.Android?
Try below solution
private void setTextViewDrawableColor(TextView textView, int color) {
for (Drawable drawable : textView.getCompoundDrawables()) {
if (drawable != null) {
drawable.setColorFilter(new PorterDuffColorFilter(ContextCompat.getColor(textView.getContext(), color), PorterDuff.Mode.SRC_IN));
}
}
}
I am using this in kotlin:
tv.getCompoundDrawables()[0].setTint(//color)
Please, notice that if you set drawables in your layout file via android:drawableStart or android:drawableEnd instead of android:drawableLeft and android:drawableRight respectively you should use TextView.getCompoundDrawablesRelative(). Otherwise you can get empty array of drawables.
private void setTextViewDrawableColor(TextView textView, int color) {
for (Drawable drawable : textView.getCompoundDrawablesRelative()) {
if (drawable != null) {
drawable.setColorFilter(new PorterDuffColorFilter(ContextCompat.getColor(textView.getContext(), color), PorterDuff.Mode.SRC_IN));
}
}
}
// index of drawable
val left = 0
val start = left
val top = 1
val right = 2
val end = right
val bottm = 3
// color int
val color = Color.RED
// apply tint for target drawable
textView.compoundDrawables.getOrNull(left)?.setTint(color)
// apply tint for all drawables
textView.compoundDrawables?.forEach { it?.setTint(color) }
NOTE!
if in XML layout you use android:stratDrawable or android:endDrawable you have to work with textView.compoundDrawablesRelative array, textView.compoundDrawables contains drawables when they have been added with android:leftDrawable or android:rightDrawable attributes.
I solve this problem adding in xml definition this line:
android:drawableTint="#color/red"
A complete example:
<TextView
android:id="#+id/tv_element"
android:layout_width="wrap_content"
android:layout_height="20dp"
android:layout_alignParentEnd="true"
android:drawableStart="#drawable/ic_icon"
android:drawableTint="#color/color"
android:visibility="visible" />
There's built in support for this through TextViewCompat.setCompoundDrawableTintList(textView, colors)
val color = ContextCompat.getColor(context, R.color.foo)
val colorList = ColorStateList.valueOf(color)
TextViewCompat.setCompoundDrawableTintList(textView, colorList)
If you want to change the tint color of the drawable of any view (tested on API 29) :
private fun setTintColor(textView: TextView, color: Int) {
DrawableCompat.setTint(DrawableCompat.wrap(textView.background).mutate(),
ContextCompat.getColor(this, color))
}
For Kotlin. Use the below extension for the TextView drawable. It supports below and above API level of 23.
private fun TextView.setTextViewDrawableColor(color: Int) {
for (drawable in this.compoundDrawablesRelative) {
drawable?.mutate()
drawable?.colorFilter = PorterDuffColorFilter(
color, PorterDuff.Mode.SRC_IN
)
}
}
Note: You can also use this function in the RecyclerView item as well, It will not override the same color for each item
I faced the problem where I am changing the color of the compound drawable and except one rest of all the colors are constant. Confusing for me.!!!
the solution that worked for me is
// Pass through the each drawable and update the tint if drawable is set.
textView.compoundDrawables.filterNotNull().forEach { drawable ->
drawable.mutate()
drawable.setTint(drawableColor)
}
Without the mutate(), the things were working partially. I got the more details here Drawable Mutations .
For the interest of the reader, I am providing a quick summery below.
Android Drawables are the drawing containers. Such as BitmapDrawable is used to display the images, ShapeDrawable is used to display the shapes and gradients.
Drawables are used extensively in the Android ecosystem thus they are optimized. So when views are created the different instances are spawned but the drawables associated with the view share the common state, called "Constant state". For example, if a drawable is a BitmapDrawable then the same bitmap is used with the all the corresponding copies or views. Advantage: It simply saves huge amount of memory.
Problem: As the same drawable is shared across the various views. Any change in the state of the drawable such as alpha, transformation etc. will impact all the places where it is used.
Solution: The mutate() method when called on a drawable, the constant state of the drawable is duplicated to allow you to change any property without affecting other drawables. Note: Bitmap is still shared.
Related
I get an array of strings from my server using a volley connection. Every single string contain a different color in hex. I use this color to set Tint of a drawable in adapter.
Here my code in adapter:
#Override
public void onBindViewHolder(#NonNull final ViewHolder holder, final int position) {
// Get item from position
MyObject object = array_data.get(position);
...
...
Drawable unwrappedDrawable = AppCompatResources.getDrawable(context, R.drawable.ic_beenhere_black_24dp);
Drawable wrappedDrawable;
if (unwrappedDrawable != null) {
wrappedDrawable = DrawableCompat.wrap(unwrappedDrawable);
DrawableCompat.setTint(wrappedDrawable, object.getMyColor());
holder.imvPreparationTime.setImageDrawable(wrappedDrawable);
}
Unfortunately the behavior is not correct. All drawable of items in recyclerview have the same color together and it change for all during scroll.
How can I perform my goal? I want that every items have his own color and not change.
This can be done using Drawable.mutate() . In your adapter class, onBindViewHolder(..) block, use below code snippet to change the tint color of your drawable -
for (Drawable drawable : myTextView.getCompoundDrawablesRelative()) {
if (drawable != null) {
Drawable wrappedDrawable = DrawableCompat.wrap(drawable);
Drawable mutableDrawable = wrappedDrawable.mutate();
DrawableCompat.setTint(mutableDrawable, ContextCompat.getColor(context, R.color.desiredColor));
}
}
Note : This code snippet I've used to change the tint of textview's drawable. So, if you required to change the tint of image or drawable file, simply do it as :
Drawable wrappedDrawable = DrawableCompat.wrap(drawable);
Drawable mutableDrawable = wrappedDrawable.mutate();
DrawableCompat.setTint(mutableDrawable, ContextCompat.getColor(context, R.color.colorGrayD5));
Happy Coding!
Since recyclerView is reusing items there is often such a behaviour. Easiest way is to put if else for view you want to set tint for.
E.g.
if (unwrappedDrawable != null) {
wrappedDrawable = DrawableCompat.wrap(unwrappedDrawable);
DrawableCompat.setTint(wrappedDrawable, object.getMyColor());
holder.imvPreparationTime.setImageDrawable(wrappedDrawable);
} else {
holder.imvPreparationTime.setImageDrawable(<Some Other drawable, for example default one>);
}
The idea is to force recycler view draw something on item and not to reuse already set one.
I'm trying to bring material text selection handles to my app. I got drawables from the SDK for middle/right/left handle (bitmaps) and text cursor (9-patch), and set:
<item name="android:textSelectHandleLeft">#drawable/text_select_handle_left_mtrl_alpha</item>
<item name="android:textSelectHandleRight">#drawable/text_select_handle_right_mtrl_alpha</item>
<item name="android:textSelectHandle">#drawable/text_select_handle_middle_mtrl_alpha</item>
<item name="android:textCursorDrawable">#drawable/text_cursor_mtrl_alpha</item>
It works as expected. However, in Lollipop these drawables are tinted with a particular color in XML using the android:tint attribute, which I can't use on API<21. So I'm trying to set a color filter at runtime.
Text cursor does not get tinted. I think this might be due to it being a 9 patch. How can a 9-patch drawable be filtered at runtime? I tried probably all of PorterDuff.Modes.
Right/left handles are black, while middle handle is white.
I.e., non of them is green as I would like. Why?
As you can see above, I set up four ImageView below my edit text, and they instead get tinted.
private void setUpTextCursors() {
Drawable left = getResources().getDrawable(R.drawable.text_select_handle_left_mtrl_alpha);
Drawable right = getResources().getDrawable(R.drawable.text_select_handle_right_mtrl_alpha);
Drawable middle = getResources().getDrawable(R.drawable.text_select_handle_middle_mtrl_alpha);
Drawable cursor = getResources().getDrawable(R.drawable.text_cursor_mtrl_alpha);
ColorFilter cf = new PorterDuffColorFilter(mGreenColor, PorterDuff.Mode.SRC_IN);
/**
* tint my ImageViews, but no effect on edit text handles
*/
left.setColorFilter(cf);
right.setColorFilter(cf);
middle.setColorFilter(cf);
/**
* no effect whatsoever
*/
cursor.setColorFilter(cf);
}
Looks like here we have both a 9-patch tinting issue - since filter fails even on test ImageViews - and an issue related to the fact that none of the applied filters get considered by the text selection manager.
Relevant source code about that is from the TextView class and from this Editor hidden helper class which I found somehow. Spent some time on it but still can't tell why my filters are ignored.
To #pskink: let cursor be the filtered drawable, I can have:
<ImageView
android:id="#id/1"
android:src="#drawable/cursor_drawable" />
<ImageView
android:id="#id/2" />
The first won't be tinted, but if I call imageView2.setBackground(cursor), then it's tinted.
Also if I have
<item name="android:textSelectHandle">#drawable/cursor_drawable</item>
this affects the edit selection (because I override the default cursor) but it's not tinted, again.
you need to override the default Resources used by your Activity:
// your activity source file
Resources res;
#Override
public Resources getResources() {
if (res == null) {
res = new TintResources(super.getResources());
}
return res;
}
the custom Resources class will override getDrawable() method so you can intercept creating your Drawables and set up the color filter, for example:
class TintResources extends Resources {
public TintResources(Resources resources) {
super(resources.getAssets(), resources.getDisplayMetrics(), resources.getConfiguration());
}
#Override
public Drawable getDrawable(int id) throws NotFoundException {
Drawable d = super.getDrawable(id);
if (id == R.drawable.text_cursor_material) {
// setup #drawable/text_cursor_material
d.setColorFilter(0xff00aa00, PorterDuff.Mode.SRC_IN);
}
return d;
}
}
the same way you can setup other Drawables (#drawable/text_select_handle_*_material), note you need that not direct way since EditText doesn't have getter methods for accessing those Drawables
This is just a partial answer, and we can also consider it quite bad, since it's a workaround. I was able to load just the handles (i.e., the BitmapDrawables) inside the edittext (or any other selection stuff) by pointing at XML files rather than at raw png files. I.e. I set:
<item name="android:textSelectHandleLeft">#drawable/text_select_handle_left_material</item>
<item name="android:textSelectHandleRight">#drawable/text_select_handle_right_material</item>
<item name="android:textSelectHandle">#drawable/text_select_handle_middle_material</item>
<item name="android:textCursorDrawable">#drawable/text_cursor_material</item>
where these are xml drawables like:
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="#drawable/text_select_handle_left_mtrl_alpha" />
or
<nine-patch xmlns:android="http://schemas.android.com/apk/res/android"
android:src="#drawable/text_cursor_mtrl_alpha" />
If I filter these drawables, I found them tinted both in views and in selections. So I altered my method like such:
private void setUpTextCursors() {
ColorFilter cf = new PorterDuffColorFilter(mColorControlActivated, PorterDuff.Mode.SRC_IN);
BitmapDrawable left = (BitmapDrawable) getResources().getDrawable(R.drawable.text_select_handle_left_material);
BitmapDrawable middle = (BitmapDrawable) getResources().getDrawable(R.drawable.text_select_handle_middle_material);
BitmapDrawable right = (BitmapDrawable) getResources().getDrawable(R.drawable.text_select_handle_right_material);
// NinePatchDrawable cursor = (NinePatchDrawable) getResources().getDrawable(R.drawable.text_cursor_material);
left.setColorFilter(cf);
right.setColorFilter(cf);
middle.setColorFilter(cf);
// cursor.setColorFilter(cf); this does not work: cursor still white!
}
However, while this works for left, right, and middle, something is still wrong with the 9-patch cursor, because I can't get it tinted.
I am editing to make the question simpler, hoping that helps towards an accurate answer.
Say I have the following oval shape:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
<solid android:angle="270"
android:color="#FFFF0000"/>
<stroke android:width="3dp"
android:color="#FFAA0055"/>
</shape>
How do I set the color programmatically, from within an activity class?
Note: Answer has been updated to cover the scenario where background is an instance of ColorDrawable. Thanks Tyler Pfaff, for pointing this out.
The drawable is an oval and is the background of an ImageView
Get the Drawable from imageView using getBackground():
Drawable background = imageView.getBackground();
Check against usual suspects:
if (background instanceof ShapeDrawable) {
// cast to 'ShapeDrawable'
ShapeDrawable shapeDrawable = (ShapeDrawable) background;
shapeDrawable.getPaint().setColor(ContextCompat.getColor(mContext,R.color.colorToSet));
} else if (background instanceof GradientDrawable) {
// cast to 'GradientDrawable'
GradientDrawable gradientDrawable = (GradientDrawable) background;
gradientDrawable.setColor(ContextCompat.getColor(mContext,R.color.colorToSet));
} else if (background instanceof ColorDrawable) {
// alpha value may need to be set again after this call
ColorDrawable colorDrawable = (ColorDrawable) background;
colorDrawable.setColor(ContextCompat.getColor(mContext,R.color.colorToSet));
}
Compact version:
Drawable background = imageView.getBackground();
if (background instanceof ShapeDrawable) {
((ShapeDrawable)background).getPaint().setColor(ContextCompat.getColor(mContext,R.color.colorToSet));
} else if (background instanceof GradientDrawable) {
((GradientDrawable)background).setColor(ContextCompat.getColor(mContext,R.color.colorToSet));
} else if (background instanceof ColorDrawable) {
((ColorDrawable)background).setColor(ContextCompat.getColor(mContext,R.color.colorToSet));
}
Note that null-checking is not required.
However, you should use mutate() on the drawables before modifying them if they are used elsewhere. (By default, drawables loaded from XML share the same state.)
A simpler solution nowadays would be to use your shape as a background and then programmatically change its color via:
view.background.setColorFilter(Color.parseColor("#343434"), PorterDuff.Mode.SRC_ATOP)
See PorterDuff.Mode for the available options.
UPDATE (API 29):
The above method is deprecated since API 29 and replaced by the following:
view.background.colorFilter = BlendModeColorFilter(Color.parseColor("#343434"), BlendMode.SRC_ATOP)
See BlendMode for the available options.
Do like this:
ImageView imgIcon = findViewById(R.id.imgIcon);
GradientDrawable backgroundGradient = (GradientDrawable)imgIcon.getBackground();
backgroundGradient.setColor(getResources().getColor(R.color.yellow));
This question was answered a while back, but it can modernized by rewriting as a kotlin extension function.
fun Drawable.overrideColor(#ColorInt colorInt: Int) {
when (this) {
is GradientDrawable -> setColor(colorInt)
is ShapeDrawable -> paint.color = colorInt
is ColorDrawable -> color = colorInt
}
}
Try this:
public void setGradientColors(int bottomColor, int topColor) {
GradientDrawable gradient = new GradientDrawable(Orientation.BOTTOM_TOP, new int[]
{bottomColor, topColor});
gradient.setShape(GradientDrawable.RECTANGLE);
gradient.setCornerRadius(10.f);
this.setBackgroundDrawable(gradient);
}
for more detail check this link this
hope help.
hope this will help someone with the same issue
GradientDrawable gd = (GradientDrawable) YourImageView.getBackground();
//To shange the solid color
gd.setColor(yourColor)
//To change the stroke color
int width_px = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, youStrokeWidth, getResources().getDisplayMetrics());
gd.setStroke(width_px, yourColor);
Expanding on Vikram's answer, if you are coloring dynamic views, like recycler view items, etc.... Then you probably want to call mutate() before you set the color. If you don't do this, any views that have a common drawable (i.e a background) will also have their drawable changed/colored.
public static void setBackgroundColorAndRetainShape(final int color, final Drawable background) {
if (background instanceof ShapeDrawable) {
((ShapeDrawable) background.mutate()).getPaint().setColor(color);
} else if (background instanceof GradientDrawable) {
((GradientDrawable) background.mutate()).setColor(color);
} else if (background instanceof ColorDrawable) {
((ColorDrawable) background.mutate()).setColor(color);
}else{
Log.w(TAG,"Not a valid background type");
}
}
this is the solution that works for me...wrote it in another question as well:
How to change shape color dynamically?
//get the image button by id
ImageButton myImg = (ImageButton) findViewById(R.id.some_id);
//get drawable from image button
GradientDrawable drawable = (GradientDrawable) myImg.getDrawable();
//set color as integer
//can use Color.parseColor(color) if color is a string
drawable.setColor(color)
Nothing work for me but when i set tint color it works on Shape Drawable
Drawable background = imageView.getBackground();
background.setTint(getRandomColor())
require android 5.0 API 21
My Kotlin extension function version based on answers above with Compat:
fun Drawable.overrideColor_Ext(context: Context, colorInt: Int) {
val muted = this.mutate()
when (muted) {
is GradientDrawable -> muted.setColor(ContextCompat.getColor(context, colorInt))
is ShapeDrawable -> muted.paint.setColor(ContextCompat.getColor(context, colorInt))
is ColorDrawable -> muted.setColor(ContextCompat.getColor(context, colorInt))
else -> Log.d("Tag", "Not a valid background type")
}
}
The simple way to fill the shape with the Radius is:
(view.getBackground()).setColorFilter(Color.parseColor("#FFDE03"), PorterDuff.Mode.SRC_IN);
May be I am too late.But if you are using Kotlin. There is way like this
var gd = layoutMain.background as GradientDrawable
//gd.setCornerRadius(10)
gd.setColor(ContextCompat.getColor(ctx , R.color.lightblue))
gd.setStroke(1, ContextCompat.getColor(ctx , R.color.colorPrimary)) // (Strokewidth,colorId)
Enjoy....
This might help
1.Set the shape color initially to transparent
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
<solid android:angle="270"
android:color="#android:color/transparent"/>
<stroke android:width="3dp"
android:color="#FFAA0055"/>
</shape>
Set the shape as a background to the view
Set your preferred color as follows:
Drawable bg = view.getBackground();
bg.setColorFilter(Color.parseColor("#Color"), PorterDuff.Mode.ADD);
I needed to do this in my adapter but the solutions above were either not working or required >= android version 10. The code below worked for me!
val drawable = DrawableCompat.wrap(holder.courseName.background)
DrawableCompat.setTint(drawable, Color.parseColor("#4a1f60"))
For anyone using C# Xamarin, here is a method based on Vikram's snippet:
private void SetDrawableColor(Drawable drawable, Android.Graphics.Color color)
{
switch (drawable)
{
case ShapeDrawable sd:
sd.Paint.Color = color;
break;
case GradientDrawable gd:
gd.SetColor(color);
break;
case ColorDrawable cd:
cd.Color = color;
break;
}
}
The Best way to change Solid color of custom drawable is
For Kotlin.
(findViewById<TextView>(R.id.testing1).getBackground()).setColorFilter(Color.parseColor("#FFDE03"), PorterDuff.Mode.SRC_IN);
We can create this kotlin function.
fun View.updateViewBGSolidColor(colorString: String) {
when (val background: Drawable = this.background) {
is ShapeDrawable -> {
background.paint.color = Color.parseColor(colorString)
}
is GradientDrawable -> {
background.setColor(Color.parseColor(colorString))
}
is ColorDrawable -> {
background.color = Color.parseColor(colorString)
}
}
}
And use it like the below:
yourTextView.updateViewBGSolidColor("#FFFFFF")
GradientDrawable gd = new GradientDrawable(
GradientDrawable.Orientation.TOP_BOTTOM,
new int[] {0xFF616261,0xFF131313});
gd.setCornerRadius(0f);
layout.setBackgroundDrawable(gd);
I'm create some calendar view and what I want to do is to create a background for a LineairLayout that is clickabe.
Therefore I create a StateListDrawable with two images:
The image for the background
The image when the item has been pressed
So far that works with this piece of code:
NinePatchDrawable background = (NinePatchDrawable) context.getResources().getDrawable(R.drawable.calendar_item);
Drawable backgroundFocus = context.getResources().getDrawable(R.drawable.calendar_focus);
int stateFocused = android.R.attr.state_focused;
int statePressed = android.R.attr.state_pressed;
StateListDrawable sld = new StateListDrawable();
sld.addState(new int[]{ stateFocused, statePressed}, backgroundFocus);
sld.addState(new int[]{-stateFocused, statePressed}, backgroundFocus);
sld.addState(new int[]{-stateFocused}, background);
return sld;
But I would like to do something extra. I'dd like the user to be able to pass in a color that he wants to use to display the background. So the background var must be variable, but it must be based on the nine-patch drawable.
So I thought I could just do something like this:
background.setColorFilter(Color.RED, PorterDuff.Mode.DST_IN);
Where Color.RED must be replaced by the color of choice of the user.
But that doesn't seem to be working. The nine-patch is created perfectly but without the color fiilter being applied.
I also tried other PoterDuff.Mode 's:
SRC
SRC_ATOP
DST_IN
...
If you have any clue what I'm doing wrong or what I could do else to solve my issue please let me know! :-)
Kr,
Dirk
I don't think you can assign ColorFilters for each Drawable in a StateListDrawable. Reason: The ColorFilter will be removed/replaced when the StateListDrawable changes state. To see this in action, change the order of the statements such that:
background.setColorFilter(Color.RED, PorterDuff.Mode.DST_IN);
comes after the creation of the StateListDrawable. You'll see that the ColorFilter IS applied. But, as soon as the state changes(click, then release), the ColorFilter isn't there any more.
StateListDrawables allow you to set a ColorFilter: StateListDrawable#setColorFilter(ColorFilter). This is how the supplied (or null) ColorFilter is used:
StateListDrawable#onStateChange(int[]):
#Override
protected boolean onStateChange(int[] stateSet) {
....
if (selectDrawable(idx)) { // DrawableContainer#selectDrawable(int)
return true;
}
....
}
DrawableContainer#selectDrawable(int):
public boolean selectDrawable(int idx) {
....
if (idx >= 0 && idx < mDrawableContainerState.mNumChildren) {
Drawable d = mDrawableContainerState.mDrawables[idx];
mCurrDrawable = d;
mCurIndex = idx;
if (d != null) {
....
// So, at this stage, any ColorFilter you might have supplied
// to `d` will be replaced by the ColorFilter you
// supplied to the StateListDrawable, or `null`
// if you didn't supply any.
d.setColorFilter(mColorFilter);
....
}
} else {
....
}
}
Workaround:
If at all a possible, use an ImageView (match_parent for dimensions) for visual communication. Set the StateListDrawable that you've created as the ImageView's background. Create another StateListDrawable for the overlay:
StateListDrawable sldOverlay = new StateListDrawable();
// Match Colors with states (and ultimately, Drawables)
sldOverlay.addState(new int[] { statePressed },
new ColorDrawable(Color.TRANSPARENT));
sldOverlay.addState(new int[] { -statePressed },
new ColorDrawable(Color.parseColor("#50000000")));
// Drawable that you already have
iv1.setBackground(sld);
// Drawable that you just created
iv1.setImageDrawable(sldOverlay);
Another possibility: use a FrameLayout in place of LinearLayout. LinearLayouts do not have a foreground property.
// StateListDrawable
frameLayout.setBackground(sld);
// For tint
frameLayout.setForeground(sldOverlay);
It does involve overdraw, making it a sub-optimal solution/workaround. Perhaps you can look at extending StateListDrawable and DrawableContainer. And since you are not using a ColorFilter for the StateListDrawable, you can remove d.setColorFilter(mColorFilter); from overridden DrawableContainer#selectDrawable(int).
I'm trying to make a general framework function that makes any Drawable become highlighted when pressed/focused/selected/etc.
My function takes a Drawable and returns a StateListDrawable, where the default state is the Drawable itself, and the state for android.R.attr.state_pressed is the same drawable, just with a filter applied using setColorFilter.
My problem is that I can't clone the drawable and make a separate instance of it with the filter applied. Here is what I'm trying to achieve:
StateListDrawable makeHighlightable(Drawable drawable)
{
StateListDrawable res = new StateListDrawable();
Drawable clone = drawable.clone(); // how do I do this??
clone.setColorFilter(0xFFFF0000, PorterDuff.Mode.MULTIPLY);
res.addState(new int[] {android.R.attr.state_pressed}, clone);
res.addState(new int[] { }, drawable);
return res;
}
If I don't clone then the filter is obviously applied to both states. I tried playing with mutate() but it doesn't help..
Any ideas?
Update:
The accepted answer indeed clones a drawable. It didn't help me though because my general function fails on a different problem. It seems that when you add a drawable to a StateList, it loses all its filters.
Try the following:
Drawable clone = drawable.getConstantState().newDrawable();
If you apply a filter / etc to a drawable created with getConstantState().newDrawable() then all instances of that drawable will be changed as well, since drawables use the constantState as a cache!
So if you color a circle using a color filter and a newDrawable(), you will change the color of all the circles.
If you want to make this drawable updatable without affecting other instances then, then you must mutate that existing constant state.
// To make a drawable use a separate constant state
drawable.mutate()
For a good explanation see:
http://www.curious-creature.org/2009/05/02/drawable-mutations/
http://developer.android.com/reference/android/graphics/drawable/Drawable.html#mutate()
This is what works for me.
Drawable clone = drawable.getConstantState().newDrawable().mutate();
This is my solution, based on this SO question.
The idea is that ImageView gets color filter when user touches it, and color filter is removed when user stops touching it. Only 1 drawable/bitmap is in memory, so no need to waste it. It works as it should.
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;
}
}
usage:
Drawable drawable = new FastBitmapDrawable(bm);
imageView.setImageDrawable(new PressedEffectStateListDrawable(drawable, 0xFF33b5e5));
I answered a related question here
Basically it seems like StateListDrawables indeed lose their filters. I created a new BitmapDrawale from a altered copy of the Bitmap I originally wanted to use.
Get clone drawable using newDrawable() but make sure it is mutable otherwise your clone effect gone, I used these few lines of code and it is working as expected. getConstantState() may be null as suggested by annotation, so handle this RunTimeException while you cloning drawable.
Drawable.ConstantState state = d.mutate().getConstantState();
if (state != null) {
Drawable drawable = state.newDrawable().mutate();
}
Drawable clone = drawable.mutate().getConstantState().newDrawable().mutate();
in case getConstantState() returns null.