android - ViewHolder single onClick affects multiple list items - android

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.

Is there any workaround this recycling mechanism to update only the
particular item that I click?
No workaround here. You have to change the dataset (at position - the item you clicked), and ask the Adapter to redraw the ListView's children. I would strongly suggest you to use the OnItemClickListener too.

To change something, you must know the position of your item:
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// Code here
}
});
this will give you the correct position you've touch it. Other way, inside your getView (in the Adapter):
DishItemList = dishCountList.get(position);
That way you'll have the position of your item. To touch only the one you want:
row.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View arg0) {
// here you can DishItemList.get("and get the key you need");
}
});

You should reset the view's state to the default one in the getView and apply the item's data after this. It works like this:
Get the row's view (either the convertView or inflate a new one)
Create and attach a view holder
Get the current item's data and state
Apply ALL the state params and data (so write "Clicked" if the current item is clicked or clear the TextView's text otherwise - do not assume that it contains the correct text already)
You'd need to store the item's state somewhere - currently you use the TextView's text to do this and it breaks due to the recycling behaviour. The correct way is to add a field to the DishItem or just create a new class to contain the state of each item (selected, focused and so on - all the states you'd like to support). In the onClickListener change the state's value and then change the views' contents or just call notifyDataSetChanged on the adapter.

Related

Set visibility for an image within a ListView row

Overview:
I have a ListView with a custom adapter/layout, every time a user adds a new row (which contains a number), I check if that number is the smallest in the list. If so, an image within that row must be set as visible while setting all other row's images as invisible.
Problem:
My ListView does not set any row's image as visible, even though I have the index of the smallest element.
How I'm doing it:
//In MainActivity
private void addProduct(float price) { //User adds product
priceList.add(price); //Add to Float list
adapter.notifyDataSetChanged();
updateView(findMinIndex(priceList)); //Find smallest val indx
}
private void updateView(int index){
View v = listView.getChildAt(index -
listView.getFirstVisiblePosition());
if(v == null)
return;
ImageView checkMark = (ImageView) v.findViewById(R.id.check_mark);
checkMark.setVisibility(View.VISIBLE); //Initially set Invisible
}
Edit, CustomAdapter:
public CustomList(Activity context,
ArrayList<Float> priceList) {
super(context, R.layout.list_single, priceList);
this.context = context;
priceList = priceList;
}
#Override
public View getView(int position, View view, ViewGroup parent) {
LayoutInflater inflater = context.getLayoutInflater();
View rowView = inflater.inflate(R.layout.list_single, null, true);
TextView price = (TextView) rowView.findViewById(R.id.new_price);
ImageView cheapest = (ImageView) rowView.findViewById(R.id.check_mark);
price.setText(priceList.get(position) + "");
return rowView;
}
Thank you
It is your priceList binded with the adapter?
First of all i would put a breakpoint to see if you are getting the right view in the updateView method.
try this way;
Create a Pojo class with imageview and it's state(Visibility) initially set all to invisible
Add your items to the ArrayList of Pojo Class type.
when user enters a new row based on your requirement set visibility state to true or false(visible or invisible) and call notifyDataSetChanged() to the adapter.
Doing this way you can have a easy track of the items.
I got it working :).
Problem is that adapter.notifyDataSetChanged(); is async, so while it's doing that, updateView(findMinIndex(priceList)); runs but doesn't find the new row as it should. Therefore, I add a runnable to the ListView object as so:
adapter.notifyDataSetChanged();
listView.post( new Runnable() {
#Override
public void run() {
updateView(findMinIdx(priceList));
}
});
Now it works perfectly!

Adapter onClickListener not firing when it should, or updating View correctly

