Dynamic row layout for ListView - android

I have a ListView where I send data from a database using SimpleCursorAdapter, but I need to have dynamic row layout, because every table can have different number of columns.
Let's say I create TextViews according to the number of columns:
public int getColumnNumbers() {
int i = 0;
cursor = db.rawQuery("PRAGMA table_info("+DATABASE_TABLE_NAME+")", null);
if (cursor.moveToFirst()) {
do {
i++;
} while (cursor.moveToNext());
}
cursor.close();
return i;
}
........
int rowsNumber = getColumnNumbers();
textViews = new TextView[rowsNumber];
for(i = 0; i < rowsNumber; i++) {
textViews[i] = new TextView(this);
textViews[i].setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
}
What I'm basically looking for, is a way to get these TextViews passed to CursorAdapter's (or other adapter's) argument int[] to
I'm pretty new to this, so I would appreciate any help or advice.
EDIT:
I'm adding my implementation of bindViewmethod, which I made with help of the links provided here, in case someone would have to face similar issue in the future.
#Override
public void bindView(View view, Context context, Cursor cursor) {
int i, count = myHelper.getColumnNumbers();
String[] columnNames = myHelper.getColumnNamesString();
String text;
LinearLayout layout = (LinearLayout) view.findViewById(R.id.linear_layout_horizontal);
for(i = 0; i < (count-1); i++) {
TextView textView = new TextView(context);
textView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 1f));
text = cursor.getString(cursor.getColumnIndexOrThrow(columnNames[i+1]));
textView.setText(text);
layout.addView((TextView)textView);
}
}
EDIT 2:
I found out yesterday that the implementation mentioned above works until you need to scroll. After scrolling, ListView gets deformed.
So again, in case someone would like to do something similar, I'm adding my whole Adapter class.
public class DatabaseCursorAdapter extends CursorAdapter {
private DatabaseHelper myHelper;
private int count;
private String[] columnNames;
public DatabaseCursorAdapter(Context context, Cursor cursor) {
super(context, cursor, 0);
myHelper = new DatabaseHelper(context);
count = myHelper.getColumnNumbers();
columnNames = myHelper.getColumnNamesString();
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
View view = LayoutInflater.from(context).inflate(R.layout.text_select, parent, false);
LinearLayout layout = (LinearLayout) view.findViewById(R.id.linear_layout_horizontal);
int i;
for(i = 0; i < count; i++) {
TextView textView = new TextView(context);
textView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, 1f));
textView.setTag(Integer.valueOf(i));
layout.addView(textView);
}
return view;
}
#Override
public void bindView(View view, Context context, Cursor cursor) {
int i;
String text;
for(i = 0; i < count; i++) {
TextView textView = (TextView) view.findViewWithTag(Integer.valueOf(i));
text = cursor.getString(cursor.getColumnIndexOrThrow(columnNames[i]));
textView.setText(text);
}
}
}
In the and, as it is with most issues, the solution is pretty easy. Technique is almost the same as if you would use a static layout, except instead of using findViewById, you just tag elements of your layout and use findViewWithTag method.

You need to look at good tutorial, a link is
Populating a ListView with a CursorAdapter . The web page has some nice explanations.
Look at TodoCursorAdapter and the bindView method to check which column/data is available from the database. Snippet of code from tutorial:
#Override
public void bindView(View view, Context context, Cursor cursor) {
// Find fields to populate in inflated template
TextView tvBody = (TextView) view.findViewById(R.id.tvBody);
// Extract properties from cursor
String body = cursor.getString(cursor.getColumnIndexOrThrow("body"));
// Populate fields with extracted properties
tvBody.setText(body);
}
I think this is a simple code design.
Code in the webpage to populate data onto Listview:
// Find ListView to populate
ListView lvItems = (ListView) findViewById(R.id.lvItems);
// Setup cursor adapter using cursor from last step
TodoCursorAdapter todoAdapter = new TodoCursorAdapter(this, todoCursor);
// Attach cursor adapter to the ListView
lvItems.setAdapter(todoAdapter);
You can implement an ArrayAdapter instead of CursorAdapter. This makes sense if your app is not continually interfacing with the database. The link is Using an ArrayAdapter with ListView . It is the same website.
In this case, look at getView method instead of the similar bindView.

Related

sqlite database on listview

