Populating single ListView with different row layouts - java

I am attempting to populate a ListView with a column from a database. I can achieve this successfully, but the problem is i would like to use a different layout resource depending on if the id matches a PendingIntent.
This is to check if an alarm exists with the rowid, and i plan to have visual feedback in the listview to show the user which alarms exist. I can successfully check the rowid against PendingIntents and get a return boolean.
The issue I run into is populating the ListView with different layout resources for different rows (is this even possible?) and i have no idea where to go from here.
I currently have the following code:
for(int x = 0; x < numNotes; x++) {
if(existAlarm(intarray[x])){
String[] from = new String[] { TodoDbAdapter.KEY_SUMMARY };
int[] to = new int[] { R.id.label1};
cursor = dbHelper.fetchTodo(intarray[x]);
SimpleCursorAdapter notes = new SimpleCursorAdapter(this,
R.layout.todo_row_green, cursor, from, to);
setListAdapter(notes);
} else if (!existAlarm(intarray[x])){
String[] from = new String[] { TodoDbAdapter.KEY_SUMMARY };
int[] to = new int[] { R.id.label};
cursor = dbHelper.fetchTodo(intarray[x]);
SimpleCursorAdapter notes = new SimpleCursorAdapter(this,
R.layout.todo_row, cursor, from, to);
setListAdapter(notes);
}
}
But the ListView just shows the final database entry from the loop.
I would be very grateful for any guidance, Thanks.

Of course, only the last item will be shown. This is because you keep changing the list adapter each time you enter the loop and execute. Get rid of the loop.
If I understand correctly, you want to display a either a green row or not depending on the ToDo. If so, write your own list adapter which is a subclass of the SimpleCursorAdapter. Then override the newView (Context context, Cursor cursor, ViewGroup parent) method to choose view to use to display the row. The cursor parameter is already moved to the correct position, so you can retrieve values from the DB for any necessary comparisons.
For example: I assume (because of the fetchTodo(intarray[x])) that you are comparing based on the id of the ToDo. In that case your code may look like:
public View newView (Context context, Cursor cursor, ViewGroup parent){
long id = cursor.getLong(TodoDbAdapter.ROW_ID_COLUMN);
LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (existAlarm(id)){
return inflater.inflate(R.layout.todo_row_green, null);
} else {
return inflater.inflate(R.layout.todo_row, null);
}
}
I don't know if I understood your code well, but you can adapt this to suit your needs.

Its quite possible to use a different view in each row: you however can't use a standard SimpleCursorAdapter for it: you have to overload the getView() function. An example is at Trying to override getView in a SimpleCursorAdapter gives NullPointerExceptio (there are a couple of errors which are answered in the post, but the basic technique is correct).
As far as showing only the last post, your code is creating a NEW SimpleCursorAdapter for every row, which is incorrect. You want to use the getView() override and then do the for loop and comparison in there.

Related

index list view with database cardview

I know there are alot of duplicates with issues like these but pleas do read mine and help me out.
I am very new to Android development as such I coded these with my own instincts and limited guides available.
I'd like to implement an indexable (A-Z) side panel just like in contacts. All the posts available are array strings with hard-coded entries but mine aren't, I am using DB Browser for SQLite and then populate each data in a cardview.
I have referred to (https://github.com/woozzu/IndexableListView/blob/master/src/com/woozzu/android/widget/IndexableListView.java) and tried all the solutions mentioned related to this post but to no avail. I believe I simply coded it the wrong way as the entries are hard-coded but mine aren't.
I am currently following this guide, (http://androidopentutorials.com/android-listview-with-alphabetical-side-index/) and have amended some codes here and there which I thought is necessary for my situation, such as adding my database into an array. However, there are multiple errors and I don't know what to do.
As such, I have no idea how to solve these errors proceed on. Please do help me out and provide me with, perhaps a step-by-step tutorial or even better video guides so that I could follow through.
Below are what I have tried:
Declaration
//indexable list view
Map<String, Integer> mapIndex;
List<String> dbList = new ArrayList<>();
OnCreate
//indexable list view
String[] dbList= database.getKeyword();
Arrays.asList(dbList);
dbList.setAdapter(new ArrayAdapter<String>(this,
android.R.layout.simple_expandable_list_item_1, dbList));
etIndexList(dbList);
displayIndex();
//indexable list view
private void getIndexList(String[] dbList){
mapIndex = new LinkedHashMap<String, Integer>();
for(int i = 0; i < dbList.length; i++){
String db = dbList[i];
String index = db.substring(0,1);
if(mapIndex.get(index) == null)
mapIndex.put(index, i);
}
}
private void displayIndex(){
LinearLayout indexLayout = (LinearLayout) findViewById(R.id.side_index);
TextView textView;
List<String> dbList = new ArrayList<String>(mapIndex.keySet());
for (String index : dbList){
textView = (TextView) getLayoutInflater().inflate(R.layout.side_index_item, null);
textView.setText(index);
textView.setOnClickListener(this);
indexLayout.addView(textView);
}
}
public void onClick(View view){
TextView selectedIndex = (TextView) view;
dbList.setSelection(mapIndex.get(selectedIndex.getText()));
}
The errors are:
If you require any more codes do let me know and I will update this post. Thank you in advance.
follow this lib for indexing list no need to write extra function this lib manage all the case
Answer to the errors that you are getting:
You are calling dbList.setAdapter(): dbList is not a ListView or RecyclerView
Call setAdapter() method on appropriate ListView or RecyclerView variable.
You are passing this to setOnClickListener() method: this is pointing to your KnowledgeActivity and not a View.OnClickListener.
Solution: Either implement View.OnClickListener in your activity or call new OnClickListener like this,
textView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
}});
database.keyword() returns List and you are trying to cast it to String[]

