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

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.

Related

Get all items from adapter (recyclerview) in Fragment

Hello I'm having an issue getting all of the items from my adapter in my fragment. To be more specific I am using a ScaleInAnimatorAdapter along with my Customer Adapter and when I attempt to get my checkbox items from the below posted code, within my Fragment, I only seem to get the visible items on screen.
private View.OnClickListener onAllClick = new View.OnClickListener() {
#Override public void onClick(View v) {
int count = listAdapter.getItemCount();
for (int i = 0; i < count; i++) {
View mChild = listTopics.getChildAt(i);
if( mChild != null ) {
Log.d(TAG,"getItemCount(): " + i );
CheckBox cBox = (CheckBox)mChild.findViewById(R.id.topic_chk);
cBox.setChecked(((CheckBox) v).isChecked());
Log.d(TAG,"isChecked" + cBox.getTag());
cBox.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(((CheckBox) v).isChecked()) {
checkboxAll.setChecked(false);
}
}
});
}
}
}
};
Essentially I am attempting to create a check all feature from the fragment, so that when this is clicked all of the checkbox items from the adapter are checked. I got that to work with the code I presented however the main issue is that I only get the items visible on the screen, so when I scroll to my other items they are not checked. Thus I am wondering if there is a better way or another way for me to get all of the items
Adapters are meant to bind underlying data stores to views; they generally shouldn't be used to store data themselves (except for having a copy of the data for view binding purposes) nor should they perform actions on data.
Instead, you should be modifying the underlying data, then updating the adapter through whatever mechanism you are already using. (Loaders, custom setters with notifyDataItemChanged, etc.).
Basically you can't. It'll be reusing the UI views.
You need to set a flag in all your data list objects, call notifyDataSetChanged() and onBindViewHolder check that flag and use it to check uncheck
if(listAdapter.getItem(position).getIsChecked())
viewHolder.checkBox.setChecked(true);
else
viewHolder.checkBox.setChecked(false);
Ok. Thank you all for those who have replied you helped me grasp something really basic and important that had alluded me.
I got it to work, so for anyone who might read this post, here is my solution:
Create necessary methods and field to update your data in a model (ie, private is_checked, set_checked(), is_checked(), etc)
In your adapter onBindViewHolder, you will set your holder checkbox to be associated to the data so something like:
holder.checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
obj.setSelected(buttonView.isChecked());
}
});
holder.checkBox.setChecked(obj.isSelected());
Then in your fragment or activity I just have a click listener on my main checkbox, and then when checked I loop through my data list, and using the model method I update my data and just simply use listAdapter.notifyDataSetChanged()
You are doing it wrongly.
You should update the adapter instead of updating the check box view.
What you can do , you can create a field in adapter data holder .
And whenever you change the selection,just update the field or multiple field and finally refresh the view.

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.

How to save button state in RecyclerView after scrolling?

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.

How do I retain selected item highlighting on gridview when numColumns changes?

I have an ActionBarActivity with a GridView.
The GridView has 2 columns in portrait and 3 columns in landscape.
When I select items in portrait (starting my ActionMode) and then rotate the device, the selected item highlighting shifts one item to the left. For example, if I select the second item and rotate, the first item will be highlighted. If I select the first item and rotate, no items are highlighted.
The actual selection in the code is correct, just the highlighting is wrong.
I notice it does not do this if I keep the numColumns the same for portrait and landscape.
I believe this issue started occurring after I changed my activity to an ActionBarActivity so it could be a bug..
Anyone know why or how to fix it?
I had a similar scenario and ended up solving the issue be creating a custom grid item with a boolean field to keep track of whether the item is selected or not and then highlighting the item appropriately through the custom adapter. Below is a rough outline of what I did:
(1) I created a custom grid item with a boolean field, which we will call selectedStatus for simplicity's sake. I also added the corresponding methods to my grid item class to get the selected status:
public boolean getSelectedStatus ()
{
return selectedStatus;
}
public void setSelectedStatus (boolean paramSelectedStatus)
{
this.selectedStatus = paramSelectedStatus;
}
(2) I then created a custom Adapter that extends BaseAdapter to handle the custom grid object I created. In this Adapter I check the if the selected status of the grid object is true or false and highlight the item accordingly, shown below:
#Override
public View getView (final int position, View convertView, ViewGroup parent)
{
// rest of getView() code...
if (!yourGridObject.getSelectedStatus())
{
convertView.setBackgroundColor(Color.TRANSPARENT);
}
else
{
convertView.setBackgroundColor(Color.LTGRAY);
}
// rest of getView() code...
return convertView;
}
(3) Lastly, you add the onItemClickListener to set the selected status and the background color of the grid items when they are selected (clicked):
yourGridView.setOnItemClickListener(new OnItemClickListener()
{
#Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id)
{
YourGridObject yourGridObject = (YourGridObject) parent.getItemAtPosition(position);
if (!yourGridObject.getSelected())
{
view.setBackgroundColor(Color.LTGRAY);
yourGridObject.setSelected(true);
}
else
{
view.setBackgroundColor(Color.TRANSPARENT);
yourGridObject.setSelected(false);
}
}
});
Implementing selection this way ensures that the highlighting (selection) of the grid items will not change when the number of columns and rows swap since the selection status is contained within the grid objects themselves.
You don't need to manually handle selection of items as suggested by Willis. Android fully supports what you are asking. I will assume you are using an ArrayAdapter however this answer would apply to all adapters. Note some adapters (like CursorAdapter) won't suffer from your posted problem and don't require the following solution because it's already doing it internally.
The problem is solved in two parts. One, the adapter must enable stable Ids. Two, your adapter must actually return stable ids. You will need to extend the ArrayAdapter or which ever adapter you are using. Then ensure you have defined the following methods as shown below.
private class MyAdapter extends ArrayAdapter<YourObjects> {
#Override
public boolean hasStableIds() {
return true;
}
#Override
public long getItemId(int position) {
//Return a unique and stable id for the given position
//While unique, Returning the position number does not count as stable.
//For example:
return getItem(position).methodThatReturnsUniqueValue();
}
}
Most adapters do not enable hasStableIds. It's primarily only used when enabling a choiceMode. Which I assume you are doing here. By returning true, you are essentially telling Android to keep track of activated (highlighted) items based on their ID value instead of their position number.
Even with stable Ids enabled, you have to actually return an ID that is unique and stable across positional changes. Since most adapters do NOT enable stable IDs, they usually only return the position number as the stable id. Technically, if an item's position never changes over time then the position number "could" be used as the stable id. However, the safest way to return a stable/unique ID is to have one assigned to the class object being stored in the adapter and pull from that.

