181

I am trying to use a RecyclerView as a horizontal ListView. I am trying to figure out how to highlight the selected item. When I click on one of the items, it gets selected and it is highlighted properly but when I click on another one, the second one gets highlighted with the older one.

Here is my onClick function:

@Override
public void onClick(View view) {

    if(selectedListItem!=null){
        Log.d(TAG, "selectedListItem " + getPosition() + " " + item);
        selectedListItem.setBackgroundColor(Color.RED);
    }
    Log.d(TAG, "onClick " + getPosition() + " " + item);
    viewHolderListener.onIndexChanged(getPosition());
    selectedPosition = getPosition();
    view.setBackgroundColor(Color.CYAN); 
    selectedListItem = view;
}

Here is the onBindViewHolder:

@Override
public void onBindViewHolder(ViewHolder viewHolder, int position) {   
    viewHolder.setItem(fruitsData[position]);
    if(selectedPosition == position)
        viewHolder.itemView.setBackgroundColor(Color.CYAN);    
    else
        viewHolder.itemView.setBackgroundColor(Color.RED);

}
Morteza Jalambadani
  • 2,190
  • 6
  • 21
  • 35
user65721
  • 2,833
  • 3
  • 19
  • 28

16 Answers16

208

This is much simple way to do it.

Have a private int selectedPos = RecyclerView.NO_POSITION; in the RecyclerView Adapter class, and under onBindViewHolder method try:

@Override
public void onBindViewHolder(ViewHolder viewHolder, int position) {   
    viewHolder.itemView.setSelected(selectedPos == position);

}

And in your OnClick event modify:

@Override
public void onClick(View view) {
     notifyItemChanged(selectedPos);
     selectedPos = getLayoutPosition();
     notifyItemChanged(selectedPos); 
}

Works like a charm for Navigtional Drawer and other RecyclerView Item Adapters.

Note: Be sure to use a background color in your layout using a selector like @colabug clarified:

<selector xmlns:android="http://schemas.android.com/apk/res/android">
  <item android:drawable="@color/pressed_color" android:state_pressed="true"/>
  <item android:drawable="@color/selected_color" android:state_selected="true"/>
  <item android:drawable="@color/focused_color" android:state_focused="true"/>
</selector>

Otherwise setSelected(..) will do nothing, rendering this solution useless.

starball
  • 20,030
  • 7
  • 43
  • 238
zIronManBox
  • 4,967
  • 6
  • 19
  • 35
  • @zIronManBox: I tried your method. Works fine except the first item is already highlighted even before the onClick method is called. Any ideas on how to fix? – AJW Aug 08 '16 at 01:39
  • @colabug: I also set up a background drawable/selector. My problem is that the first item already has the background highlighted even before the onClick method is called. Any ideas on how to fix? – AJW Aug 08 '16 at 01:42
  • I guess setting selectedPos as -1 rather than 0 would solve that issue – Gaurav Sarma Oct 19 '16 at 16:39
  • 3
    getLayoutPosition() isn't available by me. – ka3ak Mar 17 '17 at 05:43
  • 3
    Don't just use -1, use RecyclerView.NO_POSITION; (which is -1) – Martin Marconcini Apr 13 '17 at 00:45
  • 10
    @ka3ak: getLayoutPosition is method of ViewHolder class whose object is passed as first parameter in bind view method. So it can be accessed by `vieHolder.getLayoutPosition` – Tushar Kathuria May 25 '17 at 16:19
  • @Gaurav Sarma Do you mean the "int" variable should be set up like this: "private int selectedPos = RecyclerView.NO_POSITION"? rather than "... selectedPos = -1"? – AJW Dec 05 '17 at 17:21
  • `viewHolder.itemView.setSelected(selectedPos == position)` didn't work for me. `holder.itemView.setBackgroundColor(selected_position == position ? Color.GREEN : Color.TRANSPARENT)` in Meet's answer worked. – hBrent Jul 26 '18 at 15:51
  • This gives me issues. If an item is clicked again, it is replaced my another in the recycler view. – Asim Jul 01 '20 at 19:25
  • Nice stuff, but the app crashes using RecyclerView.NO_POSITION, after setting selectedPos = 0, solved the crash. – Faizan Haidar Khan Oct 20 '20 at 12:20
152

UPDATE [26/Jul/2017]:

As the Pawan mentioned in the comment about that IDE warning about not to using that fixed position, I have just modified my code as below. The click listener is moved to ViewHolder, and there I am getting the position using getAdapterPosition() method

int selected_position = 0; // You have to set this globally in the Adapter class

