Changing color of single drawable in RecyclerView will change all drawables
I just tried to change the color of my drawable inside my row depending on a value but instead of one drawable the adapter changed all of them.
Here is my Adapter:
public class ReportAdapter extends RecyclerView.Adapter<ReportAdapter.ReportViewHolder> {
DataBaseHelper dataBase;
private LayoutInflater inflater;
List<ChoosedSubject> data = Collections.emptyList();
Context context;
OnItemClickListener itemClickListener;
public ReportAdapter(Context context, List<ChoosedSubject> data, OnItemClickListener itemClickListener) {
inflater = LayoutInflater.from(context);
this.data = data;
this.context = context;
this.itemClickListener = itemClickListener;
}
@Override
public ReportViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = inflater.inflate(R.layout.report_cell, parent, false);
ReportViewHolder holder = new ReportViewHolder(view);
dataBase = new DataBaseHelper(context);
return holder;
}
//Set Data inside RecyclerView
@Override
public void onBindViewHolder(ReportViewHolder holder, int position) {
ChoosedSubject current = data.get(position);
Grades grades = new Grades(context);
Resources resources = context.getResources();
int iconColor;
Drawable icon;
icon = ContextCompat.getDrawable(context, dataBase.getSpecificChoosedSubjectAppendingToName(current.getName()).get(0).getChoosedIcon());
if (dataBase.getSpecificChoosedSubjectAppendingToName(current.getName()).get(0).getChoosedIcon() != R.drawable.subject_default) {
iconColor = resources.getColor(dataBase.getSpecificChoosedSubjectAppendingToName(current.getName()).get(0).getChoosedColor());
icon.setColorFilter(iconColor, PorterDuff.Mode.SRC_IN);
holder.icon.setBackground(icon);
} else {
holder.icon.setImageResource(R.drawable.subject_default);
}
holder.subject.setText(current.getName().toString());
NumberFormat formatter = NumberFormat.getNumberInstance();
formatter.setMinimumFractionDigits(0);
formatter.setMaximumFractionDigits(0);
String output = formatter.format(dataBase.getSpecificChoosedSubjectAppendingToName(current.getName()).get(0).getAverage());
int formattedValue = Integer.valueOf(output);
//CHANGING COLOR DEPENDING ON VALUE
int boxColor = 0;
Drawable box = ContextCompat.getDrawable(context, R.drawable.markbox);
Drawable boxBorder = ContextCompat.getDrawable(context, R.drawable.markbox_border);
if (formattedValue >= 10) {
boxColor = resources.getColor(R.color.positive);
} else if (formattedValue >= 4 && formattedValue <= 9) {
boxColor = resources.getColor(R.color.neutral);
} else if (formattedValue < 4) {
boxColor = resources.getColor(R.color.negative);
}
box.setAlpha(204);
box.setColorFilter(boxColor, PorterDuff.Mode.SRC_IN);
boxBorder.setColorFilter(boxColor, PorterDuff.Mode.SRC_IN);
holder.markbox.setImageDrawable(box);
holder.markboxBorder.setImageDrawable(boxBorder);
holder.average.setText(output);
holder.average.setTypeface(EasyFonts.robotoBlack(context));
}
@Override
public int getItemCount() {
return data.size();
}
public class ReportViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView subject;
ImageView icon;
ImageView markbox;
ImageView markboxBorder;
TextView average;
public ReportViewHolder(View itemView) {
super(itemView);
subject = (TextView) itemView.findViewById(R.id.report_subject);
icon = (ImageView) itemView.findViewById(R.id.report_icon);
markbox = (ImageView) itemView.findViewById(R.id.report_markbox);
markboxBorder = (ImageView) itemView.findViewById(R.id.report_markbox_border);
average = (TextView) itemView.findViewById(R.id.report_average);
itemView.setOnClickListener(this);
}
@Override
public void onClick(View v) {
itemClickListener.onItemClick(v, this.getAdapterPosition());
}
}
}
Knows anybody what to do? Thank you for your help!!!
Solution 1:
It's sort of caching. From the Android docs:
if you instantiate two Drawable objects from the same image resource, then change a property (such as the alpha) for one of the Drawables, then it will also affect the other. So when dealing with multiple instances of an image resource, instead of directly transforming the Drawable, you should perform a tween animation.
Drawable.mutate() after creating should fix the issue.
A mutable drawable is guaranteed to not share its state with any other drawable. This is especially useful when you need to modify properties of drawables loaded from resources. By default, all drawables instances loaded from the same resource share a common state; if you modify the state of one instance, all the other instances will receive the same modification.
Something like this:
Drawable box = ContextCompat.getDrawable(context, R.drawable.markbox).mutate();
Drawable boxBorder = ContextCompat.getDrawable(context, R.drawable.markbox_border).mutate();
Solution 2:
Thanks to Sergey that guided me to the solution. I share what I did in onBindViewHolder method.
final Drawable drawable = ContextCompat.getDrawable(mContext, R.drawable.ic_icon).mutate();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
holder.image.setBackground(drawable);
} else {
holder.image.setBackgroundDrawable(drawable);
}
Solution 3:
TL;DR. If you are worried about performance and still want some caching, use TintedIconCache
- a single class you can grab from this gist.
TintedIconCache cache = TintedIconCache.getInstance();
Drawable coloredIcon = cache.fetchTintedIcon(context, R.drawable.ic, R.color.color));
How TintedIconCache
works?
It manages a cache such that only one instance of a uniquely tinted drawable is kept into memory. It should be fast, and memory efficient.
// Get an instance
TintedIconCache cache = TintedIconCache.getInstance();
// Will be fetched from the resources
Drawable backIcon = cache.fetchTintedIcon(context, R.drawable.icon, R.color.black));
// Will be fetched from the resources as well
Drawable bleuIcon = cache.fetchTintedIcon(context, R.drawable.icon, R.color.bleu));
// Will be fetched from the cache!!!
Drawable backIconTwo = cache.fetchTintedIcon(context, R.drawable.icon, R.color.back));
Check this answer for more details.
Consider looking at the gist to see how it works under the hood.