How do I create a neverending listview of list items with checkboxes that can be removed with a delete item button? The answer is below.
-
it's actually a null pointer on the isChecked method, but it is because my getTag method isn't working correctly.Dave Hulse– Dave Hulse2013-11-19 20:16:32 +00:00Commented Nov 19, 2013 at 20:16
-
When you call getListView().getTag(i), it's getting the Tag from the whole ListView, not the individual row, as I suspect you're trying to do. And, if you've not called ListView.setTag(), it will always return null.Mike M.– Mike M.2013-11-20 02:33:22 +00:00Commented Nov 20, 2013 at 2:33
-
I hadn't actually done a checked ListView yet, so I wrote a simple test app for multiple deletion. Lemme know if you want me to share some code or suggestions.Mike M.– Mike M.2013-11-20 05:47:57 +00:00Commented Nov 20, 2013 at 5:47
-
that would be great Mike. I am now going back over the tutorial at vogella.com/articles/AndroidListView/… we'll see if I can work this out by tomorrow.Dave Hulse– Dave Hulse2013-11-20 13:35:58 +00:00Commented Nov 20, 2013 at 13:35
-
I think I figured it out.. anyone who finds this and needs help can have some of my code if they email me at [email protected]Dave Hulse– Dave Hulse2013-11-20 18:03:08 +00:00Commented Nov 20, 2013 at 18:03
1 Answer
In order to create a neverending listview the first thing you need to have is a set of two runnables. These threads will update the array of data in your adapter.
final int itemsPerPage = 100;
ArrayList<HashMap<String,String>> listItems = new ArrayList<HashMap<String,String>>();
boolean loadingMore = false;
int item = 0;
//Since we cant update our UI from a thread this Runnable takes care of that!
public Runnable returnRes = new Runnable() {
@Override
public void run() {
//Loop thru the new items and add them to the adapter
if(groceries.getGroceries().size() > 0){
for(int i=0;i < listItems.size();i++) {
HashMap<String,String> grocery = listItems.get(i);
adapter.add(grocery);
}
//Update the Application title
setTitle("Grocery List with " + String.valueOf(groceries.getGroceries().size()) + " items");
//Tell to the adapter that changes have been made, this will cause the list to refresh
adapter.notifyDataSetChanged();
//Done loading more.
loadingMore = false;
}
}
};
//Runnable to load the items
public Runnable loadMoreListItems = new Runnable() {
@Override
public void run() {
//Set flag so we cant load new items 2 at the same time
loadingMore = true;
//Reset the array that holds the new items
listItems = new ArrayList<HashMap<String,String>>();
//Get 8 new listitems
for (int i = 0; i < itemsPerPage; i++) {
if (i < groceries.getGroceries().size()) {
listItems.add(groceries.getGroceries().get(i));
item++;
}
}
//Done! now continue on the UI thread
runOnUiThread(returnRes);
}
};
Then your onCreate() method should look something like this with an array passed to your adapter:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_create_grocery_list);
//add the footer before adding the adapter, else the footer will not load!
View footerView = ((LayoutInflater)this.getSystemService(Context.LAYOUT_INFLATER_SERVICE))
.inflate(R.layout.activity_footer_view, null, false);
this.getListView().addFooterView(footerView);
adapter = new ListViewAdapter(this,groceries);
setListAdapter(adapter);
getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
//Here is where the magic happens
this.getListView().setOnScrollListener(new OnScrollListener(){
//useless here, skip!
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {}
//dumdumdum
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
//what is the bottom iten that is visible
int lastInScreen = firstVisibleItem + visibleItemCount;
//is the bottom item visible & not loading more already ? Load more !
if((lastInScreen == totalItemCount) && !loadingMore && item < groceries.getGroceries().size()){
Thread thread = new Thread(null, loadMoreListItems);
thread.start();
}
}
});
}
You will also need a delete method to remove the items with checkboxes and a checkOff method as well. They look like this:
ArrayList<Integer> checkedBoxes = new ArrayList<Integer>();
ArrayList<HashMap<String,String>> checkedItems = new ArrayList<HashMap<String,String>>();
public void deleteItem(View view) {
if (checkedBoxes.size() > 1 || checkedBoxes.size() == 0) {
Toast.makeText(getApplicationContext(), "You can only delete one item at a time. Sorry :(", Toast.LENGTH_LONG).show();
return;
} else {
checkedItems.add(groceries.getGroceries().get(checkedBoxes.get(0)));
groceries.getGroceries().removeAll(checkedItems);
checkedBoxes.clear();
try {
groceries.serialize();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Intent intent = new Intent(getApplicationContext(),CreateGroceryList.class);
startActivity(intent);
}
}
public void checkOff(View view) {
CheckBox box = (CheckBox)view;
DataModel d = (DataModel)box.getTag();
if(!checkedBoxes.contains(d.index)) {
checkedBoxes.add(d.index);
} else {
checkedBoxes.remove((Integer)d.index);
}
}
In order to communicate with the adapter it is helpful to have a DataModel class that will model our information. My DataModel has an index variable to keep track of the selected item.
public class DataModel {
int index;
HashMap<String,String> data;
boolean selected;
public DataModel(int i) {
index = i;
data = new HashMap<String,String>();
selected = false;
}
public HashMap<String, String> getData() {
return data;
}
public void setData(HashMap<String, String> data) {
this.data = data;
}
public boolean isSelected() {
return selected;
}
public void setSelected(boolean selected) {
this.selected = selected;
}
}
Finally, here is the code for the BaseAdapter:
public class ListViewAdapter extends BaseAdapter {//To create an adapter we have to extend BaseAdapter instead of Activity, or whatever.
private ListActivity activity;
private View vi;
private ArrayList<DataModel> data;
private static LayoutInflater inflater=null;
public ListViewAdapter(ListActivity a, GroceryList g) {
activity = a;
data = new ArrayList<DataModel>();
inflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
groceries = g;
}
public void add(HashMap<String,String> a){
DataModel d = new DataModel(data.size());
d.setData(a);
d.setSelected(false);
data.add(d);
}
public ArrayList<DataModel> getData() {
return data;
}
public int getCount() { //get the number of elements in the listview
return data.size();
}
public Object getItem(int position) { //this method returns on Object by position
return position;
}
public long getItemId(int position) { //get item id by position
return position;
}
public View getView() {
return vi;
}
public View getView(int position, View convertView, ViewGroup parent) { //getView method is the method which populates the listview with our personalized rows
vi=convertView;
final ViewHolder holder = new ViewHolder();
if(convertView==null) {
vi = inflater.inflate(R.layout.custom_row_view, null);
//every item in listview uses xml "listview_row"'s design
holder.name = (CheckBox)vi.findViewById(R.id.name);
holder.price = (TextView)vi.findViewById(R.id.price); // You can enter anything you want, buttons, radiobuttons, images, etc.
holder.quantity = (TextView)vi.findViewById(R.id.quantity);
holder.name
.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
DataModel element = (DataModel) holder.name
.getTag();
element.setSelected(buttonView.isChecked());
}
});
vi.setTag(holder);
holder.name.setTag(data.get(position));
ViewHolder vholder = (ViewHolder) vi.getTag();
vholder.name.setChecked(data.get(position).isSelected());
HashMap<String, String> hash = new HashMap<String, String>(); //We need a HashMap to store our data for any item
hash = data.get(position).getData();
vholder.name.setText(hash.get("brand") + " " + hash.get("name")); //We personalize our row's items.
vholder.price.setText("$" + hash.get("price"));
vholder.quantity.setText("Quantity: " + hash.get("quantity"));
} else {
vi = convertView;
((ViewHolder) vi.getTag()).name.setTag(data.get(position));
}
if (holder.name == null) {
ViewHolder vholder = (ViewHolder) vi.getTag();
vholder.name.setChecked(data.get(position).isSelected());
HashMap<String, String> hash = new HashMap<String, String>(); //We need a HashMap to store our data for any item
hash = data.get(position).getData();
vholder.name.setText(hash.get("brand") + " " + hash.get("name")); //We personalize our row's items.
vholder.price.setText("$" + hash.get("price"));
vholder.quantity.setText("Quantity: " + hash.get("quantity"));
}
return vi;
}
}
class ViewHolder {
CheckBox name;
TextView price;
TextView quantity;
public CheckBox getName() {
return name;
}
public void setName(CheckBox name) {
this.name = name;
}
public TextView getPrice() {
return price;
}
public void setPrice(TextView price) {
this.price = price;
}
public TextView getQuantity() {
return quantity;
}
public void setQuantity(TextView quantity) {
this.quantity = quantity;
}
}
You also need a few xml files in your layout folder this is what they will look like:
You need a footerview that will tell your list when to load new items:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:gravity="center_horizontal"
android:padding="3dp"
android:layout_height="fill_parent">
<TextView
android:id="@id/android:empty"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:gravity="center"
android:padding="5dp"
android:text="Add more grocery items..."/>
A custom row view that is populated by your BaseAdapter:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<CheckBox
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="CheckBox"
android:focusable="false"
android:textSize="25dip"
android:onClick="checkOff"
/>
<TextView
android:id="@+id/quantity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="40dip"
android:text="Lastname"
android:textSize="15dip" />
<TextView
android:id="@+id/price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="40dip"
android:text="Lastname"
android:textSize="15dip" />
</LinearLayout>
And a parent view, mine is called create_grocery_list because I'm writing a grocery list editor: This one must contain a ListView with the proper id.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="400dp" >
<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1" >
</ListView>
</LinearLayout>
<AbsoluteLayout
android:layout_width="match_parent"
android:layout_height="72dp" >
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_x="105dp"
android:layout_y="0dp"
android:onClick="deleteItem"
android:text="@string/deleteItem" />
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_x="8dp"
android:layout_y="0dp"
android:onClick="goToAddItemScreen"
android:text="@string/addItem" />
<Button
android:id="@+id/button3"
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_x="221dp"
android:layout_y="0dp"
android:onClick="scanner"
android:text="@string/scanCode" />
</AbsoluteLayout>
</LinearLayout>
And that's about it... hope this helps anyone. It's the most complete tutorial you'll find.
I learned all this from this tutorial: http://www.vogella.com/articles/AndroidListView/article.html#androidlists_overview then added the two runnables to make a neverending grocery list :) have fun programming...