14

I'm having trouble with highlighted an item within a RecyclerView, similar to setting the selected item in a ListView.

At the moment, I've set up up the RecyclerView, have a default LayoutManager, and have an adapter which displays all the data. I've just recently got the onClickListener() working (although I'm not sure if it should go in the onCreateViewHolder() or onBindViewHolder*(- not sure when onBindViewHolder() is called).

I've tried searching around, and the closest I've gotten is the question here. However, I'm completely lost in that question. Where is that onClick() method (inside the adapter, inside the containing Activity/fragment, etc?). What is that viewHolderListener? What is getPosition() from and what does it do exactly? Essentially, I've gotten nowhere from that question, and it was the best resource I could find so far.

Here is my current code for setting up the RecyclerView:

// Sets up the main navigation
private void setupMainNav() {
    // use this setting to improve performance if you know that changes
    // in content do not change the layout size of the RecyclerView
    mMainRecyclerNav.setHasFixedSize(true);

    // use a linear layout manager
    LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
    mMainRecyclerNav.setLayoutManager(layoutManager);

    // Create and set the adapter for this recyclerView
    MainNavAdapter adapter = new MainNavAdapter(getActivity());
    mMainRecyclerNav.setAdapter(adapter);
}

Here is my current code for the adapter:

class MainNavAdapter extends RecyclerView.Adapter<MainNavAdapter.ViewHolder> {
    Context mContext;

    // Holds the titles of every row
    String[] rowTitles;

    // Default constructor
    MainNavAdapter(Context context) {
        this.mContext = context;

        // Get the rowTitles - the necessary data for now - from resources
        rowTitles = context.getResources().getStringArray(R.array.nav_items);
    }

    // Simple class that holds all the views that need to be reused
    class ViewHolder extends RecyclerView.ViewHolder{
        View parentView; // The view which holds all the other views
        TextView rowTitle; // The title of this item

        // Default constructor, itemView holds all the views that need to be saved
        public ViewHolder(View itemView) {
            super(itemView);

            // Save the entire itemView, for setting listeners and usch later
            this.parentView = itemView;

            // Save the TextView- all that's supported at the moment
            this.rowTitle = (TextView) itemView.findViewById(R.id.row_title);
        }
    }

    // Create new views (invoked by the layout manager)
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
        // Create the view for this row
        View row = LayoutInflater.from(mContext)
                .inflate(R.layout.list_navmain_row, viewGroup, false);

        // Create a new viewHolder which caches all the views that needs to be saved
        ViewHolder viewHolder = new ViewHolder(row);

        return viewHolder;
    }

    // Replace the contents of a view (invoked by the layout manager)
    @Override
    public void onBindViewHolder(ViewHolder viewHolder, int i) {
        // - get element from your dataset at this position
        // - replace the contents of the view with that element
        viewHolder.rowTitle.setText(rowTitles[i]);

        // TODO: Make a better workaround for passing in the position to the listener
        final int position = i;



        // Set a listener for this entire view
        viewHolder.parentView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(getActivity(), "Clicked on row: " + position, Toast.LENGTH_SHORT).show();
            }
        });
    }

    // Return the size of your dataset (invoked by the layout manager)
    @Override
    public int getItemCount() {
        return rowTitles.length;
    }
}

I would appreciate any help. Thank you.

Community
  • 1
  • 1
DragonJawad
  • 1,846
  • 3
  • 20
  • 28
  • may b this help to hightlight list item in recycleview http://amolsawant88.blogspot.in/2015/08/easy-way-to-highlight-selected-rowitem.html – Amol Sawant Aug 28 '15 at 11:11

4 Answers4

18

RecyclerView does not handle item selection or states like a ListView does. Instead you have to handle this manually in your view holder.