@Override
public void onBindViewHolder(ViewHolder holder, int position) {
    Item item = items.get(position);

    // Here I am just highlighting the background
    holder.itemView.setBackgroundColor(selected_position == position ? Color.GREEN : Color.TRANSPARENT);
}

public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

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

    @Override
    public void onClick(View v) {
        // Below line is just like a safety check, because sometimes holder could be null,
        // in that case, getAdapterPosition() will return RecyclerView.NO_POSITION
        if (getAdapterPosition() == RecyclerView.NO_POSITION) return;

        // Updating old as well as new positions
        notifyItemChanged(selected_position);
        selected_position = getAdapterPosition();
        notifyItemChanged(selected_position);

        // Do your another stuff for your onClick
    }
}

hope this'll help.

Meet Vora
  • 2,783
  • 1
  • 16
  • 33
  • That's awesome! I'm a bit confused though, could you help me implement a way, expanding upon this answer, how to do whatever you are doing in the else statement when you click on an item that's already selected, so that you deselect it : else{ holder.itemView.setBackgroundColor(Color.TRANSPARENT); } – iBobb Feb 27 '16 at 14:20
  • Of course Yes. @iBobb. For NOT SELECTED items, i.e. whose position is not equals to our 'selected_position' variable will have to NO background. I used this because RecyclerView, as the name suggests, recycles each item, so it sets GREEN background for random rows while we SCROLL, that's why I placed that ELSE part. (You can just try it by comment out that ELSE part, and then scroll your Recycler-view) – Meet Vora Feb 28 '16 at 11:57
  • I don't think you understood my question. You know in any app, when you select something it gets highlighted. If you hold it again, it gets deselected. How would we implement that? Right now we can only select using your code and touching and holding the same item does nothing. – iBobb Feb 28 '16 at 14:17
  • It can be possible, but for that you have to override onLongClickListener instead of onClickListener, right now I am busy so can't provide full code, but you should have to basically do in that way. – Meet Vora Mar 01 '16 at 12:42
  • For some reason it takes time to highlight after the click, about 100 to 200 milliseconds. Any idea why? I experienced this on an emulator and my lollipop phone – suku Apr 02 '16 at 17:12
  • @suku Are you using RecyclerView inside the ScrollView? – Meet Vora Apr 06 '16 at 06:47
  • No, it's inside a linear layout – suku Apr 06 '16 at 07:34
  • @suku Can you post here your Adapter class? I think your inflated layout can be complex.. – Meet Vora Apr 08 '16 at 05:14
  • I started checking, it was a line of code in the viewholder class that was taking time, I did some changes and moved it to the onbindviewholder and the time taken reduced a lot – suku Apr 08 '16 at 06:45
  • Thanks for your help – suku Apr 08 '16 at 06:46
  • @iBobb to deselect an already selected item, you can simply put an extra condition in the if statement, something like `if (selected_position = current && flag)` and after updating selected_position, set your flag by checking if the view was changed (in my case, I'm checking for visibility of a subview, something like `holder.itemView.getViewById(some_id).getVisibility == View.VISIBLE)` – irvanjitsingh Apr 20 '16 at 22:58
  • thanks, this is the right idea but that code is a bit wrong I think. You should call notify item changed after (not before) changing selected_position, once on the old value and once on its new value. – SpaceMonkey May 30 '16 at 22:34
  • @Spacemonkey Thanks for your feedback. I will check and let you know that stuff later as I am currently busy. – Meet Vora May 31 '16 at 06:16
  • What is Item for an object? I cant find it anywhere.@Meet –  Jun 21 '16 at 13:43
  • 'item' is the fetched Object from the 'collection of Objects' (i.e. I am having an ArrayList whose declaration name is 'items'). So you can see that how I am fetching a single Item from the collection of items, by writing this: Item item = items.get(position); @Muddz – Meet Vora Jun 22 '16 at 04:36
  • This is **not** awesome! Also how you handle the ripple effect? When selecting, the Adapter should keep track of all selected items and not only 1 and we don't need to rebind the view with `notifyItemChange()`, we just need to change the state of the View in the ViewHolder, so the selection and RIPPLE(!) is done by activating the view after the click event. You have to `notifyItemChange` all views selected only when you `clearSelection()` - Check some libraries for Adapters. – Davideas Oct 10 '16 at 22:08
  • @Davidea I had not written this to check **awesomeness** (just kidding)! But according to OP, this code provides the logic to achieve how to **highlight** an item (when it will clicked) from RecyclerView (here highlight means one item at a time - if you select another one, then the previous one will get deselected). And for Ripple, thanks for advice, I will check out and let you inform. :) – Meet Vora Oct 12 '16 at 04:53
  • @Meet, for awesomeness I was referring to the first comment :-) Well, for single selection it works indeed, but for multi selection? I see that this question was visited by 54K ppl already, I was simply wondering what kind of solution they are searching. I don't think that a simple variable (as others solutions too) is the good answer. I have developed a library to selecting items last year, now it has more advanced feature, so I can say that the variable `selected_position` is just a quick and dirty way to resolve it. – Davideas Oct 12 '16 at 09:30
  • @Davidea Thanks for advice. I will have look at this(dirty variable) later. ;) – Meet Vora Oct 13 '16 at 09:59
  • The IDE warns that "Do not treat position as fixed; only use immediately and call holder.getAdapterPosition() to look it up later". Which means who should not use the position argument. – Pawan Jul 26 '17 at 07:31
  • @Pawan Thanks for pointing out that, I have updated my answer. – Meet Vora Jul 26 '17 at 11:41
  • Super working like a champ, great answer to reducing the redundant code. – Shailendra Madda Aug 09 '18 at 11:04
  • 1
    Nice work Meet. For NOT SELECTED here the codes below, its working for me, int previousselectedPosition = -2; // set globally. then come aftee these lines notifyItemChanged(selected_position); add the below lines, if(previousselectedPosition == selected_position) { selected_position = -1; notifyItemChanged(selected_position); } else { previousselectedPosition = selected_position; } try this one, it will work. – G.N.SRIDHAR Apr 27 '20 at 09:26
  • I couldn't access `selected_position ` inside `ViewHolder` so I add the `onClick` listener directly to `onBindViewHolder`. – Ahtisham Aug 12 '20 at 15:33
  • @Ahtisham That will work too. I think your ViewHoler class is a separate class (not inside of the adapter class) could be the reason why you weren't able to access selected_position. But your fix will work too. – Meet Vora Aug 13 '20 at 19:35
