Deleting items from a ListView using a custom BaseAdapter - android

I am using a customised BaseAdapter to display items on a ListView. The items are just strings held in an ArrayList.
The list items have a delete button on them (big red X), and I'd like to remove the item from the ArrayList, and notify the ListView to update itself.
However, every implementation I've tried gets mysterious position numbers given to it, so for example clicking item 2's delete button will delete item 5's. It seems to be almost entirely random.
One thing to note is that elements may be repeated, but must be kept in the same order. For example, I can have "Irish" twice, as elements 3 and 7.
My code is below:
private static class ViewHolder {
TextView lang;
int position;
}
public View getView(final int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.language_link_row, null);
holder = new ViewHolder();
holder.lang = (TextView)convertView.findViewById(R.id.language_link_text);
holder.position = position;
final ImageView deleteButton = (ImageView)
convertView.findViewById(R.id.language_link_cross_delete);
deleteButton.setOnClickListener(this);
convertView.setTag(holder);
deleteButton.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.lang.setText(mLanguages.get(position));
return convertView;
}
I later attempt to retrieve the deleted element's position by grabbing the tag, but it's always the wrong position in the list. There is no noticeable pattern to the position given here, it always seems random.
// The delete button's listener
public void onClick(View v) {
ViewHolder deleteHolder = (ViewHolder) v.getTag();
int pos = deleteHolder.position;
...
...
...
}
I would be quite happy to just delete the item from the ArrayList and have the ListView update itself, but the position I'm getting is incorrect so I can't do that.
Please note that I did, at first, have the deleteButton clickListener inside the getView method, and used 'position' to delete the value, but I had the same problem.
Any suggestions appreciated, this is really irritating me.

You have to set the position each time. Your implementation only sets the position on the creation of the view. However when the view is recycled (when convertView is not null), the position will not be set to the correct value.
public View getView(final int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.language_link_row, null);
holder = new ViewHolder();
holder.lang = (TextView)convertView.findViewById(R.id.language_link_text);
final ImageView deleteButton = (ImageView)
convertView.findViewById(R.id.language_link_cross_delete);
deleteButton.setOnClickListener(this);
convertView.setTag(holder);
deleteButton.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.lang.setText(mLanguages.get(position));
holder.position = position;
return convertView;
}

you need to implement OnItemClickListener interface, and delete the item in the onItemClick method, one parameter of the method is the position.

My final solution was to use the accepted answer by Greg and the following:
Store the holders in a HashMap, with the item positions as the keys (this is initialised as empty in the constructor)
private HashMap mHolders;
Use this as the onClickListener method:
public void onClick(View v) {
ViewHolder deleteHolder = (ViewHolder) v.getTag();
int pos = deleteHolder.position;
mHolders.remove(pos);
ViewHolder currentHolder;
// Shift 'position' of remaining languages
// down since 'pos' was deleted
for(int i=pos+1; i<getCount(); i++){
currentHolder = mHolders.get(i);
currentHolder.position = i-1;
}
mLanguages.remove(pos);
notifyDataSetChanged();
}
[Please excuse the weird formatting. The code embedding isn't working properly]

Related

ListView alternate of using ViewHolder

I have created a ListView and its custom Adapter. But due to some reason I am not allowed to get items from ViewHolder.
In my case ViewHolder has only one variable and that is of LinearLayout. LinearLayout contains the other child views(which is decided and created at run time). When I use ViewHolder and set the tag of holder object, on scroll I am getting the same views again.
Is there any other way to stop adapter to create views while scrolling ?
Or, while scrolling how can we clear the references of views ?
I have find this but I don't think this will work.
setRecyclerListener(new RecyclerListener() {
#Override
public void onMovedToScrapHeap(View view) {
//from here can we use this to clean the memory
}
});
ViewHolder is meant as a holder to contain ids of listitem layout.
It is optimization to avoid calling findViewById everytime new listitem is created for display by going through data container e.g. arrayList.
You cannot stop adapter in between creating item views.
Only items on display are created.
convertView acts as object being recycled for creating subsequent view while scrolling up/down.
You will not be able to use view holder for the purpose you are trying to achieve.
Sample usage as below.
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
View v = convertView;
ViewHolder viewHolder = null;
if(convertView == null)
{
v = LayoutInflater.from(StockDetailsActivity.this).inflate(R.layout.stock_details_list_item, null);
viewHolder = new ViewHolder();
viewHolder.model_name_tv = (TextView) v.findViewById(R.id.model_name);
viewHolder.model_type_iv = (ImageView) v.findViewById(R.id.model_type_icon);
viewHolder.model_type_tv = (TextView) v.findViewById(R.id.model_type_desc);
viewHolder.model_stock_tv = (TextView) v.findViewById(R.id.model_stock_value);
v.setTag(viewHolder);
}
else
viewHolder = (ViewHolder) v.getTag();
stockCursor.moveToPosition(position);
// logic to update data to views as appropriate goes here
return v;
}
public class ViewHolder{
public TextView model_name_tv;
public ImageView model_type_iv;
public TextView model_type_tv;
public TextView model_stock_tv;
}