ListView OnLongClick strange behaviour

I have a ListView within my project. It has many elements, and it uses a custom adapter, since its populated dynamically from a rails server.
I want to change the content of a ListItem when the item is longpressed. In order to achieve this, I have 2 layouts inside the ListItem, with one visible and one hidden.
The issue is that when I longpress an item, the layout changes (As expected), but other ListItems are also affected, and changed in the same way. This appear to occur once for every 5 items, and I cant figure out why.
This is the LongClickListener I'm using, it is located inside de GetView method on the custom adapter:
View v = convertView;
if (v == null){
LayoutInflater vi =
(LayoutInflater)getActivity().getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.list_item, null);
}
final LinearLayout placeInfo =
(LinearLayout) v.findViewById(R.id.list_item_info);
final RelativeLayout placeBrief =
(RelativeLayout)v.findViewById(R.id.list_item_brief);
v.setOnLongClickListener(new OnLongClickListener(){
#Override public boolean onLongClick(View v) {
placeInfo.setVisibility(View.GONE);
placeBrief.setVisibility(View.VISIBLE);
return false;
}});
I would appreciate any help, many thanks in advance.
ListViews recycle Views, so you only have a few views for all of your items. You're directly changing one of these view instances to switch between the info|brief. What you need is to save the status of the info|brief flag for the affected position somewhere else (e.g. a list of positions that should be "briefs" in the adapter). That way when you come back into getView() you can display the right one.

get the View at the position X of a ListView

How can I retrieve the View at the position X in a ListView? I dont want to inflate a new one, just retrieve the cell visible in the screen for change some parameters programmatically
Since views in ListView are re-used/re-cycled. There is no direct way of getting a view reference from the ListView.
If you want to access a view you need to extend ArrayAdapter and then override getView. There you should call the super.getView and write your own custom code.
If we you really need to control more than try extending BaseAdapter or CursorAdapter.
Found a dirty solution:
You should be able of identify each row generated. For example adding a TextView with visibility=gone and writing a unique value there when generating (or recycling the row)
In the listactivity call to getListView.setSelection(position) to the desired cell
Survey the listview list for the row (until displayed)
lv=getListView();
for (int i=0;i <lv.getChildCount();i++){
if (((TextView)lv.findViewById(R.id.my_hidden_textview)).getText.equals(mykey)){
// view found
} else {
// schedule another survey "soon"
}
}
For the schedule you can use something like:
final int RETRY_DELAY=100;
new Handler(){
public void handleMessage(Message msg){
if (msg.what<0) return; //something went wrong and retries expired
lv=getListView();
for (int i=0;i <lv.getChildCount();i++){
if (((TextView)lv.findViewById(R.id.my_hidden_textview)).getText.equals(mykey)){
//result = lv.findViewById(R.id.my_hidden_textview);
} else {
this.sendEmptyMessageDelayed(msg.what-1,RETRY_DELAY);
}
}
}
}.sendEmptyMessageDelayed(10,RETRY_DELAY);
As I said is a very ugly solution but it works
I didn't clearly understand your problem. But to what I've understood I would suggest you use a frame layout within a linear layout. You can use another frame layout to do your manipulations.

Format data before showing on ListAdapter