Help! This was working, but no version control.
The scoring buttons are updating a TextView several rows down from the one being clicked, and Android Studio logs I put in to problem solve aren't even registering that a button has been hit.
The whole thing is going sideways, the question is 'why does hitting the plusButton and minusButton update a TextView in a row several down from the 'active' row where I want to update stuff.
Here's a picture, I've clicked on the bear 4 times, and it's updated Player 4's score TextView, wth?
package com.basketball.dating;
import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.ArrayList;
/**
* {#link ScoreBoardAdapter} is an {#link ArrayAdapter} that can provide the layout for each list item
* based on a data source, which is a list of {#link ScoreBoard} objects.
*/
public class ScoreBoardAdapter extends ArrayAdapter<ScoreBoard> {
/** Resource ID for the background color for this list of words */
private int mColorResourceId;
final String TAG = "ScoreBoardAdapter";
public ScoreHolder holder = new ScoreHolder();
/**
* Create a new {#link ScoreBoardAdapter} object.
*
* #param context is the current context (i.e. Activity) that the adapter is being created in.
* #param scoreBoards is the list of {#link ScoreBoard}s to be displayed.
*/
public ScoreBoardAdapter(Context context, ArrayList<ScoreBoard> scoreBoards) {
super(context, 0, scoreBoards);
//mColorResourceId = new ContextCompat().getColor(getContext(), R.color.colorAccent);
}
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
// Check if an existing view is being reused, otherwise inflate the view
View listItemView = convertView;
//ScoreHolder holder = new ScoreHolder();
if (listItemView == null) {
listItemView = LayoutInflater.from(getContext()).inflate(
R.layout.list_item, parent, false);
// now stuff all the row's views into a ScoreHolder object
holder.plusButton = (TextView) listItemView.findViewById(R.id.plusButton);
holder.minusButton = (TextView) listItemView.findViewById(R.id.minusButton);
holder.scoreTextView = (TextView) listItemView.findViewById(R.id.score_text_view);
// now attach these details to the row, so it 'remembers' they're there
listItemView.setTag(holder);
}
else {
Log.wtf("ScoreBoardADAPTER", "NOT_NULL ROW");
holder = (ScoreHolder) listItemView.getTag();
}
holder.plusButton.setOnClickListener(new View.OnClickListener() {
//private int pos = position;
public void onClick(View v) {
//TextView mPlusButton = (TextView) v;
Log.wtf(TAG, "hit increment " + position);
int score = Integer.parseInt((String) holder.scoreTextView.getText());
if (score < 99) {
holder.scoreTextView.setText(String.valueOf(score + 1));
}
}
});
holder.minusButton.setOnClickListener(new View.OnClickListener() {
//private int pos = position;
public void onClick(View v) {
//TextView mMinusButton = (TextView) v;
int score = Integer.parseInt((String) holder.scoreTextView.getText());
holder.scoreTextView.setText(String.valueOf(score - 1));
}
});
// Get the {#link Word} object located at this position in the list
ScoreBoard currentScoreBoard = getItem(position);
// Find the TextView in the list_item.xml layout with the ID miwok_text_view.
TextView playerNameTextView = (TextView) listItemView.findViewById(R.id.playerName);
playerNameTextView.setText("Player " + String.valueOf(currentScoreBoard.getPlayerName()) );
// Find the ImageView in the list_item.xml layout with the Avatar.
ImageView imageView = (ImageView) listItemView.findViewById(R.id.playerAvatar);
// display the provided image based on the resource ID
imageView.setImageResource(currentScoreBoard.getAvatar());
// Make sure the view is visible
imageView.setVisibility(View.VISIBLE);
// TODO adapt to colour the leaders gold, silver, bronze
// // Set the theme color for the list item
// View textContainer = listItemView.findViewById(R.id.text_container);
// // Find the color that the resource ID maps to
// int color = ContextCompat.getColor(getContext(), R.color.colorAccent);
// // Set the background color of the text container View
// textContainer.setBackgroundColor(color);
// Return the whole list item layout so that it can be shown in the ListView.
return listItemView;
}
/* need to be able to save an object of key textViews for each list item , or 'row' */
static class ScoreHolder {
TextView plusButton;
TextView minusButton;
TextView scoreTextView;
}
}
Try to not make this as a field of the adapter
public ScoreHolder holder = new ScoreHolder();
And I think the reason for you doing that is because you couldn't get a reference to the holder from within the onClick methods, and the IDE tried to make the holder effectively final, but that wasn't possible because you needed to assign the holder to two different things, so you just moved the holder to a field.
TL;DR Try putting it back into the getView method
View listItemView = convertView;
final ScoreHolder holder = listItemView == null ? new ScoreHolder() : listItemView.getTag();
if (listItemView == null) {
listItemView = LayoutInflater.from(getContext()).inflate(
R.layout.list_item, parent, false);
If that doesn't work (can't remember if ternary's work on final variables), then you can set the tag of the two buttons to be the holder as well, then get the holder in the two click listeners onClick methods from v
Your direct problem is that you have one single ScoreHolder object for the entire Adapter. There are several views created, but since they all use the same holder, all the OnClickListeners will change the TextView of the last created list row.
Most AdapterViews, including ListView, use a pool of Views for their items. The convertView parameter will most likely be null the first few times getView is called, and when the pool is full, you will start receiving non-null objects as converViews.
TL;DR: You have several list item Views sharing a single ScoreHolder object.

CustomListAdapter list items imitate each other

I have read and tried several of the solutions to similar problems here on stack overflow, but none of them solved my problem. Here is the thing.
I have a listview which uses CustomListAdapter, each list item has a progress bar, a download button, title text and so on. When the download button is clicked a download operation is performed, and based on the result of the download(whether success or failure) the list item concerned is update(UI changes, such as if complete hide download button, update progress of the progress bar during download)
The listview displays four items at every given time
The problem is that whenever a UI change is made to an item say item 1(with index 0) the item 5 will also have the same changes, likewise if a change is made to item 3, the item 7 takes up those changes. In summary the item N+4 always imitates item N.
A look at my getView() will tell that I have checked all the known boxes.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
ViewHolder holder;
if (convertView == null) {
view = inflater.inflate(R.layout.item_mylibrarylist, null);
holder = new ViewHolder();
holder.name = (TextView)view.findViewById(R.id.name);
holder.name.setTypeface(MainActivity.font_bahamas);
holder.author = (TextView)view.findViewById(R.id.author);
holder.author.setTypeface(MainActivity.font_bahamas);
holder.worktype = (TextView)view.findViewById(R.id.worktype);
holder.worktype.setTypeface(MainActivity.font_bahamas);
holder.coverPic = (TextView)view.findViewById(R.id.coverPic);
holder.downloadBt = (TextView)view.findViewById(R.id.downloadBt);
holder.progressBar = (ProgressBar)view.findViewById(R.id.progressBar2);
holder.menuBt = (ImageView)view.findViewById(R.id.menuBt);
holder.position = position;
view.setTag(holder);
} else {
holder = (ViewHolder) view.getTag();
}
if(holder.position == position) {
setValuesForListItemViews(holder, position, view);
}
return view;
}
The method to set each of the list items..
private void setValuesForListItemViews(ViewHolder holder, int position, View view) {
if (!data.isEmpty()) {
// set the list item elements here
final CreativeWork creativeWork = data.get(position);
holder.name.setText(creativeWork.getName().toLowerCase());
holder.author.setText("by " + creativeWork.getOriginal_authors().toLowerCase());
holder.worktype.setText(creativeWork.getWork_type().toLowerCase());
Drawable draw = res.getDrawable(R.drawable.custom_progressbar2);
holder.progressBar.setProgressDrawable(draw);
holder.progressBar.setMax(100);
holder.progressBar.setVisibility(View.INVISIBLE);
holder.menuBt.setOnClickListener(new OnItemClickedListener(view, position, 1, creativeWork, holder.progressBar, holder.downloadBt));
holder.menuBt.setOnCreateContextMenuListener(new MContextMenuListener(creativeWork, holder.progressBar, holder.downloadBt, false));
//load image url
ImageLoader2 imgLoader12 = new ImageLoader2(activity);
imgLoader12.DisplayImage(creativeWork.getName(), R.drawable.downloads, holder.downloadBt);
ImageLoader imgLoader = new ImageLoader(activity);
imgLoader.DisplayImage(SLService.END_POINT + creativeWork.getImage_url(), R.drawable.soul_lounge, holder.coverPic);
//check if file already exist and switch off download button
DBHelper helper = new DBHelper(activity);
CreativeWork cw = helper.getCreativeWork(creativeWork);
if (cw != null) {
File file = new File(cw.getFilePath());
if (file.exists()) {
holder.menuBt.setOnCreateContextMenuListener(new MContextMenuListener(creativeWork, holder.progressBar, holder.downloadBt, true));
//check if the file download was complete
if (cw != null) {
if (cw.getFileSize() > file.length()) {
holder.progressBar.setProgressDrawable(activity.getResources().getDrawable(R.drawable.custom_progressbar3));
ImageLoader2 imgLoader2 = new ImageLoader2(activity);
imgLoader2.DisplayImage(cw.getName(), R.drawable.restart, holder.downloadBt);
holder.progressBar.setProgress((int) ((file.length() * 100) / cw.getFileSize()));
holder.progressBar.setVisibility(View.VISIBLE);
} else {
holder.downloadBt.setVisibility(View.INVISIBLE);
}
}
}
}
holder.downloadBt.setOnClickListener(new OnItemClickedListener(view, position, creativeWork, holder.progressBar, holder.downloadBt, 0));
}
}
Easiest way to solve this is adding some extra fields to your model class whose list you are passing to your adapter.
like
boolean showDownloadButton; //default is true
int progress;// default is 0
So when user clicks on download button (or any other desired event) change the boolean value of showDownloadButton to false for model object at the given position and call adapter.notifyDatasetChanged() and manage the button visibility accordingly in your adapter.
and do make sure to add both visible and gone condition for the view aswell
if(modelList.get(position).getShowDownloadButton())
{
btnDownload.setVisibility(View.VISIBLE)
}
else
{
btnDownload.setVisibility(View.GONE)
}
Nitesh's answer is right apart from that there is another way to do if you don't want to create Model class
Use setId and getId with position to identify unique row and assign that id to progressbar for the solution

