Selected items in RecyclerView change on scrolling - java

I have a RecyclerView with each element representing an event. I want to let the user select events by clicking it. Once selected, the event(s) and a report button will be colored:
UI before performing a click: click here.
UI After performing a click: click here.
It's pretty simple and allegedly works; I set an OnClickListener for each ViewHolder which is responsible for coloring the item, and when fired it's triggering another event in the owning activity named onOccurrenceSelected, which is responsible for changing the button's state.
However, when scrolling through the RecyclerView's items, other irrelevant items are colored like their OnClickListener was triggered (though it wasn't), and when scrolling back the selected event is colored as not selected. While this is happening, the only event that's supposed to color the items is not triggered.
Any explanation for such behavior? Thanks!
EDIT: Here are some relevant code from the adapter:
private List<Occurrence> mDataSet;
private Activity activity;
public <OccurrencesActivity extends OnOccurrenceSelectedListener> OccurrencesAdapter(OccurrencesActivity occurrencesActivity, List<Occurrence> occurrences) {
this.activity = (android.app.Activity) occurrencesActivity;
mDataSet = occurrences;
}
#Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
Occurrence instance = mDataSet.get(position);
...
setOnClickListener(holder, instance);
}
private void setOnClickListener(final ViewHolder holder, final Occurrence occurrence) {
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (!occurrence.isSelected()) {
holder.itemView.setBackgroundColor(App.getContext().getResources().getColor(R.color.turquoise));
holder.titleTextView.setTextColor(App.getContext().getResources().getColor(R.color.white));
holder.statusTextView.setTextColor(App.getContext().getResources().getColor(R.color.white));
holder.dateTextView.setTextColor(App.getContext().getResources().getColor(R.color.white));
holder.timeTextView.setTextColor(App.getContext().getResources().getColor(R.color.white));
} else {
holder.itemView.setBackgroundColor(App.getContext().getResources().getColor(R.color.white));
holder.titleTextView.setTextColor(App.getContext().getResources().getColor(R.color.turquoise));
holder.statusTextView.setTextColor(App.getContext().getResources().getColor(R.color.grey));
holder.dateTextView.setTextColor(App.getContext().getResources().getColor(R.color.grey));
holder.timeTextView.setTextColor(App.getContext().getResources().getColor(R.color.grey));
}
occurrence.setSelected(!occurrence.isSelected());
((OnOccurrenceSelectedListener)activity).onOccurrenceSelected(mDataSet);
}
});
}

Recyclerview always resuse views while scrolling so you have to store selected positions into temporary arraylist and then keep condition check into onBindViewHolder that whether that particular position is already exists in arraylist or not? I updated your adaper. find the below changes with comment
private List<Occurrence> mDataSet;
private Activity activity;
//Added here temporary ArrayList
private ArrayList<String> mSelectedPosition = new ArrayList<String>;
public <OccurrencesActivity extends OnOccurrenceSelectedListener> OccurrencesAdapter(OccurrencesActivity occurrencesActivity, List<Occurrence> occurrences) {
this.activity = (android.app.Activity) occurrencesActivity;
mDataSet = occurrences;
}
#Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
//Set ViewTag
holder.itemView.setTag(position);
//Check everyposition during view binding process
if(mSelectedPosition.contains(String.valueOf(position))){
holder.itemView.setBackgroundColor(App.getContext().getResources().getColor(R.color.white));
holder.titleTextView.setTextColor(App.getContext().getResources().getColor(R.color.turquoise));
holder.statusTextView.setTextColor(App.getContext().getResources().getColor(R.color.grey));
holder.dateTextView.setTextColor(App.getContext().getResources().getColor(R.color.grey));
holder.timeTextView.setTextColor(App.getContext().getResources().getColor(R.color.grey));
}else{
holder.itemView.setBackgroundColor(App.getContext().getResources().getColor(R.color.white));
holder.titleTextView.setTextColor(App.getContext().getResources().getColor(R.color.turquoise));
holder.statusTextView.setTextColor(App.getContext().getResources().getColor(R.color.grey));
holder.dateTextView.setTextColor(App.getContext().getResources().getColor(R.color.grey));
holder.timeTextView.setTextColor(App.getContext().getResources().getColor(R.color.grey));
}
Occurrence instance = mDataSet.get(position);
...
setOnClickListener(holder, instance);
}
private void setOnClickListener(final ViewHolder holder, final Occurrence occurrence) {
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
// Get Position
int position = (int) view.getTag();
//Remove SelectedPosition if Already there
if(mSelectedPosition.contains(position))
mSelectedPosition.remove(String.valueOf(position));
else
mSelectedPosition.add(String.valueOf(position));
notifyDataSetChanged();
//Not sure about this lines
occurrence.setSelected(!occurrence.isSelected());
((OnOccurrenceSelectedListener)activity).onOccurrenceSelected(mDataSet);
}
});
}