68

I wrote a base adapter class to automatically handle item selection with a RecyclerView. Just derive your adapter from it and use drawable state lists with state_selected, like you would do with a list view.

I have a Blog Post Here about it, but here is the code:

public abstract class TrackSelectionAdapter<VH extends TrackSelectionAdapter.ViewHolder> extends RecyclerView.Adapter<VH> {
    // Start with first item selected
    private int focusedItem = 0;

    @Override
    public void onAttachedToRecyclerView(final RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);

        // Handle key up and key down and attempt to move selection
        recyclerView.setOnKeyListener(new View.OnKeyListener() {
            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                RecyclerView.LayoutManager lm = recyclerView.getLayoutManager();

                // Return false if scrolled to the bounds and allow focus to move off the list
                if (event.getAction() == KeyEvent.ACTION_DOWN) {
                    if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
                        return tryMoveSelection(lm, 1);
                    } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
                        return tryMoveSelection(lm, -1);
                    }
                }

                return false;
            }
        });
    }

    private boolean tryMoveSelection(RecyclerView.LayoutManager lm, int direction) {
        int tryFocusItem = focusedItem + direction;

        // If still within valid bounds, move the selection, notify to redraw, and scroll
        if (tryFocusItem >= 0 && tryFocusItem < getItemCount()) {
            notifyItemChanged(focusedItem);
            focusedItem = tryFocusItem;
            notifyItemChanged(focusedItem);
            lm.scrollToPosition(focusedItem);
            return true;
        }

        return false;
    }

    @Override
    public void onBindViewHolder(VH viewHolder, int i) {
        // Set selected state; use a state list drawable to style the view
        viewHolder.itemView.setSelected(focusedItem == i);
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        public ViewHolder(View itemView) {
            super(itemView);

            // Handle item click and set the selection
            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    // Redraw the old selection and the new
                    notifyItemChanged(focusedItem);
                    focusedItem = getLayoutPosition();
                    notifyItemChanged(focusedItem);
                }
            });
        }
    }
} 
Greg Ennis
  • 14,917
  • 2
  • 69
  • 74
  • `mRecyclerView` is not declared anywhere. Should we just pass it as a parameter in the constructor and store it in a field? – Pedro Mar 26 '15 at 00:13
  • 1
    Sorry about that. My adapter is an inner class of my fragment so it has access to the recycler view field. Otherwise yes you could pass it in as a parameter. Or even better, handle `onRecyclerViewAttached` and store it in a member variable there. – Greg Ennis Mar 26 '15 at 12:54
  • 1
    Nice answer, but why use getChildPosition()? There is another method https://developer.android.com/reference/android/support/v7/widget/RecyclerView.ViewHolder.html#getAdapterPosition() – skyfishjy Apr 15 '15 at 10:03
  • I am sorry, do you mean that I just need to import your `TrackSelectionAdapter` class and use it in my list? How do I "derive my adapter from your class"? Please could you answer to my question? I am stuck so deeply: http://stackoverflow.com/questions/29695811/how-to-apply-setitemcheckedposition-true-with-recyclerview-in-android – Cris Apr 17 '15 at 18:22
  • Can you put any example full to debug ? more people not know this form see (https://www.google.es/search?q=How+to+properly+highlight+selected+item+on+RecyclerView%3F&oq=How+to+properly+highlight+selected+item+on+RecyclerView%3F&aqs=chrome..69i57j69i60.135j0j7&sourceid=chrome&es_sm=93&ie=UTF-8#q=How+to+properly+highlight+selected+item+in+RecyclerView%3F&tbs=qdr:y) please put any example thx –  Jul 30 '15 at 14:48
  • How do I use this? I have already MyAdapter class. How should I use this one? – artouiros Dec 10 '15 at 13:25
  • I don't think this deserves the accepted answer. Inside bindViewHolder you can still call notifyDataSetChanged and notifyItemChanged. set null on setCheckedChangeListener(null) and holder.checkBox.setChecked(model.getPosition.isSelected) then call holder.checkBox.setCheckedOnchangeListener(new OncheckedChangeListner) inside this you can call notify....notifyChan.... methods. – EngineSense Apr 05 '16 at 08:52
  • 2
    I tried this and found the two back-to-back calls to NotifyItemChanged() killed any semblance of smooth scrolling on slower hardware. Was particularly bad on the Fire TV before the Lollipop update – Redshirt Apr 18 '16 at 17:30
  • @Redshirt Depending on what all you need to do during the scroll events, notifyItemChanged may only need to be called once. See the snippet in my answer below. It's a bit dated, but may still apply. – entitycs Aug 07 '18 at 18:56
14

As noted in this linked question, setting listeners for viewHolders should be done in onCreateViewHolder. That said, the implementation below was originally aimed at multiple selection, but I threw a hack in the snippet to force single selection.(*1)

// an array of selected items (Integer indices) 
private final ArrayList<Integer> selected = new ArrayList<>();

// items coming into view
@Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
    // each time an item comes into view, its position is checked
    // against "selected" indices
    if (!selected.contains(position)){
        // view not selected
        holder.parent.setBackgroundColor(Color.LTGRAY);
    }
    else
        // view is selected
        holder.parent.setBackgroundColor(Color.CYAN);
}

