Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Changing one ViewHolder item also affects to other items

I have a simple mp3 player based on RecyclerView. There is an Adapter and ViewHolder for handling tracks in playlist.

public class TracksAdapter extends RecyclerView.Adapter<TracksAdapter.ViewHolder> {

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {

        View v = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.cell_playlist, parent, false);

        return new ViewHolder(v);
    }

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

        holder.trackName.setText("Track " + position);
    }

    @Override
    public int getItemCount() {

        return tracks.size();
    }

    class ViewHolder extends RecyclerView.ViewHolder {

        final TextView trackName;

        ViewHolder(View itemView) {

            super(itemView);

            trackName = itemView.findViewById(R.id.trackName);
        }
    }
}

When I click on track I want to change background color of selected track in playlist. Problem is when I set background color to one item it's will be affected to every tenth cell in playlist.

For example if there are 100 tracks in the playlist, 10 tracks will be highlighted including the selected.

Method findViewHolderForAdapterPosition(position) returns ViewHolder with selected track.

private void setTrackColor(int position) {

    RecyclerView.ViewHolder holder =
        recyclerView.findViewHolderForAdapterPosition(position);

    if (holder == null) return;

    View item = holder.itemView;
    item.setBackgroundColor(ContextCompat.getColor(main, R.color.playing)); 
}
like image 698
Alex Key Avatar asked Mar 05 '23 07:03

Alex Key


1 Answers

You can't change the color of a view direcly. In a RecyclerView, all the Views are re-used. So, if you change the color in a position, you may indirecly change the color in other positions because same view will be re-used.

You must save the position of the currently playing track separately. This way, during onBindViewHolder, you check if current view being bind is the track currently playing. If it is the same track, apply some color. If it is not the same color, restore default color

public class TracksAdapter extends RecyclerView.Adapter<TracksAdapter.ViewHolder> {

    private int mTrackPlaying = -1;

    public void setTrackPlaying(int position) {
        mTrackPlaying = position;
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        holder.trackName.setText("Track " + position);
        if(position == mTrackPlaying) {
            holder.itemView.setBackgroundColor(ContextCompat.getColor(main, R.color.playing)); 
        } else {
            // Here, you must restore the color because the view is reused.. so, you may receive a reused view with wrong colors
            holder.itemView.setBackgroundColor(ContextCompat.getColor(main, R.color.NOT_playing)); 
        }
    }
}

And then

private void setTrackColor(int position) {
    TracksAdapter adapter = (TracksAdapter) recyclerView.getAdapter();
    adapter.setTrackPlaying(position);
    // Line below will `RecyclerView` to re-draw that position.. in other words, it will triggers a call to `onBindViewHolder`
    adapter.notifyItemChanged(position);

    // Reset the color of song previously playing.. 
    adapter.notifyItemChanged(oldPosition);
}
like image 90
W0rmH0le Avatar answered Apr 02 '23 00:04

W0rmH0le