Its the default behaviour of recyclerview. it will recycle/reuse views which are not in use currently. If you want to save the state which is colored or not. Then save a parameter in your List<Object> per position. and as per position in onBindViewHolder method use that position to change the color.

Try by Setting Tag to your item in onBindViewHolder of Adapter
holder.yourItem.setTag(position);
And then Inside the onClickListener,Just save that position in shared Pref. if it's selected, whenever you set adapter then before setting values just check that is it selected or not based on shared Pref. and perform action for same.
public void onClick(View view) {
if (!occurrence.isSelected()) {
//save position in share pref.
}
}

Related

RecyclerView items not repeating, but retaining changes of previous item that was in its slot

I am trying to implement a list of files that can be selected from the RecyclerView Adapter class. While I understand it is not a good idea, I feel if I am able to accomplish this from within said class, it would be really helpful in the future.
My list item (Each individual item view for the RecyclerView) has the following structure:
|--------|----------------|
| ICON | DATA |
|--------|----------------|
Problem:
When a file is selected (by touching the icon portion of a file item), I change the background of that item to another color to denote that it has been selected.
However, when I scroll down to about 25 items later, another file has the same background color even though it's not selected (it does not show up in Log.d as being selected, nor was it in the test ArrayList that was used to store selected files).
It seems as though the item is only retaining the background change of the previous occupant.
Solution attempts:
Previously, only the variables related to each list item were declared in the RecyclerView ViewHolder class and all changes were made in the onBindViewHolder method. Now, all changes to be made have been moved to the ViewHolder class inside a method called bind. There was no change in behavior.
If I set the default background image during the very first step in onBindViewHolder, the behavior changes such that the items do not retain changes of previous occupants. However, on scrolling back, the background change for the target item reverts to the default background image.
Code:
public class RVA extends RecyclerView.Adapter<RVA.RVH>
{
private LayoutInflater inf;
private ArrayList<File> items;
// The var below is used to track the no. of selected items
// globally within the RVA class.
private int numberOfSelectedItems = 0;
public RVA(ArrayList<File> _items)
{
items = _items;
}
#Override
public RVA.RVH onCreateViewHolder(ViewGroup parent, int viewType)
{
inf = LayoutInflater.from(parent.getContext());
return new RVH(inf, parent);
}
#Override
public void onBindViewHolder(RVA.RVH holder, int position)
{
File listItem = items.get(position);
// 'binding' each file element to a respective host container.
holder.bind(listItem);
}
#Override
public int getItemCount()
{
return items.size();
}
#Override
public long getItemId(int position)
{
return position;
}
// The ViewHolder class.
// Initially it was just declared as class.
// There was no change observed after the 'final' modifier was added.
final class RVH extends RecyclerView.ViewHolder
{
private Context context;
private LinearLayout itemSelector;
private ImageView itemIcon;
private TextView itemName;
private TextView itemSize;
public RVH(LayoutInflater inf, ViewGroup parent)
{
super(inf.inflate(R.layout.list_item, parent, false));
context = parent.getContext();
// This is the SECOND outermost LinearLayout of each file item View.
// It was previously the parent Layout, but there was no difference due to change.
itemSelector = itemView.findViewById(R.id.item_selector);
// This is the icon ImageView.
itemIcon = itemView.findViewById(R.id.item_icon);
// These are the data TextViews.
itemName = itemView.findViewById(R.id.item_id);
itemSize = itemView.findViewById(R.id.item_size);
}
// The 'bind' method that registers changes.
public void bind(File fileItem)
{
String listItemName = fileItem.getName();
itemName.setText(listItemName);
//---- These are just changes to the icons depending on type. Works fine.
if(fileItem.isDirectory())
{
itemIcon.setImageResource(R.drawable.directory_icon);
itemSize.setText("Directory");
}
else
{
itemSize.setText(fileItem.length() + " B");
if(listItemName.endsWith(".jpg") || listItemName.endsWith(".jpeg") || listItemName.endsWith(".png") || listItemName.endsWith(".gif"))
{
Glide.with(context).load(fileItem).centerCrop().into(itemIcon);
}
else
{
itemIcon.setImageResource(R.drawable.file_icon);
}
}
//---- END
//---- This is the code which handles opening files according to type. Works fine.
itemSelector.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View v)
{
if(numberOfSelectedItems == 0)
{
if(!itemSize.getText().toString().endsWith(" B"))
{
Intent loadListItemIntent = new Intent(context, DirectoryViewActivity.class);
loadListItemIntent.putExtra("ITEMPATH", fileItem.getPath());
context.startActivity(loadListItemIntent);
}
else
{
if(itemName.getText().toString().endsWith(".jpg") || itemName.getText().toString().endsWith(".jpeg") || itemName.getText().toString().endsWith(".png") || itemName.getText().toString().endsWith(".gif"))
{
Glide.with(context).load(fileItem).centerCrop().into(itemIcon);
Intent loadListItemIntent = new Intent(context, ImageActivity.class);
loadListItemIntent.putExtra("ITEMPATH", fileItem.getPath());
context.startActivity(loadListItemIntent);
}
else
{
Toast.makeText(context, "File needs proper application.", Toast.LENGTH_SHORT).show();
}
}
}
}
});
//---- END
//---- !!! THIS SECTION is where the problem manifests.
itemIcon.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View v)
{
if(itemIcon.getTag().toString().equals("not_selected"))
{
// Incrementing based on selection.
++numberOfSelectedItems;
// Using a tag to identify/ denote whether item is selected.
itemIcon.setTag("selected");
// Changing the background & disabling file opening while in selection mode.
itemSelector.setBackgroundResource(R.drawable.list_item_selected);
itemSelector.setClickable(false);
itemSelector.setLongClickable(false);
// I use this odd method to send a message to the host Activity
// that we have entered selection mode & that the Activity should
// display some option buttons on the Action Bar.
if(context instanceof DirectoryViewActivity)
{
((DirectoryViewActivity)context).addSelectedItem(fileItem);
if(numberOfSelectedItems == 1)
{
((DirectoryViewActivity)context).setSelectionMode();
}
}
}
else
{
// Decrementing based on deselection.
--numberOfSelectedItems;
// Overwiting the tag to identify/ denote item is now unselected.
itemIcon.setTag("not_selected");
// Background changed back to default & file opening re-enabled.
itemSelector.setClickable(true);
itemSelector.setLongClickable(true);
itemSelector.setBackgroundResource(R.drawable.list_item_background);
// I use this method to send a message to the host Activity
// that we have exited selection mode & that the Activity should
// remove related option buttons from the Action Bar.
if(context instanceof DirectoryViewActivity)
{
((DirectoryViewActivity)context).removeSelectedItem(fileItem);
if(numberOfSelectedItems == 0)
{
((DirectoryViewActivity)context).voidSelectionMode();
}
}
}
}
});
}
}
}
This is because RecyclerView does not create views for all of your items in the list it create enough ViewHolder to fill up the screen and few more and when you scroll the old ViewHolder are bind to some other data in the adapter that is when the onBindViewHolder() is called , so basically what is happening here is you are setting the background of current ViewHolder on the screen and when you scroll the same ViewHolder in bind to the new data.
I think you have to check in the onBindViewHolder whether or not this is the item to which you want to set the background and then take the decision remove it if the item is not selected in the dataset set background if it is selected.