// selecting items
@Override
public boolean onLongClick(View v) {
        
        // select (set color) immediately.
        v.setBackgroundColor(Color.CYAN);

        // (*1)
        // forcing single selection here...
        if (selected.isEmpty()){
            selected.add(position); // (done - see note)
        }else {
            int oldSelected = selected.get(0);
            selected.clear(); // (*1)... and here.
            selected.add(position);
            // note: We do not notify that an item has been selected
            // because that work is done here.  We instead send
            // notifications for items which have been deselected.
            notifyItemChanged(oldSelected);
        }
        return false;
}
entitycs
  • 347
  • 1
  • 8
  • There's no parent field in holder. – FractalBob Nov 10 '21 at 16:09
  • @FractalBob There was in my implementation, evidently. Anyhow, those lines of code are not a part of the logic for the answer. That's where you would put your own logic for what you want to happen upon selection/deselection. – entitycs Jan 25 '22 at 06:05
8

I think, I've found the best tutorial on how to use the RecyclerView with all basic functions we need (single+multiselection, highlight, ripple, click and remove in multiselection, etc...).

Here it is --> http://enoent.fr/blog/2015/01/18/recyclerview-basics/

Based on that, I was able to create a library "FlexibleAdapter", which extends a SelectableAdapter. I think this must be a responsibility of the Adapter, actually you don't need to rewrite the basic functionalities of Adapter every time, let a library to do it, so you can just reuse the same implementation.

