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
Related
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.
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.
Im trying to lock button after user click button on costume list.
the item of list fetched from hayoola and then i use viewholder to show data
to user in list.
if user click accept or refuse button. the result will be passed to hayoola and
the selected button will be disabled
Problem is after user click button wrong button getting disable. and after five click all remaining button getting disable too.
here is my adapter code :
static class myviewholder {
TextView textfromhayoola;
ImageButton buttonaccpet;
ImageButton buttonrefuse;
}
public View getView(int position, View convertView, ViewGroup parent) {
try {
final myviewholder viewHolder;
final int itemlocation = position;
if (convertView == null) {
convertView = inflater.inflate(R.layout.listforuser, null);
viewHolder = new myviewholder();
viewHolder.textfromhayoola = (TextView) convertView.findViewById(R.id.textfromhayoola);
viewHolder.buttonaccpet= (ImageButton) convertView.findViewById(R.id.buttonaccpet);
viewHolder.buttonrefuse= (ImageButton) convertView.findViewById(R.id.buttonrefuse);
convertView.setTag(viewHolder);
}else{
viewHolder = (myviewholder) convertView.getTag();
}
HashMap<String, String> myhayooladata= new HashMap<String, String>();
myhayooladata = data.get(position);
if (myhayooladata != null) {
viewHolder.textfromhayoola.setTag(myhayooladata.get("id_textfromhayoola"));
viewHolder.textfromhayoola.setText(myhayooladata.get("textfromhayoola"));
viewHolder.buttonaccpet.setTag(getItemId(position));
viewHolder.buttonrefuse.setTag(getItemId(position));
viewHolder.buttonaccpet.setOnClickListener(new OnClickListener() {
public void onClick(View arg0) {
performOnBackgroundThread(new Runnable() {
public void run() {
passdatatohayoola(viewHolder.textfromhayoola.getTag().toString(),"accpet");
}
});
arg0.findViewWithTag(viewHolder.buttonaccpet.getTag()).setEnabled(false);
arg0.findViewWithTag(viewHolder.buttonrefuse.getTag()).setEnabled(false);
}
});
viewHolder.buttonrefuse.setOnClickListener(new OnClickListener() {
public void onClick(View arg0) {
performOnBackgroundThread(new Runnable() {
public void run() {
passdatatohayoola(viewHolder.textfromhayoola.getTag().toString(),"refuse");
}
});
arg0.findViewWithTag(viewHolder.buttonaccpet.getTag()).setEnabled(false);
arg0.findViewWithTag(viewHolder.buttonrefuse.getTag()).setEnabled(false);
}
});
Hehe. Do you think that. Just "n/number of your records" view(s) will be created?
Assume that:
If you have 100 records and 10 rows will be draw.
And 10 rows won't re-draw.
Button accept of row 1 is disabled that mean Button accept of row 11 + n is the same. :D
May be that's your problem.
And Please read this:
Let I tell you something about ViewHolder pattern.
When you load data to ListView or GridView or something like these.
If your data has 1000 records, ... The ListView doesn't load 1000 records. It's just load "n records". "n" is number of record which compatible with your screen device.
Example: n = 10.
When you scroll down, The ListView will load more data. In this case, each row (view of row) will re-draw.
And with ListView which has large data, when use scroll up or down, you will see your app will be slow... because view is re-drawing.
And ViewHolder is the pattern which help you solve that problem.
When you use ViewHolder pattern, Just "n" (in this example n = 10) views will be created.
So, In your problem. I can tell you that:
May be you are implemented ViewHolder is incorrect.
OK. Let I tell you about the solution.
if (convertView == null) {
//You just findView and set for view
// viewHolder = new ViewHolder();
// viewHolder.yourBtn = convertView.findViewById(R.id.some_view_id);
// convertView.setTag(viewHolder);
} else {
//Right here, you will get viewHolder, because convertView is not null. It mean the viewHolder is existed.
}
And right here, It mean every time you will set data (You won't cache data, just cache VIEW)
Assume that Your data is an object and object has property which stand for status of button.
MyObject {
boolean objStatus;
}
and when you set data
viewHolder.yourBtn.setEnable(instanceOfMyObject.objStatus);
I'm trying to put a text above the Image in grid view but i'm not able to do this.
My app will have a gridview with pictures, and when I click over the each picture, I have put the quantity of clicks above of this image in real time.
I create an Adapter and My images are appearing on my Activity.
How I can do this, on Android?
You can do it through the onItemClickListener
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
if (view == null) {
// Create the view here
Holder tag = new Holder();
tag.textView = (TextView) view.findViewById(textViewId);
view.setTag(tag);
}
if (counts[position] == 0) {
tag.textView.setVisibility(View.INVISIBLE);
else {
tag.textView.setVisibility(View.VISIBLE);
tag.textView.setText("" + counts[position]);
}
...
}
gridView.setAdapter(adapter);
int[] counts = new int[// item count];
gridView.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Holder tag = (Holder) view.getTag();
if (tag != null) {
counts[position] = counts[position] + 1;
tag.textView.setText("" + counts[position]);
}
}
}
You need to track the count outside as a member variable.
In your adapter (or wherever appropriate to store this data depending on how long lived this click data should be), add some sort of data structure to maintain the number of clicks per position. In your adapter's getView(), you should set the text at that position according to the number of clicks you have stored in your data structure.
In your list item click listener, increment the click count in your data structure, and call notifyDatasetChanged() on your Adapter.
An activity has a Button and a ListView.
Initially, only the Button is visible. When the button is pressed, the ListView is displayed.
When displayed, is it possible for me to show one particular item as selected/focussed?
A use case could be that suppose it is a list of language settings and when the list opens, the currently selected language must be shown as highlighted.
If I know the index of the item, how to set it as focused on display?
I post my solution, because google still doesn't know the answer.
getListView().setItemChecked(selectedGroupIndex, true);
In short, ListView::setSelection(int position) is what you need. However, depending on whether the device is in touch mode or not, it may or may not have visual effect (background highlighting). For more details, refer to Android ListView Selection Problem
If you use an Adapter for your ListView add this code to your adapter:
public class MyAdapter extends ArrayAdapter<MyClass> {
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
LayoutInflater inflator = (LayoutInflater) getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
rowView = inflator.inflate(R.layout.my_adapter, null);
} else {
rowView = (View) convertView;
}
//...
// set selected item
LinearLayout ActiveItem = (LinearLayout) rowView;
if (position == selectedItem){
ActiveItem.setBackgroundResource(R.drawable.background_dark_blue);
// for focus on it
int top = (ActiveItem == null) ? 0 : ActiveItem.getTop();
((ListView) parent).setSelectionFromTop(position, top);
}
else{
ActiveItem.setBackgroundResource(R.drawable.border02);
}
}
private int selectedItem;
public void setSelectedItem(int position) {
selectedItem = position;
}
}
In your Activity:
myAdapter.setSelectedItem(1);
I am using an Adapter and didn't want to set custom background colors, but use the android:state_selected in drawable xml. SetSelection didn't work for me, but maybe that's also since I needed SetNotifyDataChanged which shows that the Selected State is not persistent.
I also found that the Selected state for an item in a ListView is not persistent, since SetNotifyDataChanged results in updating the ListView layout which clears them all. Setting the item to Selected in the Adapter's GetView is too soon too.
Eventually I set the Selected state for the view of the selected item after the layout of the listview has been changed, which is when LayoutChange event is being triggered (in Java it's probably attaching a to OnLayoutChangeListener of the ListView).
To make it really easy I store the view of the selected item as Adapter's SelectedItemView.
In the ListView's LayoutChange eventhandler I just set the adapter's SelectedItemView.Selected to true.
Here's the code from my Activity where I set the Adapter for the ListView and also subscribe to LayoutChange (or in Java attach an OnLayoutChangeListener)
ringTonesListView.Adapter = ringTonesListAdapter;
ringTonesListView.LayoutChange += (s, layoutChangeArgs) => {
//At this stage the layout has been updated and the Selected can be set to true for the view of the selected item. This will result in android:state_selected logic to be applied as desired and styling can be completely done per layout in Resources.
ringTonesListAdapter.SelectedItemView.Selected = true;
};
Here's my code for the Adapter:
public class RingTonesListAdapter : BaseAdapter<RingToneItem>
{
List<RingTone> Items { get; set; }
public override View GetView(int position, View convertView, ViewGroup parent)
{
View view = convertView;
// re-use an existing view, if one is available
// otherwise create a new one
if (view == null)
{
view = Context.LayoutInflater.Inflate(Resource.Layout.AlertSoundItem, parent, false);
view.Click += SelectRingTone;
}
RingTone ringTone = this[position];
if (ringTone.Selected)
{
//==> Important
//Store this view since it's the view for the Selected Item
SelectedItemView = view;
//Setting view.Selected to true here doesn't help either, since Selected will be cleared after.
}
return view;
}
private void SelectRingTone(object sender, EventArgs args)
{
View view = (View)sender;
string title = view.FindViewById<TextView>(Resource.Id.ringToneTitle).Text;
RingToneItem ringToneItem = Items.First(rt => rt.Title == title);
if (!ringToneItem.Selected)
{
//The RingTone was not selected and is selected now
//Deselect Old and Select new
foreach (RingToneItem oldItem in Items.Where(rt => rt.Selected))
{
oldItem.Selected = false;
}
// Select New RingTone
ringToneItem.Selected = true;
//Update the ListView.
//This will result in removal of Selected state for all Items when the ListView updates it's layout
NotifyDataSetChanged();
}
//Now play the test sound
NotifierService.TestSound(Context, ringToneItem);
}
public View SelectedItemView { get; set; }
}