Updating more than one rows in listview

I have a listview which consists of name, text and imageview. If an user named "John" clicks the imageview in the row with user named "Bob", all the rows with name "Bob" inclusive of the currently clicked row should have their imageview changed into another image. I am trying to do this in the following code:
private class MyListAdapter extends ArrayAdapter<CommentInfo> {
public MyListAdapter()
{
super(getActivity(), R.layout.listview_xml, myComments);
}
#Override
public View getView(final int position, final View convertView, final ViewGroup parent)
{
itemView = convertView;
Bundle bundle = new Bundle();
if(itemView == null)
{
itemView = layoutInflater.inflate(R.layout.listview_xml, parent, false);
}
final CommentInfo currentComment = myComments.get(position);
List<View> viewList;
if(!ht.containsKey(currentComment.userName)){
viewList = new ArrayList<View>();
viewList.add(itemView);
Log.d("username", currentComment.userName);
Log.d("position", Integer.toString(position));
ht.put(currentComment.userName, viewList);
}
else{
((List<View>)ht.get(currentComment.userName)).add(itemView);
Log.d("username", currentComment.userName);
Log.d("position", Integer.toString(position));
}
final ImageView follows = (ImageView) itemView.findViewById(R.id.followUserBtn);
follows.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(follows_flag == 0){
follows_flag = 1;
followed_person = currentComment.userName;
follows.setImageResource(R.drawable.followusersuccesssbtn);
List<View> viewList1 = (List<View>) ht.get(currentComment.userName);
for(View view : viewList1){
ImageView follows_other = (ImageView)view.findViewById(R.id.followUserBtn);
follows_other.setImageResource(R.drawable.followusersuccesssbtn);
}
new StoreFollowed().execute();
}
else{
follows_flag = 0;
followed_person = currentComment.userName;
follows.setImageResource(R.drawable.followusericon);
List<View> viewList1 = (List<View>) ht.get(currentComment.userName);
for(View view : viewList1){
ImageView follows_other = (ImageView)view.findViewById(R.id.followUserBtn);
follows_other.setImageResource(R.drawable.followusericon);
}
new DeleteFollowed().execute();
}
}
});
return itemView;
}
}
In the above code, I store in hashtable, for each name, list of views. My issue is when John clicks on the imageview in the row with name Bob, all the rows with imageview gets its image changed rather than the rows with only Bob's name. What is the wrong I am doing here? How to resolve the issue?
First don't keep an association between your data and your view. Android ListView reuses view which means that you will have some views associated each with multiple names. I'm pretty sure your problem comes form this.
You should alway try to only modify your Model Objet and let the list view update the UI.
In you case You could have All your CommentInfo objet referencing a User with different CommentInfo from same user refrencing the User object. You can then store you image in this User and when you wan't in on click update the user's image and call notifyDataSetChanged(); so that your list update it's UI.
PS : you might wan't to take a look at ViewHolder. It has nothing to do with you problem but it's a good practice and would increase the smoothness of your list's scrolling.