This Adapter is very fast, it works out of the box (you don't need to extend it); you customize the items for every view types you need; ViewHolder are predefined: common events are already implemented: single and long click; it maintains the state after rotation and much much more.

Please have a look and feel free to implement it in your projects.

https://github.com/davideas/FlexibleAdapter

A Wiki is also available.

Davideas
  • 3,226
  • 2
  • 33
  • 51
  • Great work writing that adapter, it seems very useful. Only thing is that it really needs some basic examples and documentation, I find it a little confusing to even get it up and running. Potentially great though! – Voy Jun 20 '16 at 10:17
  • Yes, I didn't find sufficient information there to get started. Couldn't find API reference even though code seems to be commented with that in mind. The sample app seem - while broad and informative - very difficult to understand without prior knowledge of the library. All use cases are bound together, there's little indication of what demonstrates what, classes are reused throughout different scenarios which results in them being overloaded with information. I created a new issue with these suggestions here: https://github.com/davideas/FlexibleAdapter/issues/120 – Voy Jun 22 '16 at 09:37
  • **Wiki** has been completely rewritten and is on the way of completion. – Davideas Jun 06 '17 at 13:23
6

Look on my solution. I suppose that you should set selected position in holder and pass it as Tag of View. The view should be set in the onCreateViewHolder(...) method. There is also correct place to set listener for view such as OnClickListener or LongClickListener.

Please look on the example below and read comments to code.

public class MyListAdapter extends RecyclerView.Adapter<MyListAdapter.ViewHolder> {
    //Here is current selection position
    private int mSelectedPosition = 0;
    private OnMyListItemClick mOnMainMenuClickListener = OnMyListItemClick.NULL;

    ...

    // constructor, method which allow to set list yourObjectList

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        //here you prepare your view 
        // inflate it
        // set listener for it
        final ViewHolder result = new ViewHolder(view);
        final View view =  LayoutInflater.from(parent.getContext()).inflate(R.layout.your_view_layout, parent, false);
        view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //here you set your current position from holder of clicked view
                mSelectedPosition = result.getAdapterPosition();

                //here you pass object from your list - item value which you clicked
                mOnMainMenuClickListener.onMyListItemClick(yourObjectList.get(mSelectedPosition));

                //here you inform view that something was change - view will be invalidated
                notifyDataSetChanged();
            }
        });
        return result;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        final YourObject yourObject = yourObjectList.get(position);

        holder.bind(yourObject);
        if(mSelectedPosition == position)
            holder.itemView.setBackgroundColor(Color.CYAN);
        else
            holder.itemView.setBackgroundColor(Color.RED);
    }

    // you can create your own listener which you set for adapter
    public void setOnMainMenuClickListener(OnMyListItemClick onMyListItemClick) {
        mOnMainMenuClickListener = onMyListItemClick == null ? OnMyListItemClick.NULL : onMyListItemClick;
    }

    static class ViewHolder extends RecyclerView.ViewHolder {


        ViewHolder(View view) {
            super(view);
        }

        private void bind(YourObject object){
            //bind view with yourObject
        }
    }

    public interface OnMyListItemClick {
        OnMyListItemClick NULL = new OnMyListItemClick() {
            @Override
            public void onMyListItemClick(YourObject item) {

            }
        };

        void onMyListItemClick(YourObject item);
    }
}
Konrad Krakowiak
  • 12,285
  • 11
  • 58
  • 45
  • Do you think mSelectedPosition can be declared as static to maintain configuration changes? – Sathesh Mar 18 '15 at 20:07
  • No! it is wrong to make it as static it is very wrong – Konrad Krakowiak Mar 18 '15 at 20:09
  • Yeah. its dangerous. But to maintain the config change(esp. screen rotation) we can declare this variable in activity and get it from there, right? – Sathesh Mar 18 '15 at 20:15
  • There is many solution... You can use getter and setter, You can create your own method saveInstanceState for adapter and call it in saveInstanceState from Activity/Fragment – Konrad Krakowiak Mar 18 '15 at 20:18
4

there is no selector in RecyclerView like ListView and GridView but you try below thing it worked for me

create a selector drawable as below

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"> 
<item android:state_pressed="true">
   <shape>
         <solid android:color="@color/blue" />
   </shape>
</item>

<item android:state_pressed="false">
    <shape>
       <solid android:color="@android:color/transparent" />
    </shape>
</item>
</selector>

then set this drawable as background of your RecyclerView row layout as

android:background="@drawable/selector"
amodkanthe
  • 4,345
  • 6
  • 36
  • 77
4

Decision with Interfaces and Callbacks. Create Interface with select and unselect states:

public interface ItemTouchHelperViewHolder {
    /**
     * Called when the {@link ItemTouchHelper} first registers an item as being moved or swiped.
     * Implementations should update the item view to indicate it's active state.
     */
    void onItemSelected();


    /**
     * Called when the {@link ItemTouchHelper} has completed the move or swipe, and the active item
     * state should be cleared.
     */
    void onItemClear();
}

