How to save button state in RecyclerView after scrolling? - android

Inside onBindViewHolder I set a different state of a button by checking if id of that product is in a specific list:
ArrayList<String> compareProducts = CompareManager
.getProducts(context).getProducts();
if (compareProducts.contains(item.getId())) {
holder.compara.setText(context.getString(R.string.icon_checked));
}
But on scrolling, the checked buttons get messed up, by appearing in different locations. As I understand, when reusing the views, RecyclerView uses the wrong one. But why would that happen if I check by the ID of that product?
Update
#Override
public void onBindViewHolder(final ViewHolder holder, int position) {
// setting up other data from the view
// then change button if product was added in compare before (as mentioned before)
// then set button click listener
holder.compara.setOnClickListener(new View.OnClickListener() {
// change the button state
// and save the ID to CompareManager which saves data to SharedPreferences
//then set holder tag
holder.itemView.setTag(item);

Reset the view to it's default state before checking for item.getId() so if it was checked on the previous recycled view, it wouldn't re-use that and be reset to the default state.

Related

A single listener for multiple list items

I am using a RecyclerView for a list. Each layout item of the RecyclerView has a Button. This button is supposed to save some data(that which I bound to the view).
I thought that since the same button is in every view item, why not have a single static listener instance that I can bind to all the buttons. Because that would save some memory.
Now, a few points about the listener(View.OnClickListener()):
It is stored as a static member of my RecyclerView.Adapter class and gets instantiated once when the data is being bound for the first item.
The instance is created as an anonymous instance and inside a method( onBindViewHolder(ViewHolder, int position))
Inside the onClick(View v) method of the listener, I access the data to be bound. This data is retrieved from the list of data objects stored as Adapter source using position provided in this method( onBindViewHolder(ViewHolder, int position)).
Now, the problem:
When I click on button and check what item is going to be saved by the listener, I find that the first data entry in the RecyclerView is the one that gets saved corresponding to the clicks of any of the items of the RecyclerView.
I have no idea why this is happening. Can anybody find the cause of it?
Secondly, is this a good strategy to have a single listener for multiple list items?
Note: I am unable to post the code for it. Sorry about that.
The best way to handle RecyclerView clicks is like this:
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = layoutInflater.inflate(R.layout.list_item, parent, false);
final ViewHolder viewHolder = new ViewHolder(view);
viewHolder.actionButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
int itemPosition = viewHolder.getAdapterPosition();
handleActionButtonClick(itemPosition);
}
});
return viewHolder;
}
It is simple and robust. Having single click listener for all the items is an extreme example of over optimisation. These few extra bytes will never matter and it will come with cost of extra boilerplate.
If you are getting the response based upon the first item of the Recyclerview, then to get the actual postion use getAdapterPosition() at ViewHolderclass or onBindView() and getTag() might as well help to get the exact data
Since RecyclerView(as its name suggest) recycles its child views, so there is no memory related issues.

how to get RecyclerView highlighed selection changed from outside of RecyclerView?

I have made horizontal RecyclerView to show images where you select different actions by touching image. I have also highlighted current active selection by using background drawable and set that on onBindViewHolder.
public void onBindViewHolder(final MyViewHolder holder, final int position) {
holder.itemView.setSelected(selectedPos == position);
holder.imageView.setImageResource(horizontalList.get(position).imageId);
then i have button which is not part RecyclerView so you can also roll selection in recyclerview to next one by pressing it.
It's working fine except highlighting doesn't change when pressing button.
I can test that when selecting currently selected item, i get my instructions activity opened otherwise different activity will be opened.
So how i can get that current selected item highlight when pressing a button??
update 1.
I have in my activity under myButtonOnClick
int mItemCount = horizontalAdapter.getItemCount() - 1;
horizontalAdapter.notifyItemChanged(selectedItem);
if (selectedItem == mItemCount) {
selectedItem = 0;
} else {
selectedItem += 1;
}
horizontalAdapter.notifyItemChanged(selectedItem);
which doesn't do much.
update 2.
Found the problem which was at my onBindViewHolder as i declared there different variable which messed things up.
so horizontalAdapter.notifyItemChanged(selectedItem); worked just fine and my
onBindViewHolder now i have holder.itemView.setSelected(selectedItem == position);
so manipulate same variable from both button and recyclerview to get things right.
You can add an OnScrollListener to your RecyclerView to observe any change in scroll state and position.
ref to this
If it doesn't help, you may paste your code inside the button.OnClickListener for further discussion.

How do I iterate through all the viewholders in a recyclerview adapter?

I have a fragment with a RecyclerView with some records databound to it. Each record is a product that is part of a "combo" special (bud light, miller light, tuna sandwich, etc.). Basically, the customer is selecting products for a combo (6 domestic beers, 2 sandwiches and 1 drink, etc.) Each product has a set of "product groupings" (domestic beer, beer, sandwich, lunch item, etc.). Next to each item I have a plus and minus button to add/subtract items into the shopping cart. Inside my adapter, I need to check if the customer has selected all of his "domestic beers" or both of his "sandwiches". if certain group quantity totals values are over the total number of that group allowed in the combo, I need to disable the add buttons for those items.
ComboItemsAdapter.java
#Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
...
itemHolder.tvQuantity.setText("0");
...
itemHolder.buy_subtract_button.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View v) {
...
checkTotals();
}
}
itemHolder.buy_add_button.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View v) {
...
checkTotals();
}
}
}
private void checkTotals(){
//here I want to check some stuff, and if that stuff is true I want to disable all the buttons in that viewholder, some buttons will be disabled, some won't
}
So my question is what is the best way to iterate through each row in my recyclerview/adapter in the checkTotals() function and disable the appropriate buttons?
Pass the itemHolder to the checkTotal() function and under that function, perform your condition statement to enable or disable the click.
Actually the Best place to check for a condition is inside onBindViewHolder itself!
Since you going to adjust UI of each row based on some condition, you have to do it in onBindViewHolder; since it is the method that get called when a row is recycled and going to be assigned new data..
P.S: However be aware that doing complex calculation inside onBindViewHolder is not advised as this method must executed as fast as possible, if your condition is a complex one please do it inside your adapter constructor and cache the result for each row.. Later just adjust your view inside onBindViewHolder based on the result.