Update progressbar in Gridview

I have AudioListFile with Player Program.
When i click an item of GridView, sound will be played.!
enter image description here
Now, i wonder, How can i update ProgressBar in GridView?
I need mediaplayer.getDuration() in adapter.
please help me. thank you so much.
My adapter:
public View getView(final int position, View convertView, ViewGroup parent) {
if (convertView == null) {
// Initialize views
holder = new Holder();
convertView = inflater.inflate(R.layout.raw_audio_list_item, null);
holder.tvFileName = (TextView) convertView.findViewById(R.id.row_audio_list_item_tv_file_name);
holder.tvFileSize = (TextView) convertView.findViewById(R.id.row_audio_list_item_tv_file_size);
holder.pbPlayProgress = (ProgressBar) convertView.findViewById(R.id.audio_file_list_pb);
convertView.setTag(holder);
} else {
holder = (Holder) convertView.getTag();
}
// get the map object from list
AudioDataModel audioFileModel = audioFileList.get(position);
// set data to view
holder.tvFileName.setText(audioFileModel.getFileName());
holder.tvFileSize.setText(audioFileModel.getFileSize());
holder.pbPlayProgress.setMax(Integer.parseInt(audioFileModel.getFileDuration()));
convertView.setId(Integer.valueOf(audioFileModel.getFileId()));
return convertView;
}
actually you can easily do this but you have to use sparseIntegerHashmap to track the current progress for particular view because gridview or listview recycle the view they reuse the view for efficiency. so first check in getview method check whether this particular view are for this particular progress bar (means it not reused ) then set it's value from hashmap and if this view is newly created (no entry found in hashmap then) please apply default value for progress bar . simple you have to track individual view with progress in hashmap . current code might not be working because i am not on my workstation right now hope you will understand e.g
public View getView(final int position, View convertView, ViewGroup parent) {
if (convertView == null) {
// Initialize views
holder = new Holder();
convertView = inflater.inflate(R.layout.raw_audio_list_item, null);
holder.tvFileName = (TextView) convertView.findViewById(R.id.row_audio_list_item_tv_file_name);
holder.tvFileSize = (TextView) convertView.findViewById(R.id.row_audio_list_item_tv_file_size);
holder.pbPlayProgress = (ProgressBar) convertView.findViewById(R.id.audio_file_list_pb);
convertView.setTag(holder);
} else {
holder = (Holder) convertView.getTag();
}
// get the map object from list
AudioDataModel audioFileModel = audioFileList.get(position);
// set data to view
holder.tvFileName.setText(audioFileModel.getFileName());
holder.tvFileSize.setText(audioFileModel.getFileSize()); holder.pbPlayProgress.setMax(Integer.parseInt(audioFileModel.getFileDuration()));
convertView.setId(Integer.valueOf(audioFileModel.getFileId()));
//your unique id for compare
if(map.get(audioFileModel.getFileId())==holder.getId) {
holder.pbPlayProgress.setProgress(map.get(audioFileModel.getFileId()));
} else {
holder.pbPlayProgress.setProgress(//add default value );
// also add entry in hashmap
map.add(audioFileModel.getFileId(),default value)
}
return convertView;
}

Removing a row from ListView with a button click

I have a ListView whose rows comprise of some TextViews and a button. Upon a user pressing the button, I want to remove that parent that houses the button from the ListView. How do I access my custom ArrayAdapter's fields from within a nested method (onClickListener) though? All I have to work with is the View v. Am I suppose to call v.getParent() multiple times, or is there a better way to do it?
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
Action item = this.getItem(position);
if (convertView == null) {
convertView = inflater.inflate(R.layout.action_holder_layout,
parent, false);
holder = new ViewHolder();
holder.title = (TextView) convertView
.findViewById(R.id.action_holder_title);
holder.finishBtn = (Button) convertView
.findViewById(R.id.finish_action_button);
convertView.setTag(holder);
} else
holder = (ViewHolder) convertView.getTag();
holder.title.setText(item.getActionName());
holder.finishBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//REMOVE THE ACTION FROM THE ADAPTER'S ARRAYLIST
}
});
return convertView;
}
static class ViewHolder {
private TextView title;
private Button finishBtn;
}
Make item final:
final Action item = this.getItem(position);
and you can access it from onClick. Not a beautiful solution IMO, but will work:
remove(item);
If you need access to your ListView (and consequently its Adapter), a better way than using getParent().getParent()... would be to use the setTag() method on your View. Since you are already using it to insert a ViewHolder as a Tag of your view, why not add another field
ListView parentListView;
to your ViewHolder and retrieve it later in the onClick?
You could also set the ListView directly as a tag on your button.
You could also just access the adapter or other methods and fields directly from your onClick code since you're using an anonymous class.