I have a ListAdapter which takes the dates from my SQLite database and displays them all on the list. The thing is, the date is not in human readable format, and I have a helper method to perform the conversion, but how do I implement it on my code?
This is how my code looks like:
// Get all of the notes from the database and create the item list
Cursor c = mDbHelper.fetchAllItems();
startManagingCursor(c);
String[] from = new String[] { TrackerDbAdapter.KEY_DATE };
int[] to = new int[] { R.id.row_date };
// Now create an array adapter and set it to display using our row
SimpleCursorAdapter history =
new SimpleCursorAdapter(this, R.layout.history_row, c, from, to);
setListAdapter(history);
I would try creating a custom ListAdapter or custom SimpleCursorAdapter
If you do not need to use a cursor, have a look at this link. http://www.softwarepassion.com/android-series-custom-listview-items-and-adapters/ It explains how to use an arraylist and custom listAdpater.
You can also do the same with the SimpleCursorAdapter. I was unable to find a good tutorial at this time. I will add to this when I do
Use SimpleCursorAdapter.ViewBinder to attach the formatted data to Views.
SimpleCursorAdapter.ViewBinder dataBinder = new SimpleCursorAdapter.ViewBinder() {
#Override
public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
((TextView)view).setText(cursor.getString(columnIndex));
return true;
}
};
simpleCursorAdapter.setViewBinder(dataBinder)
Option 1: Like #nguyendat said, for performance, you could store the formatted date in the database, as well as the un-formatted version, to give you the most flexibility. If in the same table though, this would violate 2nd normal form because of the redundancy, and you would have to be careful in your code to update all of the data in the row.
To implement this, put your conversion code in your DBAdapter, in the insert command.
Option 2: Create a class for your date
public class FormattedDate {
private int oneDate;
public Punch (int unformattedDate) {
oneDate = unformattedDate;
} // ends constructor
#Override
public String toString() {
//put your conversion code here
return myFormattedDate;
}}
This has the added benefit of a proper place to put any other code for comparisons or conversions.
Inside your DBAdapter, change your query to this
public ArrayList<FormattedDate> fetchAllItems() {
ArrayList<FormattedDate> results = new ArrayList<FormattedDate>();
Cursor c = db.rawQuery("SELECT MY_UNFORMATTED_DATE FROM yourTable", null);
if (c.getCount() > 0) {
c.moveToFirst();
do {
results.add( new FormattedDate(c.getInt(c.getColumnIndex(MY_UNFORMATTED_DATE))));
} while (c.moveToNext());
}
c.close();
return results;
}
This returns an ArrayList of FormattedDate objects
Finally, this would populate a listview
setContentView(R.layout.my_list_view);
ArrayList<FormattedDate> dateArray = mDBHelper.fetchAllItens();
ArrayAdapter<FormattedDate> dateAdapter = new ArrayAdapter<FormattedDate> (getApplicationContext(), R.layout.list_item, dateArray);
setListAdapter(dateAdapter);

Android custom list slow

Is there a better way to create a custom list, for my code here seems to make for a less-responsive list. I can't use any of the standard Android lists because I need two ListView's in a ScrollView.
setContentView(R.layout.alertsview);
inflater = (LayoutInflater)this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
topRows = new ArrayList<View>();
topLayout = (LinearLayout)findViewById(R.id.topListLayout);
topLayout.removeAllViews();
topRows.clear();
List<Camera> camList = new ArrayList<Camera>(SitesOrchestrator.getInstance().currentSite.cameraList);
for (int i = 0; i < camList.size(); i++)
{
Camera cam = camList.get(i);
View row = inflater.inflate(R.layout.cameraalertrow, null);
TextView name = (TextView)row.findViewById(R.id.cameraNameAlerts);
CheckBox chk = (CheckBox)row.findViewById(R.id.camAlertChk);
name.setText(cam.name);
chk.setChecked(cam.alertsEnabled);
chk.setOnCheckedChangeListener(this);
String index = Integer.toString(i);
chk.setTag(index);
topRows.add(row);
topLayout.addView(row);
}
If you need to lists in the same layout, you should create your own Adapter (derive from base adapter might be good), and, suppose you have two arraylist:
ArrayList<TypeA> typeAList;
ArrayList<TypeB> typeBList;
#Override
int getViewTypeCount(){ return 2; } // means you have two different views from it
#Override
int getItemViewType(int position){
if (position>typeAList.size()) return 1;
return 0;
}
getView(int pos, View convertView, ViewGroup parent){
// Check the pos
if (getItemViewType(pos) == 0){
// Inflate view and bind for type A
}
else{
// Inflate view and bind for type B
}
}
In general, having two list view vertically is not encouraged in Android, but putting both of your content to one list do the trick. I also have a tutorial about this, though it is done in MVVM with Android-Binding.
Moreover, adding Views one by one to ScrollView to mimic the ListView would, of course, be very inefficient. The way Android ListView works (which should different from desktop frameworks) is with recycling views. Suppose you are scrolling up, once the child is scroll out of sight, the listview will recycle the topmost one and place it to the bottom, and the possibly recycled view will pass as convertView in the above getView code. Inflating is considered to be quite expensive process, and multiple child views are memory consuming as well, that's the reason why your code, compare to standard ListView, is very inefficient.

Categories

Resources