Implement interface in ViewHolder:

   public static class ItemViewHolder extends RecyclerView.ViewHolder implements
            ItemTouchHelperViewHolder {

        public LinearLayout container;
        public PositionCardView content;

        public ItemViewHolder(View itemView) {
            super(itemView);
            container = (LinearLayout) itemView;
            content = (PositionCardView) itemView.findViewById(R.id.content);

        }

               @Override
    public void onItemSelected() {
        /**
         * Here change of item
         */
        container.setBackgroundColor(Color.LTGRAY);
    }

    @Override
    public void onItemClear() {
        /**
         * Here change of item
         */
        container.setBackgroundColor(Color.WHITE);
    }
}

Run state change on Callback:

public class ItemTouchHelperCallback extends ItemTouchHelper.Callback {

    private final ItemTouchHelperAdapter mAdapter;

    public ItemTouchHelperCallback(ItemTouchHelperAdapter adapter) {
        this.mAdapter = adapter;
    }

    @Override
    public boolean isLongPressDragEnabled() {
        return true;
    }

    @Override
    public boolean isItemViewSwipeEnabled() {
        return true;
    }

    @Override
    public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
        int swipeFlags = ItemTouchHelper.END;
        return makeMovementFlags(dragFlags, swipeFlags);
    }

    @Override
    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
        ...
    }

    @Override
    public void onSwiped(final RecyclerView.ViewHolder viewHolder, int direction) {
        ...
    }

    @Override
    public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
        if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
            if (viewHolder instanceof ItemTouchHelperViewHolder) {
                ItemTouchHelperViewHolder itemViewHolder =
                        (ItemTouchHelperViewHolder) viewHolder;
                itemViewHolder.onItemSelected();
            }
        }
        super.onSelectedChanged(viewHolder, actionState);
    }

    @Override
    public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        super.clearView(recyclerView, viewHolder);
        if (viewHolder instanceof ItemTouchHelperViewHolder) {
            ItemTouchHelperViewHolder itemViewHolder =
                    (ItemTouchHelperViewHolder) viewHolder;
            itemViewHolder.onItemClear();
        }
    }   
}

Create RecyclerView with Callback (example):

mAdapter = new BuyItemsRecyclerListAdapter(MainActivity.this, positionsList, new ArrayList<BuyItem>());
positionsList.setAdapter(mAdapter);
positionsList.setLayoutManager(new LinearLayoutManager(this));
ItemTouchHelper.Callback callback = new ItemTouchHelperCallback(mAdapter);
mItemTouchHelper = new ItemTouchHelper(callback);
mItemTouchHelper.attachToRecyclerView(positionsList);

See more in article of iPaulPro: https://medium.com/@ipaulpro/drag-and-swipe-with-recyclerview-6a6f0c422efd#.6gh29uaaz

Mykola Tychyna
  • 173
  • 2
  • 9
3

this is my solution, you can set on an item (or a group) and deselect it with another click:

 private final ArrayList<Integer> seleccionados = new ArrayList<>();
@Override
    public void onBindViewHolder(final ViewHolder viewHolder, final int i) {
        viewHolder.san.setText(android_versions.get(i).getAndroid_version_name());
        if (!seleccionados.contains(i)){ 
            viewHolder.inside.setCardBackgroundColor(Color.LTGRAY);
        }
        else {
            viewHolder.inside.setCardBackgroundColor(Color.BLUE);
        }
        viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (seleccionados.contains(i)){
                    seleccionados.remove(seleccionados.indexOf(i)); 
                    viewHolder.inside.setCardBackgroundColor(Color.LTGRAY);
                } else { 
                    seleccionados.add(i);
                    viewHolder.inside.setCardBackgroundColor(Color.BLUE);
                }
            }
        });
    }
2

@zIronManBox answer works flawlessly. Although it doesn't have the capability for unselection and unseleted items in the recyclerView.

SO

add, as before, a private int selectedPos = RecyclerView.NO_POSITION; in the RecyclerView Adapter class, and under onBindViewHolder method :

@Override
public void onBindViewHolder(ViewHolder viewHolder, int position) {   
    viewHolder.itemView.setSelected(selectedPos == position);

}

And also in your OnClick event :

@Override
public void onClick(View view) {
     notifyItemChanged(selectedPos);
     selectedPos = getLayoutPosition();
     notifyItemChanged(selectedPos); 
}

Also add the following selector (drawable) in your layout , which includes a state_selected="false" with a transparent color:

<selector xmlns:android="http://schemas.android.com/apk/res/android">
  <item android:drawable="@color/pressed_color" android:state_pressed="true"/>
  <item android:drawable="@color/selected_color" android:state_selected="true"/>
  <item android:drawable="@color/focused_color" android:state_focused="true"/>
  <item android:drawable="@android:color/transparent" android:state_selected="false"/>
</selector>

Otherwise setSelected(..) will do nothing, rendering this solution useless.

Miguel Tomás
  • 1,714
  • 1
  • 13
  • 23