I'm creating an android book that has 6 season whithin sqlite database but listview take several seconds to load data.
I know that I sholud implement onscroll methode to do this,but I don't know how manage it
my code is:
public class list_story extends ListActivity{
private database db;
private String[] Name;
private String sea;
Context c;
#Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.list_story);
db = new database(this);
Bundle ex = getIntent().getExtras();
sea = ex.getString("sea");
refresh();
setListAdapter(new AA());
}
class AA extends ArrayAdapter<String>{
public AA(){
super(list_story.this,R.layout.row_list,Name);
}
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
LayoutInflater in = getLayoutInflater();
View row = in.inflate(R.layout.row_list, parent,false);
txt.setText(Name[position]);
return (row);
}
}
private void refresh(){
db.open();
int s =db.Story_count("content", sea);
Name = new String[s];
Fav=new String[s];
Tasvir=new String[s];
Scientific=new String[s];
English=new String[s];
for (int i = 0; i < s; i++) {
Name[i]=db.Story_display(i, sea, "content", 1);
Fav[i]=db.Story_display(i, sea, "content", 4);
Tasvir[i]=db.Story_display(i, sea, "content", 5);
Scientific[i]=db.Story_display(i, sea, "content", 6);
English[i]=db.Story_display(i, sea, "content", 7);
}
db.close();
}
}
Basically every time you try to inflate a new view, you will literally inflate a new view. The problem with that is if you have 100 plus items to put into the list view, well that's 100+ views you've defined. That's extremely inefficient. Use the ViewHolder pattern for ListView, or use a the RecyclerView. Better yet use the RecyclerView. There's a lot of information on the web for both of those. Just look up "how to use ViewHolder in a list view"
If you are only using a single text field and a db i would recomend you to use a simpleCursorAdapter :
db = new DBAdapter(this);
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
android.R.layout.simple_list_item_1,
db.getAllTitles(),
new String[] { "title" },
new int[] { android.R.id.text1 });
ListView listView = (ListView) findViewById(R.id.list);
listView.setAdapter(adapter);
If you have further questions check this great example https://thinkandroid.wordpress.com/2010/01/09/simplecursoradapters-and-listviews/

How to intercept and change data passing from DB to ListView?

