I'm having trouble to make a multiple deletion works on a ListView in android.
1) I've created a CustomAdapter to customize each row with a few buttons and texts (I'll put here only what I think its most important):
public class PlayableCustomAdapter extends ArrayAdapter<String> {
private final boolean[] checkedItems;
private final LayoutInflater mInflater;
private final ActivityCallback activityCallback;
public PlayableCustomAdapter(Context context, ActivityCallback activityCallback,
List<String> files) {
super(context, NO_SELECTION);
this.activityCallback = activityCallback;
this.mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
this.checkedItems = new boolean[files.size()];
addAll(files);
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
View row = convertView;
Holder holder;
if (row == null) {
row = mInflater.inflate(R.layout.list_view_row_song, null);
holder = new Holder();
holder.text = (TextView) row.findViewById(R.id.tvFileName);
holder.configs = (TextView) row.findViewById(R.id.tvFileConfiguration);
holder.checkBox = (CheckBox) row.findViewById(R.id.cbDelete);
holder.playButton = (ImageButton) row.findViewById(R.id.btnPlay);
holder.settingsButton = (ImageButton) row.findViewById(R.id.btnSettings);
holder.text.setText(getItem(position));
holder.configs.setText(getLyricConfiguration(getItem(position)));
row.setTag(holder);
} else {
holder = (Holder) convertView.getTag();
}
holder.playButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
startPrompter(position);
}
});
holder.settingsButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
startSettings(position);
}
});
holder.text.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
startEditFile(position);
}
});
holder.checkBox.setChecked(checkedItems[position]);
holder.checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
checkedItems[position] = true;
activityCallback.showContent();
} else {
verifySelectedItems(position);
}
}
});
return row;
}
private void verifySelectedItems(int position) {
checkedItems[position] = false;
for (boolean checked : checkedItems) {
if (checked)
return;
}
activityCallback.hideContent();
}
public boolean isChecked(int position) {
return checkedItems[position];
}
@Override
public void remove(String s) {
verifySelectedItems(getPosition(s));
super.remove(s);
notifyDataSetChanged();
}
static class Holder {
TextView text;
TextView configs;
ImageButton playButton;
CheckBox checkBox;
ImageButton settingsButton;
}
2) I've used this adapter in a Activity and everything works like a charm, except from the multiple deletion. Only when I clear all the adapter items the listview is updated correctly. When I delete one or more items, the adapter seems to be fine. All the positions are passed correctly. But I don't know how, only the items in the end of the list are removed from the view.
3) As you can see, I'm using notifyDataSetChanged();
after deletion and the problem still happens. The only (and HORRIBLE) solution that I could make it work was recreating the Activity after deletion (recreate();
). The screen blinks, its awful, but it's fully functional. And now that I have some free time I am asking you guys for some help.
Here is the code from the activity that uses the adapter:
private void deleteFilesFromDisk(List<Integer> positionsToDelete, ArrayAdapter adapter) {
File[] files = getAppFiles();
for (int position : positionsToDelete) {
if (files[position].delete()) {
adapter.remove(fileNames.get(position));
} else
showMessage(R.string.delete_file_error, fileNames.get(position));
}
recreate(); // Horrible workaround, but it works!
}
Please remember that this method is only a example. Especially the last line. Without the recreation command, only the items in the end of the list are removed (at least visually speaking, from the UI).
Any sugestions?
Your problem might be here
@Override
public void remove(String s) {
verifySelectedItems(getPosition(s));
super.remove(s);
notifyDataSetChanged();
}
You remove the item from the adapter, but not the index from checkedItems
If i were you, instead of keeping a boolean array to keep track of checked items, I would use an ArrayList<Integer>
and keep all of the checked positions in the list.
You could then change that method to look something like this
@Override
public void remove(String s) {
int pos = getPosition(s);
verifySelectedItems(pos);
remove(s);
checkedItems.remove(Integer.valueOf(pos));
notifyDataSetChanged();
}
And then this...
private void verifySelectedItems(int position) {
checkedItems[position] = false;
for (boolean checked : checkedItems) {
if (checked)
return;
}
activityCallback.hideContent();
}
could instead be this
private void verifySelectedItems(int position) {
if(!checkedItems.contains(position)) {
activityCallback.hideContent();
}
}
EDIT:
Sorry I didn't read the question properly, I think this is your problem
if (row == null) {
row = mInflater.inflate(R.layout.list_view_row_song, null);
holder = new Holder();
holder.text = (TextView) row.findViewById(R.id.tvFileName);
holder.configs = (TextView) row.findViewById(R.id.tvFileConfiguration);
holder.checkBox = (CheckBox) row.findViewById(R.id.cbDelete);
holder.playButton = (ImageButton) row.findViewById(R.id.btnPlay);
holder.settingsButton = (ImageButton) row.findViewById(R.id.btnSettings);
holder.text.setText(getItem(position));
holder.configs.setText(getLyricConfiguration(getItem(position)));
row.setTag(holder);
} else {
holder = (Holder) convertView.getTag();
}
You are removing the correct items, but this prevents the views from being updated after a notifydatasetchanged, maybe try something like this
if(convertView == null) {
convertView = mInflater.inflate(R.layout.list_view_row_song, null);
}
Holder holder = new Holder();
holder.text = (TextView) convertView.findViewById(R.id.tvFileName);
holder.configs = (TextView) convertView.findViewById(R.id.tvFileConfiguration);
holder.checkBox = (CheckBox) convertView.findViewById(R.id.cbDelete);
holder.playButton = (ImageButton) convertView.findViewById(R.id.btnPlay);
holder.settingsButton = (ImageButton) convertView.findViewById(R.id.btnSettings);
holder.text.setText(getItem(position));
holder.configs.setText(getLyricConfiguration(getItem(position)));