How to set textView in a recyclerView at desired position

I have a recyclerView in which i have a list of items displayed in a LinearLayout.There is a "increase" button in every list which will increment a quantity by "1".But when I click the button on the first list item to increment the number..the incremented value is displayed in the last list item of the recyclerView not in the desired position where i clicked.Can anyone help me find the solution?
#Override
public void onBindViewHolder(MyViewHolder holder, int position) {
myholder = holder;
holder.item_name_text.setText(data.get(position).getName());
holder.item_price_text.setText(data.get(position).getPrice().toString());
holder.item_quantity_text.setText("500");
//Adding item to the cart
holder.add_image.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(totalItem>=0 && totalItem<10){
totalItem++;
myholder.item_totalquantity_text.setText(String.valueOf(totalItem));
}else{
Toast.makeText(myholder.itemView.getContext(),"Cannot add more item",Toast.LENGTH_SHORT).show();
}
}
});
So I always think of recyclerviews as the frontend that displays my data. If you would like to make changes to the actual data itself and make sure it gets reflected in the recyclerview, you will need to ensure the changes are made inside the arraylist of data objects.
You will need to add a field inside the data object and call it counter. Then you must increment the counter once the user clicks on the onClickListener.
myholder.item_totalquantity_text.setText(data.get(position).setCounter(data.get(position).getCounter())+1);
Do not set OnClickListener() in onBindViewHolder(). Instead do it in your ViewHolder class itself. And main ly as RecyclerView re-uses the holder objects to represent the new set of data that is becoming visible. So the listener on which yo click might be belonging to some other view holder so it appears there instead.
Have a quick read of this and this. These are not very relevant to you but it has info to understnad recycling concept so you can fix your stuff easily.

Items ImageButton drawable change upon click in ListView seems to change for 2 items at a time

I know this question has been asked before and got an understanding roughly of whats happening but i cant seem to find a solution.
In my Custom List Adapter and inside public View getView(int position, View convertView, ViewGroup parent) { ive setup a click function for the items ImageButton.
final ImageButton bookmark = (ImageButton)
convertView.findViewById(R.id.bookmarkthis);
bookmark.setTag(position);
bookmark.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
bookmark.setImageResource(R.drawable.ic_bookmarked);
bookmark.setTag(R.drawable.ic_bookmarked);
}
});
The Imagebutton is a clear star and when Clicked it changes that same drawable to a yellow star. It works Ok.
Problem is when i click Item in Position 0 Star Icon to make yellow , it also changes for item 8 further down the list not in View Yet. if i click position 1 changes also for position 9 and so on.
I had a look around and researched the issue and even tried a holder for the Imagebutton but no go. Something is preventing the drawable change for the ImageButton to its correct Position only.
Thanks
Solution is to initially Set a flag item in the array for each Item. Then onClick set the flag to true just for that item. Then in the getView its just a case of an if statement to check the flag as the Items get Cycled.
//in array creation
items.setFlag("false");
//in getView as you set Text and what ever get the Flag state
String flag = m.getFlag();
//check the flag state and take action in this case change the icon accordingly
if (Objects.equals(flag, "true")) {
bookmark.setImageResource(R.drawable.ic_bookmarked);
bookmark.setTag(R.drawable.ic_bookmarked);
}
else {
bookmark.setImageResource(R.drawable.ic_bookmark);
}
// and in the click function
#Override
public void onClick(View v) {
bookmark.setImageResource(R.drawable.ic_bookmarked);
m.setFlag("true");
}
You need to reset your bookmark image resource & tag values a non-null convertView is passed in to getView. Your onClick handler is setting them to these values:
bookmark.setImageResource(R.drawable.ic_bookmarked);
bookmark.setTag(R.drawable.ic_bookmarked);
So you'll need to reset them to the default values when showing a new list item.
ListView reuses the views that fall out of range. You don't reset the "bookmark" icon when reusing the views (when you get a non-null convertView). Make sure to always reset all properties of your views to the correct values, and you won't have a problem.

Categories

Resources