How to make single selection work in a nested RecyclerView

For our application I had to implement a nested RecyclerView. I'm getting a list of Tables from JSON and every Table has another list with groups from each table. I can get everything on the screen as requested, the problem is the selection.
I have 2 different RecyclerViews on the screen and I can not seem to get a single selection working in this environment, especially after scrolling. Every group and every table has a Toggle Button, and only one can be active at a time.
This is how the main screen looks like
So far I've tried putting a boolean isSelected on the Model but that didn't work out at all. The closest solution I came up with was a helper class that searches every CompoundButton on-screen and deselects them all when one is selected. The problem is this helper class cant get the Buttons which are off-screen.
How I populate ParentAdapter (in MainActivity):
public void setAdapter(List<Table> tableList)
{
RecyclerView recycler_view_parent = findViewById(R.id.recyclerparent);
LinearLayoutManager manager=new LinearLayoutManager(MainActivity.this);
manager.setOrientation(LinearLayoutManager.HORIZONTAL);
recycler_view_parent.setLayoutManager(manager);
recycler_view_parent.setHasFixedSize(true);
recycler_view_parent.setItemViewCacheSize(tableList.size());
ParentAdapter parentAdapter=new ParentAdapter(tableList,MainActivity.this);
recycler_view_parent.setAdapter(parentAdapter);
}
How i populate ChildAdapter (in onBindViewHolder of ParentAdapter):
FlexboxLayoutManager manager = new FlexboxLayoutManager(context);
manager.setFlexDirection(FlexDirection.COLUMN);
manager.setJustifyContent(JustifyContent.FLEX_START);
manager.setFlexWrap(FlexWrap.WRAP);
manager.setAlignItems(AlignItems.BASELINE);
holder.recycler_view_child.setLayoutManager(manager);
holder.recycler_view_child.setHasFixedSize(true);
adapter = new ChildAdapter(tableList, tableList.get(position).getGroups(), context);
holder.recycler_view_child.setAdapter(adapter);
The desired output should be only 1 Table OR Group at a time can be toggled (in total, not one from every RecyclerView) and the state should be the same after scrolling/device rotation).
I did a lot of research over the last days on this subject and I can not seem to find a working example of nested RecyclerView with single selection over both RVs.
So does anyone have an idea on how to solve this? I think the biggest issue is telling the Parent that a Button in Child was toggled and vice-versa.
I think for the ParentAdapter it should look something like this:
#Override
public void onBindViewHolder(#NonNull RecyclerView.ViewHolder holder, final int position) {
final Table table = tablelist.get(position);
ViewHolder viewHolder = (ViewHolder) holder;
if (table.isTableSelected()) {
viewHolder.toggletable.setChecked(true);
lastToggled = position;
} else {
viewHolder.toggletable.setChecked(false);
}
viewHolder.toggletable.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
if (b) {
table.setTableSelected(true);
// notify ChildAdapter and group.setGroupSelected(false)
if (lastToggled >= 0) {
tablelist.get(lastToggled).setTableSelected(false);
// notify ChildAdapter and group.setGroupSelected(false)
notifyItemChanged(lastToggled);
}
lastToggled = position;
} else {
table.setTableSelected(false);
}
}
});
}
Thanks in advance.
UPDATE: Managed to come up with a solution myself, although 100% sure, not the best approach.
First of all, implement Greenrobots EventBus:
implementation 'org.greenrobot:eventbus:3.1.1'
Now in the Activity where you hold both RecyclerViews register the Event listener:
#Override
protected void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
and subscribe 2 methods. One for Parent Events and one for Children Events. This methods will trigger every time an item is selected!
#Subscribe(threadMode = ThreadMode.MAIN)
public void onParentEventClicked(ParentAdapter.ParentEvent event) {
// to access the inner adapter here you must set it to public in the ParentAdapter(public ChildAdapter adapter;)
adapter.adapter.deSelectChild();
}
#Subscribe(threadMode = ThreadMode.MAIN)
public void onChildEventClicked(ChildAdapter.ChildEvent event) {
// normal ParentAdapter reference(ParentAdapter adapter;)
adapter.deSelectParent();
}
Inside your ParentAdapter create a method to deselect all parent items and a static class to fire the event:
public void deSelectParent()
{
for (int i=0;i<data.size();i++)
{
data.get(i).setSelected(false);
}
notifyDataSetChanged();
}
public static class ParentEvent {
View view;
int position;
}
Inside your ChildAdapter create a method to deselect all child items and a static class to fire the event:
public void deSelectChild()
{
for (int i=0;i<data.size();i++)
{
datachild.get(i).setSelected(false);
}
notifyDataSetChanged();
}
public static class ChildEvent {
View view;
int position;
}
now in both Parent and Child onBindViewHolders, you need similar logic for your models:
if (item.isSelected()) {
holder.yourbutton.setChecked(true);
} else {
holder.yourbutton.setChecked(false);
}
holder.yourbutton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
ParentEvent event = new ParentEvent();
event.view = holder.yourbutton;
event.position = position;
EventBus.getDefault().post(event);
if (holder.yourbutton.isChecked()) {
for (int i = 0; i < data.size(); i++) {
data.get(i).setSelected(false);
}
data.get(position).setSelected(true);
} else {
data.get(position).setSelected(false);
}
notifyDataSetChanged();
}
});
And thats pretty much it, every click on a ParentItem will trigger the deselect method for ChildAdapter and vice-versa.
Due to the high usage of notifyDataSetChanged() I recommend using this line to get rid of the blinking:
recycler_view_parent.setItemAnimator(null);
Any problems let me know!