The first thing you can do is declare your item view as clickable, in your `ViewHolder constructor :

    public ViewHolder(View itemView) {
        super(itemView);

        // Make this view clickable
        itemView.setClickable(true);

        // ...
    } 

Then if you want to highlight the selection, you must keep track of the selected rows in your adapter (for example using a List of indices), and in your onBindViewHolder method :

@Override 
public void onBindViewHolder(ViewHolder viewHolder, int i) {
    // mark  the view as selected:
    viewHolder.parentview.setSelected(mSelectedRows.contains(i));
} 

As a side note you should set the onClickListener on your parent view in the onCreateViewHolder instead of the onBindViewHolder. The onBindViewHolder method will be called many times for the same view holder, and you'll perform more operations than necessary

XGouchet
  • 10,002
  • 10
  • 48
  • 83
  • Thanks for answering this old open question! – DragonJawad Feb 14 '15 at 03:54
  • @MDragon00 If you also need to move selection with the keyboard, check my solution here: http://stackoverflow.com/questions/27194044/how-to-properly-highlight-selected-item-on-recyclerview#answer-28838834 – Greg Ennis Mar 03 '15 at 18:10
  • I thought that `onBindViewHolder` is supposed to be used when the item in a row is needed to be displayed. I guess the question referred to highlighting an item only when this has been selected, not during its creation. Why did you suggest to use the `setSelected`method inside `onBindViewHolder` then? Since your answer has already been accepted, you could answer to my question :) http://stackoverflow.com/questions/29695811/how-to-apply-setitemcheckedposition-true-with-recyclerview-in-android – Cris Apr 17 '15 at 15:50
  • Because onBind method takes a recycled view that could be selected or unselected previously (and not used anymore) and pull data from the adapter inside it. That's why you need to know if that element was already or not selected. Check my https://github.com/davideas/FlexibleAdapter – Davideas Jun 29 '15 at 11:48
  • This is fine for turning ON a selected item in a RecyclerView, but what about turning an item OFF when something else is selected? Since Views are recycled, positions are no longer valid. Or am I missing something? – SMBiggs May 16 '16 at 20:36
  • @ScottBiggs Positions into your data structure are still valid, and you're given the position in onBindHolder. So if you update your data structure which holds the properly selected items, then onBindViewHolder should display correctly. – Shawn Lauzon Jun 09 '16 at 01:40
  • @ScottBiggs Ahh, I see what you're saying. Yes, I'm seeing the same thing right now. It usually works by keeping a reference to the View, but because they are recycled this doesn't work if you scroll too far off the screen. I haven't found a solution yet :( – Shawn Lauzon Jun 09 '16 at 01:53
  • Hi @ScottBiggs See my answer below which solves the problem :) – Shawn Lauzon Jun 09 '16 at 02:23
10

There's an easy way to do it:

  int row_index=0;
    @Override
    public void onBindViewHolder(MyViewHolder holder, final int position) {
        Movie movie = moviesList.get(position);
        holder.title.setText(movie.getTitle());
        holder.genre.setText(movie.getGenre());
        holder.year.setText(movie.getYear());

        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                row_index=position;
                notifyDataSetChanged();
            }
        });

        if(row_index==position){
            holder.itemView.setBackgroundColor(Color.parseColor("#567845"));
        }
        else
        {
            holder.itemView.setBackgroundColor(Color.parseColor("#ffffff"));
        }

    }
Victor Ruiz.
  • 1,706
  • 22
  • 22
5

I'm extending @XGouchet's answer because even though it put me on the right path, I still needed to do extra work to support selecting AND UNSELECTING rows (like a toggle). Both @ScottBiggs and I needed this type of support.

The code holds onto mSelectedPosition which allows the selection to be set correctly in onBindViewHolder and mSelectedView which allows the selection to be toggled correctly (both on and off) when clicked.

private class AsanaAdapter extends RecyclerView.Adapter<AsanaViewHolder> {
    private RecyclerView mRecyclerView;

    private final List<Asana> mAsanas;
    private int mSelectedPosition;
    private View mSelectedView;

    public AsanaAdapter(RecyclerView recyclerView, List<Asana> items) {
        mRecyclerView = recyclerView;
        mAsanas = items;
        setHasStableIds(true);
        setSelected(0);
    }

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

    @Override
    public AsanaViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View newView = getLayoutInflater().inflate(
                R.layout.performance_builder_list_content, parent, false);
        newView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (!v.isSelected()) {
                    // We are selecting the view clicked
                    if (mSelectedView != null) {
                        // deselect the previously selected view
                        mSelectedView.setSelected(false);
                    }
                    mSelectedPosition = mRecyclerView.getChildAdapterPosition(v);
                    mSelectedView = v;
                    setTitle(mAsanas.get(mSelectedPosition).getName());
                } else {
                    // We are deselecting the view clicked
                    setTitle("");
                    mSelectedPosition = -1;
                    mSelectedView = null;
                }

                // toggle the item clicked
                v.setSelected(!v.isSelected());
            }
        });
        return new AsanaViewHolder(newView);
    }

    @Override
    public void onBindViewHolder(AsanaViewHolder holder, int position) {
        if (position == mSelectedPosition) {
            holder.itemView.setSelected(true);

            // keep track of the currently selected view when recycled
            mSelectedView = holder.itemView;
        } else {
            holder.itemView.setSelected(false);
        }
    }
}
Shawn Lauzon
  • 6,234
  • 4
  • 35
  • 46
  • Took a bit to test your code (I had to re-work the click listeners and figure out a few odd method calls), but it seems to work just fine. Big thumbs up, thanks! – SMBiggs Jun 12 '16 at 21:14
2

Try this solution if you want to highlight selected item only for a moment after click. I use retrolambda but you can use normal Runnable object as well.

1.

class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
    @BindView(R.id.appIcon)
    ImageView appIcon;
    @BindView(R.id.appName)
    TextView appName;

    public ViewHolder(View itemView) {
        super(itemView);
        itemView.setOnClickListener(this);
        ButterKnife.bind(this, itemView);
    }

    @Override
    public void onClick(View v) {
        v.setSelected(true);
        new Handler().postDelayed(() -> v.setSelected(false), 100);
        //or new Handler().postDelayed(new Runnable...

        //your onClick action
    }
}
  1. create list_item_selector.xml in your Drawable directory

    <?xml version="1.0" encoding="utf-8"?>
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
        <item android:state_selected="true" android:drawable="@color/colorPrimary" />
        <item android:drawable="@android:color/transparent" />
    </selector>
    
  2. Set your selector in root layout of your single item

    android:background="@drawable/list_item_selector"
    
Bartosz Wilk
  • 131
  • 6