2

Programmatically

NB: Without using any file selectors.

Steps involved:

  1. Creating a global position variable i.e int selectedPosition = -1;

Integers:

-1 for default no selection

0 for default first item selection

  1. Check if the variable equals to the current position position

    public void onBindViewHolder(@NonNull ViewHolder holder, int position)
    {
        if(selectedPosition == position)
            holder.itemView.setBackgroundColor(Color.GREEN); //selected
        else
            holder.itemView.setBackgroundColor(Color.BLACK); //not selected
        ....
    }
    
  2. Using setOnClickListener method to navigate between items

    holder.itemView.setOnClickListener(v ->
    {
        if(selectedPosition == position)
        {
            selectedPosition = -1;
            notifyDataSetChanged();
            return;
        }
    
        selectedPosition = position;
        notifyDataSetChanged();
    
        // TODO: Put your itemview clicked functionality below
    });
    

Note

  • Using this code creates a toggle effect on items:

          if(selectedPosition == position)
          {
              selectedPosition = -1;
              notifyDataSetChanged();
              return;
          }
    
          selectedPosition = position;
          notifyDataSetChanged();
    
  • Using only this code creates a classical selection way of a list (One item at a time)

          selectedPosition = position;
          notifyDataSetChanged();
    
4xMafole
  • 479
  • 1
  • 8
  • 20
  • 1
    I don't think this is a good idea, because when you deselect an item, the onClickListener won't get called, which means your fragment won't know what's going on in your RecyclerView. – Calvin Ku Oct 12 '21 at 15:06
1

I had same Issue and i solve it following way:

The xml file which is using for create a Row inside createViewholder, just add below line:

 android:clickable="true"
 android:focusableInTouchMode="true"
 android:background="?attr/selectableItemBackgroundBorderless"

OR If you using frameLayout as a parent of row item then:

android:clickable="true"
android:focusableInTouchMode="true"
android:foreground="?attr/selectableItemBackgroundBorderless"

In java code inside view holder where you added on click listener:

@Override
   public void onClick(View v) {

    //ur other code here
    v.setPressed(true);
 }
Vikas Tiwari
  • 499
  • 4
  • 11
1

I couldn't find a good solution on the web for this problem and solved it myself. There are lot of people suffering from this problem. Thus i want to share my solution here.

While scrolling, rows are recycled. Thus, checked checkboxes and highlighted rows do not work properly. I solved this problem by writing the below adapter class.

I also implement a full project. In this project, you can select multiple checkboxes. Rows including selected checkboxes are highlighted. And more importantly, these are not lost while scrolling. You can download it from the link :

https://www.dropbox.com/s/ssm58w62gw32i29/recyclerView_checkbox_highlight.zip?dl=0

    public class RV_Adapter extends RecyclerView.Adapter<RV_Adapter.ViewHolder> {
        public ArrayList<String> list;
        boolean[] checkBoxState;
        MainActivity mainActivity;
        MyFragment myFragment;
        View firstview;

        private Context context;

        FrameLayout framelayout;

        public RV_Adapter() {

      }

        public RV_Adapter(Context context, MyFragment m, ArrayList<String> list ) {
          this.list = list;
          myFragment = m;
          this.context = context;
          mainActivity = (MainActivity) context;
          checkBoxState = new boolean[list.size()];
          // relativeLayoutState = new boolean[list.size()];
        }

        public class ViewHolder extends RecyclerView.ViewHolder  {
            public TextView textView;
            public CheckBox checkBox;
            RelativeLayout relativeLayout;
            MainActivity mainActivity;
            MyFragment myFragment;
            public ViewHolder(View v,MainActivity mainActivity,MyFragment m) {
                super(v);
                textView = (TextView) v.findViewById(R.id.tv_foodname);
                /**/
                checkBox= (CheckBox) v.findViewById(R.id.checkBox);
                relativeLayout = (RelativeLayout)v.findViewById(R.id.relativelayout);
                this.mainActivity = mainActivity;
                this.myFragment = m;
                framelayout = (FrameLayout) v.findViewById(R.id.framelayout);
                framelayout.setOnLongClickListener(m);
            }

        }

        @Override
        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            LayoutInflater inflater = LayoutInflater.from(parent.getContext());
            firstview = inflater.inflate(R.layout.row, parent, false);
            return new ViewHolder(firstview,mainActivity, myFragment);
        }

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

            holder.textView.setText(list.get(position));

            holder.itemView.setOnClickListener(new View.OnClickListener() {
              @Override
              public void onClick(View v) {

              }
            });

            // When action mode is active, checkboxes are displayed on each row, handle views(move icons) on each row are disappered.
            if(!myFragment.is_in_action_mode)
            {

              holder.checkBox.setVisibility(View.GONE);
            }
            else
            {
              holder.checkBox.setVisibility(View.VISIBLE);
              holder.checkBox.setChecked(false);
            }

              holder.checkBox.setTag(position);

              holder.checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener(){
                @Override
                public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
                  if(compoundButton.isPressed()) // ekrandan kaybolan checkbox'lar otomatik olarak state degistiriyordu ve bu listener method cagiriliyordu, bunu onlemek icin isPressed() method'u ile kullanici mi basmis diye kontrol ediyorum.
                  {
                    int getPosition = (Integer) compoundButton.getTag();  // Here we get the position that we have set for the checkbox using setTag.
                    checkBoxState[getPosition] = compoundButton.isChecked(); // Set the value of checkbox to maintain its state.
                    //relativeLayoutState[getPosition] = compoundButton.isChecked();

                  if(checkBoxState[getPosition] && getPosition == position )
                    holder.relativeLayout.setBackgroundResource(R.color.food_selected); /** Change background color of the selected items in list view  **/
                  else
                    holder.relativeLayout.setBackgroundResource(R.color.food_unselected); /** Change background color of the selected items in list view  **/
                    myFragment.prepareselection(compoundButton, getPosition, holder.relativeLayout);

                  }
                }
              });
              holder.checkBox.setChecked(checkBoxState[position]);

              if(checkBoxState[position]  )
                holder.relativeLayout.setBackgroundResource(R.color.food_selected); /** Change background color of the selected items in list view  **/
              else
                holder.relativeLayout.setBackgroundResource(R.color.food_unselected);
        }



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

        public void updateList(ArrayList<String> newList){
          this.list = newList;
          checkBoxState = new boolean[list.size()+1];
        }

      public void resetCheckBoxState(){
        checkBoxState = null;
        checkBoxState = new boolean[list.size()];
      }

    }

