Android RecyclerView not changing background color on first item programmatically
Asked Answered
S

3

5

I'm creating a list with a RecyclerView. Every list item is a post from a user (Right now hard coded). The background for every post is loaded from a layer-list XML file in the drawable folder.

Everything is working as intended with the texts etc. but I'm trying to change the background color programmatically. It changes the background color for every item, except for the first item, and I cannot figure out why.

The first item always gets the background color specified by the solid color of the shape inside the item called shape_background in the XML file, so it is not changed, but the following items get the color #ff22ff.

This is the implementation of the adapter:

class PostListAdapter extends RecyclerView.Adapter<PostListAdapter.PostViewHolder>{

    private LayoutInflater inflater;
    private List<PostRow> data = Collections.emptyList();

    PostListAdapter(Context context, List<PostRow> data) {
        inflater = LayoutInflater.from(context);
        this.data = data;
    }

    @Override
    public void onBindViewHolder(PostViewHolder holder, int position) {

        PostRow current = data.get(position);
        holder.text.setText(current.text.toUpperCase());
        holder.time.setText(current.time.toUpperCase());
        holder.answers.setText(current.answers.toUpperCase());

        try {
            // "#ff22ff" will be changed to current.color, unique color for every post
            // That string is parsed from a JSON request, hence the try-catch.
            int color = Color.parseColor("#ff22ff"); 
            holder.shape.setColor(color);
        } catch (Exception e){
            e.printStackTrace();
        }

    }

    @Override
    public PostViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = inflater.inflate(R.layout.post_row, parent, false);
        return new PostViewHolder(view);
    }

    @Override
    public int getItemCount() {
        return data.size();
    }

    class PostViewHolder extends RecyclerView.ViewHolder {

        TextView text;
        TextView time;
        TextView answers;
        GradientDrawable shape;

        PostViewHolder(View itemView) {
            super(itemView);
            text = (TextView) itemView.findViewById(R.id.text);
            time = (TextView) itemView.findViewById(R.id.time);
            answers = (TextView) itemView.findViewById(R.id.answers);
            LayerDrawable layers = (LayerDrawable) ContextCompat.getDrawable(itemView.getContext(), R.drawable.bubble);

            shape = (GradientDrawable) (layers.findDrawableByLayerId(R.id.shape_background));
        }

    }

}

Why is the background of the first item not changed, but the texts are?

Thank you in advance!

Smollett answered 29/12, 2016 at 1:22 Comment(2)
Do you get any exception? Why try catch?Adult
I understand that the comment wasn't clear enough. The hard coded string "#ff22ff" will later on be changed to the string current.color. That string is parsed from a JSON response. So just in case something gets messed up in that process, surround that with the try-catch. I will update the comment, thanks.Smollett
P
4

On your onBindViewHolder, get your view(from holder) for that you need to change background color & get its current background(drawable bubble, that you already set in XML attribute)

LayerDrawable layerDrawable = (LayerDrawable) yourView.getBackground().getCurrent();
GradientDrawable gradientDrawable = (GradientDrawable) layerDrawable.findDrawableByLayerId(R.id. shape_background).getCurrent();
// set you color based on position
gradientDrawable.setColor(Color.parseColor("#ff22ff"));
Pilpul answered 29/12, 2016 at 5:36 Comment(1)
Thank you, this solved my issue in the way I wanted. Using API 15 and above, so the setBackground in liubaoyua's answer could not be used in my case, but it's correct as well.Smollett
S
4

you have called holder.shape.setColor(color);

but the holderView didn't refresh.

maybe you should call

holder.itemView.setBackgroundDrawable( ContextCompat.getDrawable(itemView.getContext(), R.drawable.bubble))

to refresh the holderView

Selfloading answered 29/12, 2016 at 5:46 Comment(1)
This is correct, note that setBackgroundDrawable is deprecated though. One should use setBackground for API level 16 and above, even though setBackground calls the setBackgroundDrawable method.Smollett
A
4

You have two different issues here.

First - you change drawable itself but not view background. It is solved by liubaoyua and Vignesh Sundar answers.

Second - you change R.drawable.bubble statically. Android can cache and reuse it even after mutation. It can lead to another issues when you start use different colors for different items.

To solve second issue you must call android.graphics.drawable.Drawable#mutate every time before firstly mutate it.

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.

So it should looks something like this:

class PostViewHolder extends RecyclerView.ViewHolder {

    TextView text;
    TextView time;
    TextView answers;
    LayerDrawable layers;
    GradientDrawable shape;

    PostViewHolder(View itemView) {
        super(itemView);

        text = (TextView) itemView.findViewById(R.id.text);
        time = (TextView) itemView.findViewById(R.id.time);
        answers = (TextView) itemView.findViewById(R.id.answers);

        layers = (LayerDrawable) ContextCompat
                .getDrawable(itemView.getContext(), R.drawable.bubble)
                .mutate();

        shape = (GradientDrawable) layers.findDrawableByLayerId(R.id.shape_background);
        itemView.setBackground(layers);
    }

    void setColor(Color color) {
        shape.setColor(color);
        itemView.setBackgroundDrawable(layers);
    }
}

May be can be little simplified by moving .mutate() from layer to shape.

Accent answered 29/12, 2016 at 16:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.