Spinner onItemSelected with item already selected

I've a Spinner with onItemSelected interation that works, but how the Api specification says:
This callback is invoked only when the newly selected position is
different from the previously selected position or if there was no
selected item.
I need to remove this limitation and i want that the callback is invoked also if the user select the same element.
How to do that? I read a suggestion about extending Spinner class and set the position to INVALID_POSITION, but i've not understood/able to do that.
Anyone did the same thing?
I also needed a solution to this problem. What I wanted to do was have a Spinner that has date ranges with a custom range option. The rows would look something like this:
Apr 10 - May 10
Mar 10 - Apr 10
Feb 10 - Mar 10
Custom Range
The problem is that if the user selects a custom range and then wants to change their custom range, they have to select a different range and then select the custom range option again. I wanted the user to just be able to select "Custom Range" again so that the custom range dialog could be shown again.
I sub-classed Spinner and created my own listener. The code switches the selection, but then immediately switches it so that nothing is selected. In my listener I just ignore any position that is less than zero.
The Spinner just displays the last selected item. I created my own custom adapter and specify what to display for each view, but that shouldn't be necessary. Here is how I sub-classed Spinner.
package com.example.widget;
import android.content.Context;
import android.widget.Spinner;
public class DateRangeSpinner extends Spinner {
private ItemSelectionListener listener;
public DateRangeSpinner(Context context) {
super(context);
}
/**
* This listener will be fired every time an item is selected,
* regardless of whether it has already been selected or not.
*
* #param l
*/
public void setOnItemSelectedListener(ItemSelectionListener l) {
listener = l;
}
public void removeOnItemSelectedListener() {
listener = null;
}
#Override
public void setSelection(int position) {
setSelection(position, true);
}
#Override
public void setSelection(int position, boolean animate) {
if (listener != null) {
listener.onItemSelected(position);
}
super.setSelection(position, animate);
super.setSelection(-1, animate);
}
public interface ItemSelectionListener {
public void onItemSelected(int position);
}
}
I hope this helps!
You can do this by a custom adapter, like create a layout of your desire views, then inflate this in custom adapter then on onItemClick function you can get the view by this function.
To distinguish each view you must to set the tag of each row.
It probably works in your condition.
Let me know any issue if you have
why you have select the selected item again. Just give a refresh button if you want to perform that task again.
This is the spinner Element with custom dialog mode and whitout promt:
<Spinner
android:id="#+id/spinner_metatag"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:entries="#array/search_adv"
/>
The Array element where the default value is putted at position 0:
<string-array name="search_adv">
<item>#string/search_adv_prompt</item>
<item>#string/search_adv_title</item>
<item>#string/search_adv_desc</item>
<item>#string/search_adv_autore</item>
....
</string-array>
The String elements for the array with the default value:
<string name="search_adv_prompt">Scegli un metatag</string> <!-- Default value-->
<string name="search_adv_title">Titolo</string>
<string name="search_adv_desc">Descrizione</string>
<string name="search_adv_autore">Autore</string>
...
And here the code to prevent the event fired on onCreateMethod and the work around to permit to select the same element already selected:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.search_adv_main);
spinner = (Spinner) findViewById(R.id.spinner_metatag);
spinner.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent, View view, int pos,long id) {
//prevent onCreate event fire and the loop
if(pos==0)
return;
//HERE YOUR CODE
//at the end move to the default element the spinner
spinner.setSelection(0);
}
#Override
public void onNothingSelected(AdapterView<?> arg0) {}
});
}
Hope helps. The idea come from the second solution from TreKing's answer
Use OnItemClickListener instead of on itemSelectedListener.That will work for every click whether it is same or different.
I guess you must be storing the value in a variable, initialize the vairable with -1. And change the value as the user selects the item spinner, if the value is -1 ask the user to reselect or whatever u want.
#i want that the callback is invoked also if the user select the same element.
Android will do it for u as this is the default behavior of android.

Categories

Resources