Having 2 Recyclerview in one layout. The vertical recyclerview with checkbox is either not checking or checking other position

I have two recyclerview in one layout. The vertical recyclerview shows all the user and the horizontal recyclerview shows the selected user from vertical recyclerview. The vertical recyclerview has a checkbox(clickable=false) to know that the user picks it and to do that I created an interface for vertical recyclerview, an ItemCheck and for the horizontal I created an interface of onItemClick. So my plan is when user click in vertical recyclerview it will add the item on the horizontal view and when the user unCheck it, the item will be remove to the horizontal recyclerview. And in the horizontal recyclerview, when the user clicks the item, the item will be remove in the selected user and in the vertical recyclerview it will uncheck the checkbox. This is the code for doing that.
EDIT:
In the Activity:
private void setUpAdapter() {
mUsersAdapter = new PickMemberAdapter(PickMemberActivity.this, mUserNameList,
mUserDescList, mUserPicList, new PickMemberAdapter.RecyclerViewItemClick() {
#Override
public void OnItemCheckClickListener(PickMemberAdapter.UsersViewHolders holder,
String name, int position) {
String userKey = mUserKey.get(position);
if (!holder.mCheckBox.isChecked()) {
holder.mCheckBox.setChecked(true);
mSelectedUser.add(userKey);
mSelectedName.add(name);
} else {
holder.mCheckBox.setChecked(false);
mSelectedUser.remove(userKey);
mSelectedName.remove(name);
}
mUsersAdapter.notifyDataSetChanged();
mSelectedAdapter.notifyDataSetChanged();
Toast.makeText(PickMemberActivity.this, mSelectedUser.toString()
+ "\n" + mSelectedName.toString(), Toast.LENGTH_LONG).show();
}
});
mSelectedAdapter = new SelectedUserAdapter(mSelectedName,
new SelectedUserAdapter.RecyclerViewUnselect() {
#Override
public void ItemRemoveClick(String name, int position) {
String userKey = mUserKey.get(position);
mSelectedUser.remove(userKey);
mSelectedName.remove(name);
mUsersAdapter.notifyDataSetChanged();
mSelectedAdapter.notifyDataSetChanged();
Toast.makeText(PickMemberActivity.this, mSelectedUser.toString()
+ "\n" + mSelectedName.toString(), Toast.LENGTH_LONG).show();
}
});
mSearchList.setAdapter(mUsersAdapter);
mSelectedUserList.setAdapter(mSelectedAdapter);
}
In the Adapter
public void onBindViewHolder(#NonNull final UsersViewHolders holder, int position) {
holder.setName(mUserNameList.get(position));
holder.setDesc(mUserDescList.get(position));
holder.setImage(mUserPicList.get(position));
holder.mCheckBox.setOnCheckedChangeListener(null);
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mListener.OnItemCheckClickListener(holder,
mUserNameList.get(holder.getAdapterPosition()), holder.getAdapterPosition());
}
});
}
public interface RecyclerViewItemClick {
void OnItemCheckClickListener(UsersViewHolders holder, String name, int position);
}
Now my problem is the checkbox is either not checking or checking another position.
It should look like this (from Messenger Lite app)
You are updating data of RecyclerView so you should notify adapter. Call notifyDataSetChanged() after you update list.
mSearchList.notifyDataSetChanged();
mSelectedUserList.notifyDataSetChanged();
I solved this problem to maintain separate boolean for check and uncheck in user model and user it onBindViewHolder to update view.