In my application I read data from database, put it into cursor and pass it to ListView using adapter. These data are numbers from 1 to 12, but I need them to be presented in the ListView as names of months. How and on which step of reading and displaying these data can I intercept them and change from numbers to text?
You can modify the data, right before it the adapter sets the text to your TextView. It'll be done in adapter's getView method.
Try this
import java.text.DateFormatSymbols;
public String getMonth(int month) {
return new DateFormatSymbols().getMonths()[month-1];
}
You may right a custom adapter for that!. If your data is cursor object you can write custom adapter class extending from "CursorAdapter". Other wise you can extend from "BaseAdapter".
call it by:
ShowListCursorAdapter adapter = new ShowListCursorAdapter(getActivity(), R.layout.fragment_list_detail, cursor,
columns, views, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
getListView().setAdapter(adapter);
In custom Adapter extends cursorAdapter:
Constructor:
public ShowListCursorAdapter(Context context, int layout, Cursor cursor, String[] columns, int[] views, int flag) {
super(context,cursor,flag);
mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mCursor = cursor;
mLayout = layout;
mTo = views;
mFrom = columns;
}
Implement 2 methods in CursorAdapter.
#Override
public View newView(Context context, Cursor cursor, ViewGroup viewGroup) {//This will be called once
mView = mInflater.inflate(mLayout, viewGroup, false);
return mView;
}
#Override
public void bindView(View view, Context context, Cursor cursor) {//This is called no.of row times
for(int i = 0; i< mTo.length;i++) {//If you have single column no need of for loop
TextView content = (TextView) view.findViewById(mTo[i]);
content.setText(mCursor.getString(mCursor.getColumnIndex(mFrom[i])));////here you can convert number to month and display
}
}

Filling list view from multiple columns in a cursor, but only one row?

Sorry if this seems stupid, but I'm sort of new to all this stuff. The situation is that i have a lot of data stored in a database that I need to present in list views. The first view pulls 15 rows and uses only two out of 14 columns in the db. I use this adapter to present this in a list view:
private class CustomListAdapter extends SimpleCursorAdapter {
private Cursor cursor;
public CustomListAdapter(Context context, int textViewResourceId, Cursor cursor, String from[], int to[]) {
super(context, textViewResourceId, cursor, from, to);
this.cursor = cursor;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v == null) {
LayoutInflater vi = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.row, null);
}
cursor.moveToPosition(position);
if (cursor != null) {
TextView lt = (TextView) v.findViewById(R.id.lefttext);
TextView rt = (TextView) v.findViewById(R.id.righttext);
if (lt != null) {
lt.setText(/*cursor.getString(cursor.getColumnIndex(EwstableContentProvider.TIMESTAMP))*/cursor.getString(cursor.getColumnIndex(EwstableContentProvider._ID))); }
if (rt != null){
rt.setText(cursor.getString(cursor.getColumnIndex(EwstableContentProvider.TOTALEWS)));
}
}
return v;
}
}
}
This may even be stupid, but at least it works.
Now, on the next activity i need to present data from all the columns, but only from the row that the user selected on the previous activity. I was looking at putting it inside a list view like the one from http://www.softwarepassion.com/android-series-custom-listview-items-and-adapters/, which is also where i modified the adapter from.
this way, i would put data from two fields in the db into each item in the list view. this is perfect, it would be one data point and a comment that goes with it.
The problem is that at this point i only have one row in the cursor, so the bit after #Override is only executed once, so instead of the 7 items in the list view, I get one.
I'd really appreciate any help, even if it is to do it in an entirely different way.
Assuming that you know the number of columns, could you just use a for loop to iterate through all the columns, storing each string into a String array.
String[] arr = new String[cursor.getColumnCount()];
for(int i=0; i < cursor.getColumnCount(); i++)
arr[i] = cursor.getString(i);
Then use the String[] with an ArrayAdapter for your listview.
UPDATE: sorry didn't read the question carefully; see other answer.
You need to use a cursor adapter. I recommend the SimpleCursorAdapter (example below).
You will also need to change the "from" parameter to the column name (key) for the text you want displayed. An example from my personal code is below. This line,
new String[] { DBAdapter.KEY_NAME },
is the important one. It is defined in DBAdapter to be:
public static final String KEY_NAME = "name";
which matches the name of the first column in my own database.
DBAdapter dba = new DBAdapter(this);
dba.open();
Cursor c = dba.list_listMode();
SimpleCursorAdapter ca = new SimpleCursorAdapter(
this,
R.layout.list_item,
c,
new String[] { DBAdapter.KEY_NAME },
new int[] { R.id.list_item_text });
lv.setTextFilterEnabled(true);
lv.setAdapter(ca);
lv.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view,
int position, long id_long) {

NullPointerException while traversing a ListView

Code:
SimpleCursorAdapter ada = new SimpleCursorAdapter(this,
R.layout.custom_layout, ssCursor, new String[] {
"String" }, new int[] {
R.id.txt2 });
lvSms.setAdapter(ada);
btnDel.setOnClickListener(new View.OnClickListener() {
private ArrayList<String> msgArr;
public void onClick(View v) {
LinearLayout ly = (LinearLayout) findViewById(R.id.lv);
ListView lvMsg = (ListView) ly.getChildAt(3);
int listCount = lvSms.getCount();
for (int a = 0 ; a < listCount ; a++)
{
LinearLayout ll = (LinearLayout) lvMsg.getChildAt(a);
LinearLayout l2 = (LinearLayout) ll.getChildAt(0);
LinearLayout l3 = (LinearLayout) l2.getChildAt(0);
CheckBox chkBox =(CheckBox) l3.getChildAt(0);
boolean valueOfChkBox = chkBox.isChecked();
if (valueOfChkBox) {
LinearLayout l4 = (LinearLayout) l2.getChildAt(1);
TextView txt1 = (TextView) l4.getChildAt(0);
msgArr = new ArrayList<String>();
msgArr.add(txt1.getText().toString());
Log.i("hello", txt1.getText().toString());
}
} });
I am getting a NullPointerException, as getChildAt returns the visible rows and I have to also check the invisible rows, either they are checked or not. So how could I check it on separate button?
I'm guessing that you want to obtain the text of the TextViews from the rows that are checked in the ListView. You can't use getChildAt() and just parse all the rows, instead you should use the data that you pass in the adapter, the cursor(and figure out what rows are checked). Because you use a custom row for the layout you'll have to somehow maintain the checked/unchecked status of your rows CheckBoxes(with a custom adapter). When is time to get the text, at a click of that Button, then simply find out what rows are currently checked and extract from the cursor directly the text you want.
A simple way to store the status of the CheckBoxes is to have an ArrayList<Boolean> that you'll modify depending on which CheckBox you act(probably not that efficient):
private class CustomAdapter extends SimpleCursorAdapter {
// this will hold the status of the CheckBoxes
private ArrayList<Boolean> checkItems = new ArrayList<Boolean>();
public CustomAdapter(Context context, int layout, Cursor c,
String[] from, int[] to) {
super(context, layout, c, from, to);
int count = c.getCount();
for (int i = 0; i < count; i++) {
checkItems.add(false);
}
}
//this method returns the current status of the CheckBoxes
//use this to see what CheckBoxes are currently checked
public ArrayList<Boolean> getItemsThatAreChecked() {
return checkItems;
}
#Override
public void bindView(View view, Context context, Cursor cursor) {
super.bindView(view, context, cursor);
int position = cursor.getPosition();
CheckBox ckb = (CheckBox) view.findViewById(R.id.checkBox);
ckb.setTag(new Integer(position));
ckb.setChecked(checkItems.get(position));
ckb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
Integer realPosition = (Integer) buttonView.getTag();
checkItems.set(realPosition, isChecked);
}
});
}
}
Then parse the ArrayList you get from getItemsThatAreChecked() and extract the data from the cursor. Here you have a small example http://pastebin.com/tsZ6mzt9 (or here git://gist.github.com/2634525.git with the layouts)
LinearLayout ly = (LinearLayout) findViewById(R.id.lv);
The above line - does it try to take the root layout? If so, you can't use findViewById. Rather use:
getLayoutInflater().inflate(R.layout.mylayout)
getLayoutInflater() is Activity's method