Weird behavior in ListView GetView() method position

I've a custom adapter for my ListView where I send to it a List, and if the position is in the list then the imageview (that is in the custom row) change its src to another.. Here is the GetView method:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.start_row, null); // line
// 47
holder = new ViewHolder();
holder.tv_SuraName = (TextView) convertView.findViewById(R.id.Start_Name);
holder.tv_SuraName.setTypeface(Font);
holder.tv_PageNumber = (TextView) convertView.findViewById(R.id.Start_Numbering);
holder.im_Audio = (ImageView) convertView.findViewById(R.id.Start_ImageView);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.tv_SuraName.setText(SuraList_SuraNamesCode[position]);
holder.tv_PageNumber.setText(Integer.toString(PageNumber[position]));
holder.im_Audio.setOnClickListener(new imageViewClickListener(position));
if (TilawaAvailable.contains(position))
holder.im_Audio.setImageResource(R.drawable.quran_list_audioavailable);
return convertView;
}
I send a List with 1 position.If I scroll the ListView slowly, this works good and only 1 imageview gets changed as it should be. But if I scroll fast, other imagesviews that are close to the correct position also gets changed!
Can anyone tell me why?
You don't ever set the imageResource to something else if position isn't contained in your list. When the view with the custom image leaves the screen it is probably getting placed at a lower position in the list and getting reused.
Try changing this:
if (TilawaAvailable.contains(position))
holder.im_Audio.setImageResource(R.drawable.quran_list_audioavailable);
To this:
if (TilawaAvailable.contains(position))
holder.im_Audio.setImageResource(R.drawable.quran_list_audioavailable);
else
holder.im_audio.setImageResource(r.drawable.SOME_THING_ELSE);

Custom adapter getting wrong position on getView metod in ListView

I have list view with custom array adapter. I want to get items click position from getView method. I am getting a few list view items position but when i add more then 7 items into my list i get wrong position from getView method. I mean when i click 9th list item it returns 1.
Here is my code
#Override
public View getView(int pos, View convertView, ViewGroup parent) {
this.position = pos;
Log.v("View position", Integer.toString(pos));
lineView = convertView;
if(lineView==null)
{
adapterLine=new AdapterLine();
layoutInflater=context.getLayoutInflater();
lineView=layoutInflater.inflate(com.inomera.sanalmarket.main.R.layout.adapter, null,true);
adapterLine.sListText = (TextView) lineView.findViewById(R.id.sListText);
adapterLine.sListCheckbox = (CheckBox) lineView.findViewById(R.id.sListCheckbox);
adapterLine.sListImageView = (ImageView) lineView.findViewById(R.id.imageView1);
adapterLine.gestureOverlayView = (GestureOverlayView) lineView.findViewById(com.inomera.sanalmarket.main.R.id.gestureOverlayView1);
adapterLine.gestureOverlayView.setGestureVisible(false);
// To remember whitch tab is selected
adapterLine.sListImageView.setTag(pos);
adapterLine.sListCheckbox.setTag(pos);
adapterLine.sListText.setTag(pos);
Log.v("adapter", "position of adapter is " + Integer.toString(pos));
Thanks for any help!
The array adapter will only create one object per visible row in your list view. After this, the adapter will recycle that already created view. This is the purpose of the:
if(lineView==null)
line in your adapter.
You will want to put in an else section that sets up the row using the recycled view. This other article may be helpful:
How can I make my ArrayAdapter follow the ViewHolder pattern?
This implementation is incomplete add else section which sets value from data. here is implementation which worked for me
public View getView(final int position, View convertView, ViewGroup parent) {
final ViewHolder viewHolder;
if (convertView == null) {
viewHolder = new ViewHolder();
LayoutInflater inflator = reportViewContext.getLayoutInflater();
convertView = inflator.inflate(R.layout.tabitems, null);
viewHolder.name= (TextView) convertView
.findViewById(R.id.user_reportTab_userName);
//set other values needed in viewHolder if any
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder)convertView.getTag();
}
//data is list of Data model
if(data.size()<=0) {
//if data unavailable }
else {
/***** Get each Model object from Array list ********/
System.out.println("Position:"+position);
/************ Set Model values in Holder elements ***********/
viewHolder.name.setText(data.get(position).getName());
}
return convertView;
}
I know the question is very old, answered in case anyone stumbled on similar problem

Categories

Resources