Access a certain button from recyclerview and change it's background image

I'm trying to change the background image of a certain button when clicked in a recyclerView. The button's background image is changing properly but also changing the background of the button found in the 8th..16th..etc row as well. (I am currently populating data in the recycler view using a for-loop for testing)
My code is
#Override
public void onBindViewHolder(final myFirstAdapter.ViewHolder holder, int position) {
firstlistitem listItem = listItems.get(position);
holder.itemView.setTag(position);
holder.btnBookMark.setTag(position);
holder.btnBookMark.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
holder.btnBookMark.setBackgroundResource(R.drawable.bookmarkred);
}
});
}
The background is changing because, its a RecyclerView and it recyclers the view and reuses it. Hence, when you click on 8th position and scroll to bottom, it is reusing the item at 8 position with custom background for other items. Because of this you are getting custom background for later items on scroll.
If you have many items which will have changed background do this.
class ViewHolder extends RecyclerView.ViewHolder{
boolean newBackground = false;
Button button
ViewHolder(View itemView){
...
button.setOnClickListner((v) - > newBackground = true);
}
}
Then you can check the value of newBackground and set the background in onBindViewHolder()
If you have only one item of which the background will change at a time then you can declare a field in the Adapter and check that to change the background.
class MyAdapter extends RecyclerView.Adapter<ViewHolder>{
int newBackgroundPos = -1;
#Override
public final void onBindViewHolder(ViewHolder holder, int position) {
holder.button.setOnClickListener((v) -> newBackGroundPos = position);
if(position == newBackgroundPos)
holder.itemView.setBackground(newBackground);
else
holder.itemView.setBackground(normalBackground);
}
}

Android RecyclerView addition & removal of items

