I am using a Custom List Adapter with ViewHolder pattern to inflate views into my List that shows an image (width = match_parent
), some text on the left (below the image) and a button on the right(also below the image).
Here is the code for the adapter class -
public class DishItemListAdapter extends ArrayAdapter<DishItem> {
//declare fonts - BOLD and LIGHT
final Typeface tf_light = Typeface.createFromAsset(getContext().getAssets(),
"fonts/Roboto-Thin.ttf");
final Typeface tf_bold = Typeface.createFromAsset(getContext().getAssets(),
"fonts/Roboto-Regular.ttf");
//get item count
CartItemCount cartItemCount;
//count for dish at particular position
ArrayList<Integer> dishCountList = new ArrayList<>();
//for matching key string in SharedPrefs
String existingKeyString;
Typeface font_light, font_bold;
/* List of DishItem Objects shown on the Dashboard */
private List<DishItem> DishItemList = new ArrayList<DishItem>();
public DishItemListAdapter(Context context, int textViewResourceId) {
super(context, textViewResourceId);
this.font_bold = tf_bold;
this.font_light = tf_light;
}
/* Add a New DishItem Item (object) to the list of DishItems on the Dashboard i.e. DishItemList */
@Override
public void add(DishItem object) {
DishItemList.add(object);
super.add(object);
}
@Override
public int getCount() {
return this.DishItemList.size();
}
@Override
public DishItem getItem(int index) {
return this.DishItemList.get(index);
}
@Override
public View getView(final int position, final View convertView, ViewGroup parent) {
Log.e("getView() at " + position, "");
View row = convertView;
final DishItemViewHolder viewHolder;
// A ViewHolder keeps references to children views to
// avoid unnecessary (and expensive) calls to findViewById() on each row.
if (row == null) {
LayoutInflater inflater = (LayoutInflater) this.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
row = inflater.inflate(R.layout.list_item_dish, parent, false);
//instantiate DishItem View Holder
viewHolder = new DishItemViewHolder();
//get BUTTON for Adding Dish to Cart
viewHolder.addToCart = (Button) row.findViewById(R.id.add_to_cart_button);
//BUTTONS for + and - (adding and removing items from cart)
viewHolder.addItemButton = (Button) row.findViewById(R.id.increase_item_count);
viewHolder.removeItemButton = (Button) row.findViewById(R.id.decrease_item_count);
//DISH NAME, CHEF NAME, DISH PRICE and DISH IMAGE
viewHolder.dishName = (TextView) row.findViewById(R.id.dish_name_textview);
viewHolder.chefName = (TextView) row.findViewById(R.id.chef_name_textview);
viewHolder.dishPrice = (TextView) row.findViewById(R.id.dish_price_textview);
viewHolder.dishImage = (ImageView) row.findViewById(R.id.dish_imageview);
//image absolute path
viewHolder.imageStorePath = new String();
//image for depicting whether image is VEG or NON VEG
viewHolder.veg_nonveg_indicator = (ImageView) row.findViewById(R.id.veg_nonveg_indicator);
//viewSwitcher for switching between BUTTON and - + button
viewHolder.viewSwitcher = (ViewSwitcher) row.findViewById(R.id.viewswitcher);
//indicator for item added to Cart
viewHolder.addedToCartIndicator = (TextView) row.findViewById(R.id.added_to_cart_text_indicator);
viewHolder.addedToCartIndicator.setVisibility(View.INVISIBLE);
//counter for number of items selected for a particular dish
viewHolder.dishQuantity = (TextView) row.findViewById(R.id.dish_quantity);
//set tag for the ViewHolder
row.setTag(viewHolder);
} else {
/* Get the ViewHolder back to get fast access to the DishItem UI widgets (views) */
viewHolder = (DishItemViewHolder) row.getTag();
}
//create object of Item Count Class
cartItemCount = new CartItemCount();
/* fetch DishItem View at current position (="position") */
final DishItem dishItem = getItem(position);
row.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e("clicked"+position,"");
viewHolder.dishName.setText("CLICKED");
}
});
return row;
}
static class DishItemViewHolder {
TextView dishName;
TextView chefName;
TextView dishPrice;
TextView dishQuantity;
String imageStorePath;
boolean isDishItemSelected;
Button addToCart, addItemButton, removeItemButton;
ImageView veg_nonveg_indicator;
ImageView dishImage;
ViewSwitcher viewSwitcher;
TextView addedToCartIndicator;
}
}
PROBLEM
Suppose I add 6 DishItem
beans (model) to the list. Then, when I perform onClick on the 1st item in the list, the 1st item's text changes to CLICKED as it should. Also, in the Log, in says clicked:0
(as 1st list item's index is 0).
But the text for 4th list item also changes to CLICKED, which it shouldn't.
Now I read this post explaining the recycling mechanism of ListView.
However, I DO NOT want it to work that way since I want to update only those items I click. What am I doing wrong here? Is there any workaround this recycling mechanism to update only the particular item that I click?
UPDATE
Problem solved. I followed BlackBelt's approach and have thus accepted his answer, but I'd like to thank all of you for your inputs !! :)
Here is the updated getView() method.
/**
*
* @param position The position of the item within the adapter's data set of the item whose view we want.
* @param convertView The old view to reuse, if possible.
* Note: You should check that this view is non-null and of an appropriate type before using.
* If it is not possible to convert this view to display the correct data, this method
* can create a new view. Heterogeneous lists can specify their number of view types,
* so that this View is always of the right type (see getViewTypeCount() and getItemViewType(int)).
* @param parent The parent that this view will eventually be attached to
* @return A View corresponding to the data at the specified position.
*/
@Override
public View getView(final int position, final View convertView, ViewGroup parent) {
View row = convertView;
final DishItemViewHolder viewHolder;
// A ViewHolder keeps references to children views to
// avoid unnecessary (and expensive) calls to findViewById() on each row.
if (row == null) {
LayoutInflater inflater = (LayoutInflater) this.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
row = inflater.inflate(R.layout.list_item_dish, parent, false);
//instantiate DishItem View Holder
viewHolder = new DishItemViewHolder();
//get BUTTON for Adding Dish to Cart
//viewHolder.addToCart = (Button) row.findViewById(R.id.add_to_cart_button);
viewHolder.addItemButton = (Button) row.findViewById(R.id.add_to_cart_secondary_button);
viewHolder.removeItemButton = (Button) row.findViewById(R.id.remove_from_cart_button);
//DISH NAME, CHEF NAME, DISH PRICE and DISH IMAGE
viewHolder.dishName = (TextView) row.findViewById(R.id.dish_name_textview);
viewHolder.chefName = (TextView) row.findViewById(R.id.chef_name_textview);
viewHolder.dishPrice = (TextView) row.findViewById(R.id.dish_price_textview);
viewHolder.dishImage = (ImageView) row.findViewById(R.id.dish_imageview);
//image absolute path
viewHolder.imageStorePath = new String();
//image for depicting whether image is VEG or NON VEG
viewHolder.veg_nonveg_indicator = (ImageView) row.findViewById(R.id.veg_nonveg_indicator);
//indicator for item added to Cart
viewHolder.addedToCartIndicator = (TextView) row.findViewById(R.id.added_to_cart_text_indicator);
viewHolder.addedToCartIndicator.setVisibility(View.INVISIBLE);
//set tag for the ViewHolder
row.setTag(viewHolder);
} else {
/* Get the ViewHolder back to get fast access to the DishItem UI widgets (views) */
viewHolder = (DishItemViewHolder) row.getTag();
}
//get object for CART ITEM COUNT class
final CartItemCount cartItemCount = new CartItemCount();
//get current dish item (MODEL from Bean class)
final DishItem dishItem = getItem(position);
//disable any highlighting unless dish is selected (verified from SharedPreferences)
viewHolder.dishImage.setColorFilter(null);
//hide ITEM COUNT indicator over the image unless dish is selected (again, verified from SharedPreferences)
viewHolder.addedToCartIndicator.setVisibility(View.INVISIBLE);
//show the + and - buttons on the right and left (respectively) side on the Dish ImageView
viewHolder.addItemButton.setVisibility(View.VISIBLE);
viewHolder.removeItemButton.setVisibility(View.VISIBLE);
//get data from Preferences (to see which dish is selected)
SharedPreferences pref = getContext().getSharedPreferences("DishDetails", Context.MODE_PRIVATE);
Map<String, ?> allEntries = pref.getAll();
for (Map.Entry<String, ?> entry : allEntries.entrySet()) {
Log.d("KEY = " + entry.getKey(), " VALUE = " + entry.getValue().toString());
}
//get Count for each dish in the list and set Quantity in the Model (DishItem.java)
if (pref != null) {
int currentDishCount = pref.getInt("dishCount" + position, 0);
Log.e("Current DishCount", String.valueOf(currentDishCount));
if (currentDishCount > 0) {
getItem(position).setisDishItemSelected(true);
dishItem.setDishQuantity(currentDishCount);
Log.d("dish item" + position," selected");
}
}
//update Views for selected DishItems
if (dishItem.isDishItemSelected()) {
viewHolder.dishImage.setColorFilter(Color.parseColor("#80E0E0E0"));
viewHolder.addedToCartIndicator.setVisibility(View.VISIBLE);
viewHolder.addedToCartIndicator.setText(dishItem.getDishQuantity() + " items in cart");
}
viewHolder.addItemButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dishItem.setisDishItemSelected(true);
dishItem.setDishQuantity(dishItem.getDishQuantity() + 1);
//save data to preferences
SharedPreferences pref = getContext().getSharedPreferences("DishDetails", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = pref.edit();
editor.putInt("dishCount" + position, dishItem.getDishQuantity());
editor.commit();
//increment Total Number of Items in Cart
int itemCount = cartItemCount.getitemCount();
cartItemCount.setitemCount(itemCount + 1);
Log.d("itemCount =", String.valueOf(itemCount));
//broadcast the value of itemCount to MainMenuActivity
Intent intent = new Intent("NEW_CART_ITEM");
intent.putExtra("value", cartItemCount.getitemCount());
getContext().sendBroadcast(intent);
Log.d("broadcast", "sent");
//notify adapter of change in underlying data (i.e. update View to show Changes in Model)
notifyDataSetChanged();
}
});
viewHolder.removeItemButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d("Old Dish Qty. ", String.valueOf(dishItem.getDishQuantity()));
//if dishCount has reached ZERO, set Dish as NOT SELECTED for buying
if (dishItem.getDishQuantity() == 0) {
dishItem.setisDishItemSelected(false);
} else {
dishItem.setisDishItemSelected(true);
dishItem.setDishQuantity(dishItem.getDishQuantity() - 1);
Log.d("New Dish Qty.", String.valueOf(dishItem.getDishQuantity()));
//decrement TOTAL number of items in Cart
int itemCount = cartItemCount.getitemCount();
cartItemCount.setitemCount(itemCount - 1);
Log.d("itemCount =", String.valueOf(itemCount));
//broadcast the value of itemCount to MainMenuActivity
Intent intent = new Intent("NEW_CART_ITEM");
intent.putExtra("value", cartItemCount.getitemCount());
getContext().sendBroadcast(intent);
Log.d("broadcast", "sent");
//recheck -> if dish Count has reached ZERO, set Dish as NOT SELECTED for buying
if (dishItem.getDishQuantity() == 0) {
dishItem.setisDishItemSelected(false);
}
}
//save Current Quantity of Dish Selected to SharedPreferences
SharedPreferences pref = getContext().getSharedPreferences("DishDetails", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = pref.edit();
editor.putInt("dishCount" + position, dishItem.getDishQuantity());
editor.commit();
//notify adapter of change in underlying data (i.e. update View to show Changes in Model)
notifyDataSetChanged();
}
});
return row;
}
The idea was simple (I didn't know it before) -
- In the onClick Listener for the buttons, change the Model i.e. Data i.e. DishItem using the setters and getters defined in the bean class.
call
notifyDataSetChanged()
to tell the adapter about changes in data so that Views can be adjusted accordingly.
- In the getView() method, set views bases on those data values.