I have a ListView which uses CHOICE_MODE_MULTIPLE.
Once the user has made their selections, I need to remove the selected items from the list.
I have used getCheckedItemPositions() to return the SparseBooleanArray of checked items.
All the documentation and related questions I've read indicate that to remove items from a list efficiently, you should use an Iterator, but I can't figure out how to do this.
I have achieved the same operation by looping though the items in the ListView by index and using a temporary clone of the items list to look up which items should be removed (see code below). But supposedly looping through indexes like this to remove items from a List = Bad Coding Monkey. I'm told I should do it with an Iterator instead. But how?
protected void removeItems() {
SparseBooleanArray itemsSelectionState = mItemsListView.getCheckedItemPositions();
ArrayList<String> itemsClone = (ArrayList<String>) mItems.clone();
for (int i = 0; i < mItemsListView.getCount(); i++) {
if (itemsSelectionState.get(i) == true) {
mItems.remove(itemsClone.get(i));
}
}
mItemsListView.clearChoices();
mItemsListAdapter.notifyDataSetChanged();
}
You need the position anyway to lookup the boolean value in the Sparse array. there's not much point in using an iterator if you have to maintain an index value to determine if you should remove that item.
Yes, I think your approach is valid. The iterator does have a remove() method, but you would still need have an index handy. If the id's are stable (with ArrayList based adapters, the id usually equals the position) then it might be more efficient to use getItemIds () and iterate through that list, using the id's as positions.
// could also store ids[] and pre-allocate mItemsClone to ids.length
for (long id : ..getItemIds() ){
itemsClone.put( mItems.get( (int) id) );
}
and that might be a bit more efficient since it's only one pass
Related
I'm using listview with ArrayAdapter and have following code:
final SparseBooleanArray checked = listViewFilters.getCheckedItemPositions();
if (checked.size() == listViewFilters.getAdapter().getCount()) {
//all is selected
} else {
//collect each value
}
All worked fine until I needed all items to be selected from start. So I added this code for selecting all items:
for (int i = 0; i < listViewFilters.getAdapter().getCount(); i++) {
listViewFilters.setItemChecked(i, true);
}
After adding above code, if I checked off some items, checked.size() = count of all items anyway. Expected result is - checked.size() < count.
Thanks.
well. listView.getCheckedItemCount() works fine.
It's weird to see that listView.getCheckedItemCount() returns different value than listView.getCheckedItemPositions().size() only after setItemChecked(i, true)
getCheckedItemPositions might prove to be a little tricky. As you noticed, based on the interaction with the ListView, it might return different results.
Short version: once you selected and deselected an item, that item is still returned by getCheckedItemPositions, with a value of false. So you need to actually read the values from the keys instead of relying on the size.
In an Android app, I have a common setup -- a ListView with an ArrayAdapter. At a certain point I call the adapter's getFilter().filter() method, which very nicely restricts the collection of ListView items displayed on the screen. But what I'd really like to do is obtain, programmatically, a list of those displayed items. The Filter object itself seems to "know" this information, but I can't get at that information because Filter.values() is protected. That is, I'd like to do something like this:
Filter myfilter = adapter.getFilter();
myfilter.filter(text, new Filter.FilterListener() {
#Override
public void onFilterComplete(int count) {
Filter.FilterResults results = myfilter.values(); // Won't compile!
... do something with results ...
}
}
Is there a way to get what I want, short of implementing my own subclass of ArrayAdapter? I'm thinking, if Google has gone to the trouble of giving us a count of the number of items that have passed through the filter (that is, through the Filter.FilterListener.onFilterComplete(int count) method), they would have made the items themselves available...somehow, somewhere?
When an ArrayAdapter is filtered...the problem is getting the undisplayed items. The displayed items is all you'll ever get to access. So once you conduct a filter, you can simply iterate through the adapter to obtain each item. Pseudocode example:
List<Foo> data = new ArrayList<Foo>();
int count = mAdapter.getCount();
for (int index = 0; index < count; ++index) {
data.add(mAdapter.getItem(index));
}
This is the generic way to obtain the list of items you've placed into the adapter. If a filter operation never happened, it'll return you all the items in the adapter. If filtering has happened, it'll only return you the displayed data. There are some oddities when working with filtering and the ArrayAdapter. I'd recommend reading this, for further info on it's bugs and gotchas.
I can retrieve a list of all items of a spinner by:
List<String> list = new ArrayList<String>();
for (int i =0; i<spinner.getCount(); ++i)
{
String item = String.valueOf(spinner.getItemAtPosition(i));
list.add(item);
}
Or storing the item list globally...
Is there any more elegant way, something like .getItemList()?
My concern is the iteration (linear complexity), I would prefer to directly get the list from the adapter (possibly constant complexity?)
See http://developer.android.com/reference/android/widget/Adapter.html and http://developer.android.com/reference/android/widget/SpinnerAdapter.html (which inherits from Adapter)
You will find that the method you're looking for doesn't exist
Your solution is about as elegant as it gets
I want to have a listview with a custom adatper, extends ArrayAdapter, where Type has Name field (String). I want to sort the items Alphabitcly (this is not a problem) and then put Alphabetic sections.
For example:
A
Alpha
Any
...
C
Child
...
I
ink
Also - results are retrieved from a webservice using Paging, so when user presses the GetMoreResult button, list should be updated accordingly - will notifyDataSetChanged will work?
I was trying the same thing. I tried doing it using the currentChar logic mentioned by Kasper. It appears that the listView() calls are not always in the order you'd expect so that doesn't work very well. My solution is like this:
In the XML add a TextView with visibility GONE like so. This will be your separator tag.
<TextView
style="?android:attr/listSeparatorTextViewStyle"
android:id="#+id/separator"
android:layout_width="fill_parent"
android:visibility="gone"
android:layout_height="wrap_content"
android:textColor="#android:color/white" />
NOTE: I use the Android standard separator style. Of course you can make your own.
Then add a boolean array to you Adapter class with a method something like this
/**
* Method takes the alphabetically sorted ArrayList as argument
*/
private void assignSeparatorPositions(ArrayList<Items> items) {
separatorAtIndex = new boolean[items.size()];
char currentChar = 0;
for (int i=0; i< items.size(); i++) {
if ( itemss.get(i).getName().charAt(0) != currentChar ) {
separatorAtIndex[i] = true;
} else {
separatorAtIndex[i] = false;
}
currentChar = items.get(i).getName().charAt(0);
}
}
Finally in you getView() do something like this:
if ( separatorAtIndex[position] ) {
holder.sectionHeader.setText(""+name.charAt(0));
holder.sectionHeader.setVisibility(View.VISIBLE);
} else {
holder.sectionHeader.setVisibility(View.GONE);
}
This method has the advantage of not requiring you to keep track of the new positions of items after adding a separate header view.
You can download code for listview with alphabets.
http://www.anddev.org/code-snippets-for-android-f33/alphabetical-listview-in-android-t56983.html
What you need is a seperator. Use this guide to figure out how to add a seperator.
Then in your Adapter you need a variable that can tell you where in the alphabet you are.
private Char currentChar;
In the getView-method you will then need to determine if you're adding a normal Item or you're adding a seperator. Something like:
if( currentItem.getCharAt( 0 ) != currentChar )
//add a seperator.
else
//add an item.
One thing to remember is that you might mix with the indexes of the items, because the ListView will suddenly contain more items than the array. One way to fix this, is to add dummy items (could just be a null object) to the array.
notifyDataSetChanged will update the list, yes.
Just remember not to reassign your list object when updating as your list adapter retains a reference to the one you passed it at construction. Just clear it and refill it before calling notifyDataSetChanged.
To make Saad Farooq's answer work with a CursorAdapter, I use something like the following hack in getView():
String thisTitle = getStringValue(cursor, TITLE_NAME);
char thisSection = thisTitle.toUpperCase().charAt(0);
holder.sectionHeader.setText(thisSection);
holder.separator.setVisibility(View.GONE);
// Check if the previous entry was a different alphabetic character
if (cursor.moveToPrevious())
{
String prevTitle = getStringValue(cursor, TITLE_NAME);
char prevSection = prevTitle.toUpperCase().charAt(0);
if (prevSection != prevTitle)
{
holder.separator.setVisibility(View.VISIBLE);
}
cursor.moveToNext();
}
Note that there are better ways to show a segmented list like this, but this works for a quick hack when needed.
is it possible to programatically access specific rows in a list of CheckedTextViews to change the state of their textboxes?
my program has a listview which has several CheckedTextViews which the user can press to toggle state.
I want to save the state of the checkboxes when the user leaves the activity, so I have in my onPause method:
public void onPause(){
super.onPause();
SparseBooleanArray positions;
positions = listView.getCheckedItemPositions();
ListAdapter items = listView.getAdapter();
int j = items.getCount();
ArrayList<Long> ids = new ArrayList<Long>();
for (int k =0; k < j;k++){
if(positions.get(k)==true){
ids.add(items.getItemId(k));
}
}
this.application.getServicesHelper().open();
this.application.getServicesHelper().storeServices(ids,visit_id);
this.application.getServicesHelper().close();
}
which very simply iterates the list view, adds the checked items to an ArrayList and then saves that list of ids to the database.
My problem lise in trying to reset the list once a user goes back to that activity.
so far in my onStart method, I recall the checked items from the database, but I do not know how to march the ids returned to the listview elements. can I do something like:
listView.getElementById(id_from_database).setChecked?
I know I cant use getElementById but I have it here to show what I mean
Thanks in advance
Kevin
You can call
listView.setItemChecked(int position, boolean value)
This is what Ive ended up doing.. but it seems like a complete hack.
Basically I have to set up a double for loop.. one to iterate through my list elements, and one to iterate through the cursor that I have retreived my check list state (a simply array of ids of elements that were checked when state was last saved)
my outer for iterates through the list elements checking each id against a loop through the list of ids to be set as checked. if they equal each other then set that item as checked.
// mAdapter is contains the list of elements I want to display in my list.
ServiceList.this.setListAdapter(mAdapter);
// Getting a list of element Ids that had been previously checked by the user. getState is a function I have defined in my ServicesAdapter file.
Cursor state = ServiceList.this.application.getServicesHelper().getState(visit_id);
int checks = state.getCount();
int check_service;
int c = mAdapter.getCount();
if(checks>0){
state.moveToFirst();
for (int i=0; i<checks; i++) {
// set check_service = the next id to be checked
check_service = state.getInt(0);
for(int p=0;p<c;p++){
if(mAdapter.getItemId(p)==check_service){
// we have found an id that needs to be checked. 'p' corresponds to its position in my listView
listView.setItemChecked(p,true);
break;
}
}
state.moveToNext();
}
}
ServiceList.this.application.getServicesHelper().close();
Please tell me there is a more efficient way of achieving this!!
Thanks
Kevin