I have a RecyclerView with an TextView text box and a cross button ImageView. I have a button outside of the recyclerview that makes the cross button ImageView visible / gone.
I'm looking to remove an item from the recylerview, when that items cross button ImageView is pressed.
My adapter:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> implements View.OnClickListener, View.OnLongClickListener {
private ArrayList<String> mDataset;
private static Context sContext;
public MyAdapter(Context context, ArrayList<String> myDataset) {
mDataset = myDataset;
sContext = context;
}
#Override
public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.my_text_view, parent, false);
ViewHolder holder = new ViewHolder(v);
holder.mNameTextView.setOnClickListener(MyAdapter.this);
holder.mNameTextView.setOnLongClickListener(MyAdapter.this);
holder.mNameTextView.setTag(holder);
return holder;
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.mNameTextView.setText(mDataset.get(position));
}
#Override
public int getItemCount() {
return mDataset.size();
}
#Override
public void onClick(View view) {
ViewHolder holder = (ViewHolder) view.getTag();
if (view.getId() == holder.mNameTextView.getId()) {
Toast.makeText(sContext, holder.mNameTextView.getText(), Toast.LENGTH_SHORT).show();
}
}
#Override
public boolean onLongClick(View view) {
ViewHolder holder = (ViewHolder) view.getTag();
if (view.getId() == holder.mNameTextView.getId()) {
mDataset.remove(holder.getPosition());
notifyDataSetChanged();
Toast.makeText(sContext, "Item " + holder.mNameTextView.getText() + " has been removed from list",
Toast.LENGTH_SHORT).show();
}
return false;
}
public static class ViewHolder extends RecyclerView.ViewHolder {
public TextView mNumberRowTextView;
public TextView mNameTextView;
public ViewHolder(View v) {
super(v);
mNameTextView = (TextView) v.findViewById(R.id.nameTextView);
}
}
}
My layout is:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:id="#+id/layout">
<TextView
android:id="#+id/nameTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"
android:padding="5dp"
android:background="#drawable/greyline"/>
<ImageView
android:id="#+id/crossButton"
android:layout_width="16dp"
android:layout_height="16dp"
android:visibility="gone"
android:layout_marginLeft="50dp"
android:src="#drawable/cross" />
</LinearLayout>
How can I get something like an onClick working for my crossButton ImageView? Is there a better way? Maybe changing the whole item onclick into a remove the item? The recyclerview shows a list of locations that need to be edited. Any technical advice or comments / suggestions on best implementation would be hugely appreciated.
I have done something similar.
In your MyAdapter:
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
public CardView mCardView;
public TextView mTextViewTitle;
public TextView mTextViewContent;
public ImageView mImageViewContentPic;
public ImageView imgViewRemoveIcon;
public ViewHolder(View v) {
super(v);
mCardView = (CardView) v.findViewById(R.id.card_view);
mTextViewTitle = (TextView) v.findViewById(R.id.item_title);
mTextViewContent = (TextView) v.findViewById(R.id.item_content);
mImageViewContentPic = (ImageView) v.findViewById(R.id.item_content_pic);
//......
imgViewRemoveIcon = (ImageView) v.findViewById(R.id.remove_icon);
mTextViewContent.setOnClickListener(this);
imgViewRemoveIcon.setOnClickListener(this);
v.setOnClickListener(this);
mTextViewContent.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View view) {
if (mItemClickListener != null) {
mItemClickListener.onItemClick(view, getPosition());
}
return false;
}
});
}
#Override
public void onClick(View v) {
//Log.d("View: ", v.toString());
//Toast.makeText(v.getContext(), mTextViewTitle.getText() + " position = " + getPosition(), Toast.LENGTH_SHORT).show();
if(v.equals(imgViewRemoveIcon)){
removeAt(getPosition());
}else if (mItemClickListener != null) {
mItemClickListener.onItemClick(v, getPosition());
}
}
}
public void setOnItemClickListener(final OnItemClickListener mItemClickListener) {
this.mItemClickListener = mItemClickListener;
}
public void removeAt(int position) {
mDataset.remove(position);
notifyItemRemoved(position);
notifyItemRangeChanged(position, mDataSet.size());
}
Edit:
getPosition() is deprecated now, use getAdapterPosition() instead.
first of all, item should be removed from the list!
mDataSet.remove(getAdapterPosition());
then:
notifyItemRemoved(getAdapterPosition());
notifyItemRangeChanged(getAdapterPosition(), mDataSet.size()-getAdapterPosition());
if still item not removed use this magic method :)
private void deleteItem(int position) {
mDataSet.remove(position);
notifyItemRemoved(position);
notifyItemRangeChanged(position, mDataSet.size());
holder.itemView.setVisibility(View.GONE);
}
Kotlin version
private fun deleteItem(position: Int) {
mDataSet.removeAt(position)
notifyItemRemoved(position)
notifyItemRangeChanged(position, mDataSet.size)
holder.itemView.visibility = View.GONE
}
The Problem
RecyclerView was built to display data in an efficient and responsive manner.
Usually you have a dataset which is passed to your adapter and is looped through to display your data.
Here your dataset is:
private ArrayList<String> mDataset;
The point is that RecyclerView is not connected to your dataset, and therefore is unaware of your dataset changes.
It just reads data once and displays it through your ViewHolder, but a change to your dataset will not propagate to your UI.
This means that whenever you make a deletion/addition on your data list, those changes won't be reflected to your RecyclerView directly. (i.e. you remove the item at index 5, but the 6th element remains in your recycler view).
A (old school) solution
RecyclerView exposes some methods for you to communicate your dataset changes, reflecting those changes directly on your list items.
The standard Android APIs allow you to bind the process of data removal (for the purpose of the question) with the process of View removal.
The methods we are talking about are:
notifyItemChanged(index: Int)
notifyItemInserted(index: Int)
notifyItemRemoved(index: Int)
notifyItemRangeChanged(startPosition: Int, itemCount: Int)
notifyItemRangeInserted(startPosition: Int, itemCount: Int)
notifyItemRangeRemoved(startPosition: Int, itemCount: Int)
A Complete (old school) Solution
If you don't properly specify what happens on each addition, change or removal of items, RecyclerView list items are animated unresponsively because of a lack of information about how to move the different views around the list.
The following code will allow RecyclerView to precisely play the animation with regards to the view that is being removed (And as a side note, it fixes any IndexOutOfBoundExceptions, marked by the stacktrace as "data inconsistency").
void remove(position: Int) {
dataset.removeAt(position)
notifyItemChanged(position)
notifyItemRangeRemoved(position, 1)
}
Under the hood, if we look into RecyclerView we can find documentation explaining that the second parameter we pass to notifyItemRangeRemoved is the number of items that are removed from the dataset, not the total number of items (As wrongly reported in some others information sources).
/**
* Notify any registered observers that the <code>itemCount</code> items previously
* located at <code>positionStart</code> have been removed from the data set. The items
* previously located at and after <code>positionStart + itemCount</code> may now be found
* at <code>oldPosition - itemCount</code>.
*
* <p>This is a structural change event. Representations of other existing items in the data
* set are still considered up to date and will not be rebound, though their positions
* may be altered.</p>
*
* #param positionStart Previous position of the first item that was removed
* #param itemCount Number of items removed from the data set
*/
public final void notifyItemRangeRemoved(int positionStart, int itemCount) {
mObservable.notifyItemRangeRemoved(positionStart, itemCount);
}
Open source solutions
You can let a library like FastAdapter, Epoxy or Groupie take care of the business, and even use an observable recycler view with data binding.
New ListAdapter
Google recently introduced a new way of writing the recycler view adapter, which works really well and supports reactive data.
It is a new approach and requires a bit of refactoring, but it is 100% worth switching to it, as it makes everything smoother.
here is the documentation, and here a medium article explaining it
Here are some visual supplemental examples. See my fuller answer for examples of adding and removing a range.
Add single item
Add "Pig" at index 2.
String item = "Pig";
int insertIndex = 2;
data.add(insertIndex, item);
adapter.notifyItemInserted(insertIndex);
Remove single item
Remove "Pig" from the list.
int removeIndex = 2;
data.remove(removeIndex);
adapter.notifyItemRemoved(removeIndex);
Possibly a duplicate answer but quite useful for me. You can implement the method given below in RecyclerView.Adapter<RecyclerView.ViewHolder>
and can use this method as per your requirements, I hope it will work for you
public void removeItem(#NonNull Object object) {
mDataSetList.remove(object);
notifyDataSetChanged();
}
I tried all the above answers, but inserting or removing items to recyclerview causes problem with the position in the dataSet. Ended up using delete(getAdapterPosition()); inside the viewHolder which worked great at finding the position of items.
The problem I had was I was removing an item from the list that was no longer associated with the adapter to make sure you are modifying the correct adapter you can implement a method like this in your adapter:
public void removeItemAtPosition(int position) {
items.remove(position);
}
And call it in your fragment or activity like this:
adapter.removeItemAtPosition(position);
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
private Context context;
private List<cardview_widgets> list;
public MyAdapter(Context context, List<cardview_widgets> list) {
this.context = context;
this.list = list;
}
#NonNull
#Override
public MyViewHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int i) {
View view = LayoutInflater.from(this.context).inflate(R.layout.fragment1_one_item,
viewGroup, false);
return new MyViewHolder(view);
}
public static class MyViewHolder extends RecyclerView.ViewHolder {
TextView txtValue;
TextView txtCategory;
ImageView imgInorEx;
ImageView imgCategory;
TextView txtDate;
public MyViewHolder(#NonNull View itemView) {
super(itemView);
txtValue= itemView.findViewById(R.id.id_values);
txtCategory= itemView.findViewById(R.id.id_category);
imgInorEx= itemView.findViewById(R.id.id_inorex);
imgCategory= itemView.findViewById(R.id.id_imgcategory);
txtDate= itemView.findViewById(R.id.id_date);
}
}
#NonNull
#Override
public void onBindViewHolder(#NonNull final MyViewHolder myViewHolder, int i) {
myViewHolder.txtValue.setText(String.valueOf(list.get(i).getValuee()));
myViewHolder.txtCategory.setText(list.get(i).getCategory());
myViewHolder.imgInorEx.setBackgroundColor(list.get(i).getImg_inorex());
myViewHolder.imgCategory.setImageResource(list.get(i).getImg_category());
myViewHolder.txtDate.setText(list.get(i).getDate());
myViewHolder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
list.remove(myViewHolder.getAdapterPosition());
notifyDataSetChanged();
return false;
}
});
}
#Override
public int getItemCount() {
return list.size();
}}
i hope this help you.
if you want to remove item you should do this:
first remove item:
phones.remove(position);
in next step you should notify your recycler adapter that you remove an item by this code:
notifyItemRemoved(position);
notifyItemRangeChanged(position, phones.size());
but if you change an item do this:
first change a parameter of your object like this:
Service s = services.get(position);
s.done = "Cancel service";
services.set(position,s);
or new it like this :
Service s = new Service();
services.set(position,s);
then notify your recycler adapter that you modify an item by this code:
notifyItemChanged(position);
notifyItemRangeChanged(position, services.size());
hope helps you.
String str = arrayList.get(position);
arrayList.remove(str);
MyAdapter.this.notifyDataSetChanged();
To Method onBindViewHolder Write This Code
holder.remove.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Cursor del=dbAdapter.ExecuteQ("delete from TblItem where Id="+values.get(position).getId());
values.remove(position);
notifyDataSetChanged();
}
});
Incase Anyone wants to implement something like this in Main class instead of Adapter class, you can use:
public void removeAt(int position) {
peopleListUser.remove(position);
friendsListRecycler.getAdapter().notifyItemRemoved(position);
friendsListRecycler.getAdapter().notifyItemRangeChanged(position, peopleListUser.size());
}
where friendsListRecycler is the Adapter name
you must to remove this item from arrayList of data
myDataset.remove(holder.getAdapterPosition());
notifyItemRemoved(holder.getAdapterPosition());
notifyItemRangeChanged(holder.getAdapterPosition(), getItemCount());
//////// set the position
holder.cancel.setTag(position);
///// click to remove an item from recycler view and an array list
holder.cancel.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
int positionToRemove = (int)view.getTag(); //get the position of the view to delete stored in the tag
mDataset.remove(positionToRemove);
notifyDataSetChanged();
}
});
make interface into custom adapter class and handling click event on recycler view..
onItemClickListner onItemClickListner;
public void setOnItemClickListner(CommentsAdapter.onItemClickListner onItemClickListner) {
this.onItemClickListner = onItemClickListner;
}
public interface onItemClickListner {
void onClick(Contact contact);//pass your object types.
}
#Override
public void onBindViewHolder(ItemViewHolder holder, int position) {
// below code handle click event on recycler view item.
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
onItemClickListner.onClick(mContectList.get(position));
}
});
}
after define adapter and bind into recycler view called below code..
adapter.setOnItemClickListner(new CommentsAdapter.onItemClickListner() {
#Override
public void onClick(Contact contact) {
contectList.remove(contectList.get(contectList.indexOf(contact)));
adapter.notifyDataSetChanged();
}
});
}
In case you are wondering like I did where can we get the adapter position in the method getadapterposition(); its in viewholder object.so you have to put your code like this
mdataset.remove(holder.getadapterposition());
In the activity:
mAdapter.updateAt(pos, text, completed);
mAdapter.removeAt(pos);
In the your adapter:
void removeAt(int position) {
list.remove(position);
notifyItemRemoved(position);
notifyItemRangeChanged(position, list.size());
}
void updateAt(int position, String text, Boolean completed) {
TodoEntity todoEntity = list.get(position);
todoEntity.setText(text);
todoEntity.setCompleted(completed);
notifyItemChanged(position);
}
in 2022, after trying everything the whole internet given below is the answer
In MyViewHolder class
private myAdapter adapter;
inside MyViewHolder function initalise adapter
adapter = myAdapter.this
inside onclick
int position = getAdapterPosition()
list.remove(position);
adapter.notifyItemRemoved(position);

Categories

Resources