I need my application to apply a strike through to the contents of each item in my ListView based on whether or not the content of each item is marked as checked off or not in my SQLite database. Right now I obtain the text for each item in the list view from a database table, store it in a List, pass that into a ArrayAdapter, and then use that to set my ListView adapter. This is my code:
private ListView taskList = (ListView) view.findViewById(R.id.task_list);
// Get text for each TextView from database
db.getWritableDatabase();
tasks = db.readTasks((int) listId);
// Set up adapter for ListView
adapter = new ArrayAdapter<String>(
view.getContext(),
android.R.layout.simple_expandable_list_item_1,
tasks);
taskList.setAdapter(adapter);
My thought was to iterate through each item in the ListView after it has been set and check to see if the corresponding record in the database had it marked as checked or not, and then act accordingly on the item. I don't know how to iterate over the contents of a ListView however, nor am I certain this is the best way to do this. Does anyone know how to iterate over the contents of a ListView or is there an entirely better way to do this?
Firstly define a Task object with useful properties:
class Task {
public boolean isComplete() {...};
#override String toString() {...};
}
Then make your db method return a list of such objects:
ArrayList<Task> tasks = db.readTasks((int) listId);
Finally, subclass the ArrayAdapter:
private static class TaskAdapter extends ArrayAdapter<Task> {
private ArrayList<Task> mTasks;
TaskAdapter (Context c, ArrayList<Task> list) {
super(c, android.R.layout.simple_spinner_item, list);
mTasks = list;
}
#Override
public View getView(int position, #Nullable View convertView, #NonNull ViewGroup parent) {
View v = super.getView(position, convertView, parent);
TextView txt = v.findViewById(android.R.id.text1);
int flags = txt.getPaintFlags();
if (mList.get(position).isComplete()) {
flags = flags | Paint.STRIKE_THRU_TEXT_FLAG;
} else {
flags = flags & ~Paint.STRIKE_THRU_TEXT_FLAG;
}
txt.setPaintFlags(flags);
}
}
You can also do a lot more when you subclass the adapter: eg. create your own view layout (eg. if you wanted a 'tick' graphic instead of a strikethough).
Google for examples of this kind of thing. There are many.
Related
I am using a custom spinner library, Material Spinner. I have set adapter to the spinner as I want the font size to be different in the getDropDownView. I have three spinners out of which two just show the packagename. The middle spinner is working properly.
Everything is the same for all three spinners other than the array and the width. So why is the middle spinner. I can't figure out what the issue is.
This is not a duplicate question
One reason mentioned is that the getView method should be overridden. As can be seen below, it has been done. What I did find that in the case of the 1st and 3rd spinner the getView method or the getDropDownView method is not being called. I don't understand why.
getItemCount, getItemId, getItem methods are not needed. But when I had these methods also, the result did not change.
MaterialSpinner msDay = (MaterialSpinner) getActivity().findViewById(R.id.fgenderage_ms_day);
List dayList = Arrays.asList(getResources().getStringArray(R.array.fgenderage_day));
msDay.setItems(new EnglishSpinnerAdapter(getActivity(), dayList));
MaterialSpinner msMonth = (MaterialSpinner) getActivity().findViewById(R.id.fgenderage_ms_month);
List monthList = Arrays.asList(getResources().getStringArray(R.array.fgenderage_month));
msMonth.setAdapter(new EnglishSpinnerAdapter(getActivity(), monthList));
MaterialSpinner msYear = (MaterialSpinner) getActivity().findViewById(R.id.fgenderage_ms_year);
List yearList = Arrays.asList(getResources().getStringArray(R.array.fgenderage_year));
msYear.setItems(new EnglishSpinnerAdapter(getActivity(), yearList));
The adapter:
public class EnglishSpinnerAdapter extends MaterialSpinnerAdapter{
private List list;
private Context ctx;
public EnglishSpinnerAdapter(Context context, List items) {
super(context, items);
ctx = context;
list = items;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView textView = new TextView(ctx);
textView.setText(list.get(position).toString());
return textView;
}
#Override
public View getDropDownView(int position, View convertView, ViewGroup parent) {
TextView textView = new TextView(ctx);
textView.setText(list.get(position).toString());
return textView;
}
change this
msDay.setItem(new EnglishSpinnerAdapter(getActivity(), dayList));
to this
msDay.setAdapter(new EnglishSpinnerAdapter(getActivity(), dayList));
Link : ListView MyExample
So I followed the tutorial link above to add subitem using :
static final ArrayList<HashMap<String,String>> list = new ArrayList<HashMap<String,String>>();
to add String for subitem.
Now, I wanted to add a boolean checkbox to the existing String, I use
static final ArrayList<HashMap<String,Boolean>> DAMN=new ArrayList<HashMap<String,Boolean>>();
Example of my private void listViewItem():
private void listViewItem(){
HashMap<String,String> hash=new HashMap<String, String>();
hash.put("Student_name","Joshua");
hash.put("Student_ID","111111464");
list.add(hash);
HashMap<String,Boolean>boo=new HashMap<String,Boolean>();
boo.put("attend",true);
DAMN.add(boo);'
On my onCreate, I include both SimpleAdapter for adapter ( String,String) and (String, Boolean)
SimpleAdapter adapter=new SimpleAdapter(
this,OMG,R.layout.tolist_row,
new String[]{"Student_name","Student_ID"},
new int[] {R.id.StudenName,R.id.studentID});
SimpleAdapter adapter1=new SimpleAdapter(this,DAMN,R.layout.tolist_row1,
new String[] {"attend"},
new int[] {R.id.attend});
listViewItem();
setListAdapter(adapter);
setListAdapter(adapter1);
The problem I can only get is my checkbox checked or unchecked but not the ID and name.
Unless I comment
//setListAdapter(adapter1) only I can get my Student_name and Student_name but without the checkbox ticked.
How do I have both result ?
I cannot give you the whole code but for a start, do something like this:
First create DataHolder for your data i.e: Strings and Booeleans etc. idea if to use a composite custom object as data holder instead of raw Strings and booleans.
public class DataHolder{
private String studentNname;
private Boolean attended;
//add getters and setters etc.
}
Now implement your adapter like this.
public class MyCustomAdapter extends BaseAdapter{
//populate this data using constructor or a setter or any other way
private List<DataHolder> data;
private LayoutInflater inflater = ....
//get the inflater form the fragment or the activity which ever you are using;
//implement overridden and abstract methods
//for counts of items return the size of HashMap
#Override
public View getView(int position, View convertView, ViewGroup parent){
if (convertView == null){
convertView = inflater.inflate(R.layout.<your list item layout xml file>, null);
}
DataHolder dataHolder = this.getItem(position);
//now you have the convertView (your list item view)
//and the data to be show in that view (dataHolder object)
//populate the convertView with the data from the dataHolder
return convertView;
}
}
Finally create an instance of this adapter, provide it the data i.e: List and pass this instance to setAdapter of listView.
I am using a custom ListView, in which I need to add a new item. Since there will be a lot of items needed to be added, I want to code it in the most efficient way so the smartphone is not loosing a lot of memory. Here is the code of my custom ListView:
class MessageActivityAdapter extends ArrayAdapter<String>
{
Context context;
String[] message;
String time;
MessageActivityAdapter(Context c, String[] message, String time)
{
super(c, R.layout.messagescreen_list_row, R.id.textView1, message);
this.context = c;
this.message = message;
this.time = time;
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View row = inflater.inflate(R.layout.messagescreen_list_row, parent, false);
TextView tvMessage = (TextView) row.findViewById(R.id.textView1);
tvMessage.setText(message[position][0]);
return row;
}
}
This is how I load the ListView on the first time:
adapter = new MessageActivityAdapter(this, values, time);
listView.setAdapter(adapter);
This is how I change the text in one of the items and update the ListView:
adapter.message[itemPos] = newItemText;
adapter.notifyDataSetChanged();
And thats how I thought I can add a new item to the ListView. Basically I copy the old array "message" and create a new array "messageNew" which has a +1 bigger size. Then I use "messageNew" as the new array in my MessageActivityAdapter and update the adapter. The problem: copying and creating a new array costs a lot of memory. Why I don't use an ArrayList? Because later I will change the array into a multidimensional array and multidimensional ArrayLists don't exist.
String[] messageNew = getNewMessageArray();
adapter.message = messageNew;
adapter.notifyDataSetChanged();
I'm also not sure if changing the "message" array and using the notifyDataSetChanged() method is the best way at all...
I am trying to modify the sample code below. It currently populates a View that contains an imageview and a textview. I have added an additional textview to my XML layout and am trying to figure out how to replace the simple array with a hash map or even a multidimensional array to populate not just the imageview and the first textview but also the second one.
I would appreciate sample code that shows the entire process. Thanks!
public class DynamicDemo extends ListActivity {
TextView selection;
private static final String[] items={"lorem", "ipsum", "dolor",
"sit", "amet"}
#Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
setListAdapter(new IconicAdapter());
selection=(TextView)findViewById(R.id.selection);
}
public void onListItemClick(ListView parent, View v,
int position, long id) {
selection.setText(items[position]);
}
class IconicAdapter extends ArrayAdapter<String> {
IconicAdapter() {
super(DynamicDemo.this, R.layout.row, R.id.label, items);
}
public View getView(int position, View convertView,
ViewGroup parent) {
View row=super.getView(position, convertView, parent);
ImageView icon=(ImageView)row.findViewById(R.id.icon);
if (items[position].length()>4) {
icon.setImageResource(R.drawable.delete);
}
else {
icon.setImageResource(R.drawable.ok);
}
return(row);
}
}
}
The easiest thing to do is use an ArrayAdapter<MyDataObject> where
public class MyDataObject {
public String string1;
public String string2;
// any other useful attributes
}
And then you would change items to a MyDataObject[] items stored in your class, and instead of doing super.getView(index) you'd do items[index] (which would yield a MyDataObject) and use that data instead.
Also, importantly: you should use the convertView. And possibly the ViewHolder pattern.
Edit: At OP's request, a little more elaboration. Note that this uses the convertView pattern but not the ViewHolder pattern (you should be able to adopt that fairly easily).
In your Adapter, you'd change getView() as follows:
public View getView(int position, View convertView,
ViewGroup parent) {
ViewGroup row;
if(convertView == null){
// create your view here.
row = getLayoutInflater().inflate(R.layout.row);
} else {
row = convertView;
}
// note: when you implement ViewHolder, the ViewHolder will
// hold this reference so that you don't need to look it up every time.
ImageView icon=(ImageView)row.findViewById(R.id.icon);
// here you're employing the "items" array that you were using
// before, except now it contains MyDataObjects. pick out the
// string (or other data you want to check) from the resulting MyDataObject,
// and see if it's longer than 4 characters.
MyDataObject objectAtThisPosition = items[position];
if (objectAtThisPosition.string1.length()>4) {
icon.setImageResource(R.drawable.delete);
}
else {
icon.setImageResource(R.drawable.ok);
}
// Do whatever else you want to with objectAtThisPosition.
return(row);
}
That's it for the easy way, and quite similar to what you have.
Some more detail; if you don't care, skip it. :)
I know that Adapters can seem magical, so in the interest of showing how ListView adapters work, here's an example using a List instead of an Array, so we can remove any magic that ArrayAdapter does with the array behind the scenes. I use a List because they can be more versatile for whatever you're trying to accomplish (ArrayList or LinkedList or what-have-you).
To use a List you'd have the following in your Activity:
private List<MyDataObject> myList = new ArrayList<MyDataObject>();
And instead of items[position] you'd use
MyDataObject objectAtThisPosition = myList.get(position);
If you want to change your data set dynamically, you should probably use this approach (keeping myList at the Activity level) instead of using an Array and an ArrayAdapter. That would mean you'd need to change from extending ArrayAdapter<String> to just extending BaseAdapter<MyDataObject> (most of the methods in BaseAdapter are trivial to implement) since our data size, for example, would be determined by our list, and not the ArrayAdapter's array.
I know that's kind of a fire hose, but let me know if you have any questions!
Use a separator in string, like \t.
Or use an array of straing arrays.
Or use an array of Pair<String, String>.
Or use an array of custom objects.
I have a ListView in a LitsActivity consisting of rows which are inflated from a separate XML file. The rows are populated by convertView method in my custom adapter for this ListView. I'm trying to invoke a context menu on each row. Normally, we do this by calling
registerForContextMenu(ourListViewInstance);
in onCreate method. But it doesn't work for me, onCreateContextMenu method is not called because there is no list rows at this point, they appear a bit later. I tried to use
registerForContextMenu(row);
in getView method of my custom list adapter so that each row gets registered for "long clicks" and it works, but for some reasons it's inacceptable and the usual way is required.
This is my ItemsAdapter which creates instances of ListView rows:
class ItemsAdapter extends ArrayAdapter<ItemsModel> {
public ItemsAdapter(ArrayList<ItemsModel> list) {
super(Items.this, R.layout.custom_row_view, list);
}
private ItemsModel getModel(int position) {
return (((ItemsAdapter) itemsList.getAdapter()).getItem(position));
}
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
View row = convertView;
final ItemsModel currentItemModel = getModel(position); // Model class storing data for all the rows.
ItemsResourceManager resourceManager = null; // class used to easily get and set row views.
if (row == null) {
row = View.inflate(getBaseContext(), R.layout.custom_row_view, null);
row.setClickable(true);
row.setFocusable(true);
row.setBackgroundResource(android.R.drawable.menuitem_background);
resourceManager = new ItemsResourceManager(row);
row.setTag(resourceManager);
} else {
resourceManager = (ItemsResourceManager) row.getTag(); //class used to easily get and set row views.
}
registerForContextMenu(row); // works for each separate LisView row
//... skipped setText actions for this row
}
return row;
}
}
Also I tried to completely clean up my custom_row_view.xml from any focusable elements but it didn't help.
The issue was in the onClickListener in my custom adapter class. It was preventing contextMenu handling because the "short" click was invoked every time. Now both clickListener and ContextMenu handler are located in ListActivity class and it works just fine.