Screenshots of the app :

screen1 screen2 screen3 screen4

oiyio
  • 5,219
  • 4
  • 42
  • 54
-1

Set private int selected_position = -1; to prevent from any item being selected on start.

 @Override
 public void onBindViewHolder(final OrdersHolder holder, final int position) {
    final Order order = orders.get(position);
    holder.bind(order);
    if(selected_position == position){
        //changes background color of selected item in RecyclerView
        holder.itemView.setBackgroundColor(Color.GREEN);
    } else {
        holder.itemView.setBackgroundColor(Color.TRANSPARENT);
        //this updated an order property by status in DB
        order.setProductStatus("0");
    }
    holder.itemView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            //status switch and DB update
            if (order.getProductStatus().equals("0")) {
                order.setProductStatus("1");
                notifyItemChanged(selected_position);
                selected_position = position;
                notifyItemChanged(selected_position);
             } else {
                if (order.getProductStatus().equals("1")){
                    //calls for interface implementation in
                    //MainActivity which opens a new fragment with 
                    //selected item details 
                    listener.onOrderSelected(order);
                }
             }
         }
     });
}
PMerlet
  • 2,568
  • 4
  • 23
  • 39
LeonD
  • 1
  • 3
-1

Just adding android:background="?attr/selectableItemBackgroundBorderless" should work if you don't have background color, but don't forget to use setSelected method. If you have different background color, I just used this (I'm using data-binding);

Set isSelected at onClick function

b.setIsSelected(true);

And add this to xml;

android:background="@{ isSelected ? @color/{color selected} : @color/{color not selected} }"
Mohamed
  • 1,251
  • 3
  • 15
  • 36
-2

Make selector:

 <?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@color/Green_10" android:state_activated="true" />
    <item android:drawable="@color/Transparent" />
</selector>

Set it as background at your list item layout

   <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:background="@drawable/selector_attentions_list_item"
        android:layout_width="match_parent"
        android:layout_height="64dp">

In your adapter add OnClickListener to the view (onBind method)

 @Suppress("UNCHECKED_CAST")
    inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

        fun bindItems(item: T) {
            initItemView(itemView, item)
            itemView.tag = item
            if (isClickable) {
                itemView.setOnClickListener(onClickListener)
            }
        }
    }

In the onClick event activate the view:

 fun onItemClicked(view: View){
        view.isActivated = true
    }
i_tanova
  • 667
  • 4
  • 5
  • 2
    This has 2 issues: 1) You have not provided any way to undo the activation of other items in the recycler. 2) Views are reused. So it's possible that same activated view will be visible down the order when scrolled. – Anup Sharma Sep 09 '19 at 20:09