listview adapter click handled by wrong object when notifyDataSetChanged is called frequently and view inflated from layout

I have an Android ListView that is frequently updated. When I click on an item in the list, some subset of the time the wrong object gets the click event. This occurs even when the update does not change the list.
Am I just coding this wrong, or is there a race condition in event handling through the view hierarchy?
The ListView uses an adapter that extends BaseAdapter, and its getView() method looks like the following. But the problem only appears when USE_LAYOUT=true.
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
View view;
TextView textView;
ViewHolder viewHolder;
if (convertView == null) {
if (USE_LAYOUT) {
view = context.getLayoutInflater().inflate(R.layout.row, parent, false);
textView = (TextView) view.findViewById(R.id.rowTextView);
} else {
view = new TextView(context);
textView = (TextView) view;
}
viewHolder = new ViewHolder();
viewHolder.textView = textView;
view.setTag(viewHolder);
} else {
view = convertView;
viewHolder = (ViewHolder) view.getTag();
textView = viewHolder.textView;
}
final String item = getItem(position);
textView.setText(position + " " + item);
textView.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
Log.d("Adapter", position + " " + item);
}
});
return view;
}
So in terms of the code above, the activity displays a list view showing "1 item1", "2 item2", "3 item3", etc. If I click on, say item3, some percentage of the time, the log will show "21 item21".
A couple of other points about the code:
The item is just a String, the adapter maintains a List<String>, getItem() just calls items.get(index), and setItems() sets the list and calls notifyDataSetChanged().
The problem occurs if the list is not changed. I.e., the timer calls setItems(new ArrayList<String>(getItems()).
setItems() is called from the UI thread via runOnUiThread().
The timer goes off every 500ms or so, but the problem occurs with other frequencies.
The row layout is simply a TextView inside a LinearLayout.
I have observed that when views are recycled through getView, they are recycled in reverse order. In other words, whennotifyDataSetChanged() is called and the list has not scrolled, the first item is recycled with the last item, the second item is recycled with the second to last item, etc. Hence my suspicion of a race condition. Also, I observe it on 2.1 and 2.3, but not 4.0 (although I'm not sure it never occurs there).
The problem is that the code in the onClick() method is run at a different time than when getView() executes, so position is probably not what you expect it to be... Try this:
final String item = getItem(position);
textView.setTag(position);
textView.setText(position + " " + item);
textView.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
int position = (Integer) v.getTag();
Log.d("Adapter", position + " " + getItem(position));
}
});
In other words everything inside new OnClickListener() { ... } cannot see the local variables inside getView() (like position and item), but it can see the class variables and methods (like getItem()).

Categories

Resources