populating a listView with images on the device

Ok so Ive spent the last two days looking for a good simple example of how to use the images on a device to populate a list view and Ive come to the conclusion that there is no easy way to do this. I know that everytime I put together bits and pieces from some one elses examples that I usually end up with lots of extra code i dont need. Can some one please show me how to simply load images from the device to a list view. There is no book, tutorial, or post on here that just simply shows how to do this and its kind of funny and crazy at the same time.
Here's the rest of the code, using the SDImageLoader linked to above from here: http://www.samcoles.co.uk/mobile/android-asynchronously-load-image-from-sd-card
The ListAdapter class:
public class PBListAdapter extends SimpleCursorAdapter {
//MEMBERS:
private int mLayoutId;
private SpecimenHunterDatabaseAdapter mDbHelper;
private final SDImageLoader mImageLoader = new SDImageLoader();
//METHODS:
public PBListAdapter(Context context, int layout, Cursor c) {
super(context, layout, c, new String[] {}, new int[] {});
mLayoutId = layout;
mDbHelper = new SpecimenHunterDatabaseAdapter(context);
mDbHelper.open();
}
#Override
public View newView(Context context, Cursor c, ViewGroup parent) {
final LayoutInflater inflater = LayoutInflater.from(context);
View v = inflater.inflate(mLayoutId, parent, false);
return v;
}
#Override
public void bindView(View v, Context context, Cursor c) {
String title = c.getString(c.getColumnIndexOrThrow(SpecimenHunterDatabaseAdapter.KEY_CAPTURES_TITLE));
String species = mDbHelper.fetchSpeciesName(c.getInt(c.getColumnIndexOrThrow(SpecimenHunterDatabaseAdapter.KEY_CAPTURES_SPECIES)));
int pounds = c.getInt((c.getColumnIndexOrThrow(SpecimenHunterDatabaseAdapter.KEY_CAPTURES_POUNDS)));
int ounces = c.getInt((c.getColumnIndexOrThrow(SpecimenHunterDatabaseAdapter.KEY_CAPTURES_OUNCES)));
int drams = c.getInt((c.getColumnIndexOrThrow(SpecimenHunterDatabaseAdapter.KEY_CAPTURES_DRAMS)));
String weight = pounds + context.getString(R.string.addcapture_pounds) + " " +
ounces + context.getString(R.string.addcapture_ounces) + " " +
drams + context.getString(R.string.addcapture_drams);
String photoFilePath = c.getString(c.getColumnIndexOrThrow(SpecimenHunterDatabaseAdapter.KEY_CAPTURES_PHOTO));
String comment = c.getString(c.getColumnIndexOrThrow(SpecimenHunterDatabaseAdapter.KEY_CAPTURES_COMMENT));
TextView nameView = (TextView)v.findViewById(R.id.pb_row_species_name);
TextView weightView = (TextView)v.findViewById(R.id.pb_row_capture_weight);
TextView titleView = (TextView)v.findViewById(R.id.pb_row_capture_title);
TextView commentView = (TextView)v.findViewById(R.id.pb_row_capture_comment);
ImageView photoView = (ImageView)v.findViewById(R.id.pb_row_capture_photo);
mImageLoader.load(context, photoFilePath, photoView);
nameView.setText(species);
titleView.setText(title);
weightView.setText(weight);
commentView.setText(comment);
}
}
In the actual ListActivity, in your onCreate() set the list adapter:
private void bindData() {
Cursor c = mDbHelper.fetchAllPBs();
startManagingCursor(c);
setListAdapter(new PBListAdapter(this, R.layout.pb_list_item, c));
}
In my example the absolute filepaths for the images are stored in the database when added via the app.

Categories

Resources