How to properly highlight selected item on RecyclerView?
Asked Answered
A

16

184

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);

}
Alienor answered 28/11, 2014 at 18:13 Comment(5)
Using focusable views is not a good idea for trying to track selected item. Check my answer for a complete solutionArrowroot
may b this help u amolsawant88.blogspot.in/2015/08/…Dour
Recycler view item selection : https://mcmap.net/q/137461/-recyclerview-scroll-to-position-and-get-that-viewRendon
This is hard to follow when you don't have anything already working, which is bad since the answers tag along and don't specify much about what goes where.Lalo
Hey where did you place this OnClick function, I am trying to figure out the same.Phia
A
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);
                }
            });
        }
    }
} 
Arrowroot answered 3/3, 2015 at 18:8 Comment(9)
mRecyclerView is not declared anywhere. Should we just pass it as a parameter in the constructor and store it in a field?Aldridge
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.Arrowroot
Nice answer, but why use getChildPosition()? There is another method developer.android.com/reference/android/support/v7/widget/…Mcgrath
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: #29696311Melodimelodia
Can you put any example full to debug ? more people not know this form see (google.es/…) please put any example thxRoughish
How do I use this? I have already MyAdapter class. How should I use this one?Campagna
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.Kinkajou
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 updateEachelle
@Eachelle 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.Aleda
A
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.

Allomorph answered 5/5, 2015 at 7:15 Comment(10)
@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?Shoreward
@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?Shoreward
I guess setting selectedPos as -1 rather than 0 would solve that issueInfinitive
getLayoutPosition() isn't available by me.Powell
Don't just use -1, use RecyclerView.NO_POSITION; (which is -1)Ine
@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.getLayoutPositionBarbarian
@Gaurav Sarma Do you mean the "int" variable should be set up like this: "private int selectedPos = RecyclerView.NO_POSITION"? rather than "... selectedPos = -1"?Shoreward
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.Jodhpurs
This gives me issues. If an item is clicked again, it is replaced my another in the recycler view.Martlet
Nice stuff, but the app crashes using RecyclerView.NO_POSITION, after setting selectedPos = 0, solved the crash.Chromatophore
P
154

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.

Preachment answered 12/2, 2016 at 10:32 Comment(25)
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); }Panorama
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)Preachment
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.Panorama
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.Preachment
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 phoneAbstention
@Abstention Are you using RecyclerView inside the ScrollView?Preachment
No, it's inside a linear layoutAbstention
@Abstention Can you post here your Adapter class? I think your inflated layout can be complex..Preachment
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 lotAbstention
Thanks for your helpAbstention
@Panorama 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)Blessington
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.Moynahan
@Spacemonkey Thanks for your feedback. I will check and let you know that stuff later as I am currently busy.Preachment
What is Item for an object? I cant find it anywhere.@MeetArne
'item' is the fetched Object from the 'collection of Objects' (i.e. I am having an ArrayList<Item> 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); @MuddzPreachment
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.Frankly
@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. :)Preachment
@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.Frankly
@Davidea Thanks for advice. I will have look at this(dirty variable) later. ;)Preachment
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.Defray
@Defray Thanks for pointing out that, I have updated my answer.Preachment
Super working like a champ, great answer to reducing the redundant code.Amarillo
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.Vertebrate
I couldn't access selected_position inside ViewHolder so I add the onClick listener directly to onBindViewHolder.Sample
@Sample 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.Preachment
A
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);
                }
            });
        }
    }
} 
Arrowroot answered 3/3, 2015 at 18:8 Comment(9)
mRecyclerView is not declared anywhere. Should we just pass it as a parameter in the constructor and store it in a field?Aldridge
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.Arrowroot
Nice answer, but why use getChildPosition()? There is another method developer.android.com/reference/android/support/v7/widget/…Mcgrath
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: #29696311Melodimelodia
Can you put any example full to debug ? more people not know this form see (google.es/…) please put any example thxRoughish
How do I use this? I have already MyAdapter class. How should I use this one?Campagna
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.Kinkajou
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 updateEachelle
@Eachelle 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.Aleda
A
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;
}
Aleda answered 2/12, 2014 at 1:22 Comment(2)
There's no parent field in holder.Inarticulate
@Inarticulate 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.Aleda
F
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.

Frankly answered 6/5, 2015 at 16:3 Comment(3)
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!Renn
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: github.com/davideas/FlexibleAdapter/issues/120Renn
Wiki has been completely rewritten and is on the way of completion.Frankly
C
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);
    }
}
Conjoined answered 9/1, 2015 at 20:43 Comment(4)
Do you think mSelectedPosition can be declared as static to maintain configuration changes?Cheesecloth
No! it is wrong to make it as static it is very wrongConjoined
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?Cheesecloth
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/FragmentConjoined
V
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"
Veronaveronese answered 6/5, 2015 at 16:10 Comment(1)
This does nothing useful. It will just change the colour doing the press time.Enterogastrone
V
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

Vagabond answered 28/1, 2016 at 13:49 Comment(0)
A
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);
                }
            }
        });
    }
Arrowworm answered 9/11, 2016 at 2:35 Comment(0)
C
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.

Culp answered 21/10, 2020 at 8:15 Comment(0)
C
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();
    
Conjunct answered 11/9, 2021 at 15:2 Comment(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.Sapor
G
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);
 }
Gates answered 26/7, 2016 at 12:45 Comment(0)
L
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

Lyte answered 10/10, 2017 at 22:32 Comment(0)
O
-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);
                }
             }
         }
     });
}
Obverse answered 8/2, 2017 at 17:7 Comment(0)
C
-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} }"
Cardiograph answered 30/5, 2017 at 21:28 Comment(0)
A
-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
    }
Askew answered 28/5, 2019 at 13:47 Comment(1)
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.Zimmermann

© 2022 - 2024 — McMap. All rights reserved.