I'm creating android app with lots of data user enters.
Now I need to display some data just like on the image below
I made first part of image, where all data is listed. Now I need to made some recapitulation where data is grouped by sort. If there are items with the same grade it needs to place them under the same row and count how many pieces of them are and also sum their masses and prices.
This is part of code where I'm displaying itemsList:
public class LogsRecapitulation extends AppCompatActivity {
private ListView mainListView;
private BaseAdapter listAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_listview);
mainListView = (ListView) findViewById(R.id.ListViewItem);
//recieve RecepitID in displayLogs.activity
final long forwardedId = (long) getIntent().getExtras().get(String.valueOf("recepitID"));
List<Logs> logsList = new Select().from(Logs.class).where("Receipt = " + forwardedId).execute();
listAdapter = new RecapitulationArrayAdapter(logsList);
mainListView.setAdapter(listAdapter);
}
private class RecapitulationArrayAdapter extends BaseAdapter {
private LayoutInflater inflater;
private List<Logs> logsList;
public RecapitulationArrayAdapter(List<Logs> logsList) {
inflater = LayoutInflater.from(LogsRecapitulation.this);
this.logsList = logsList;
}
#Override
public int getCount() {
return logsList.size();
}
#Override
public Object getItem(int position) {
return logsList.get(position);
}
#Override
public long getItemId(int position) {
return logsList.get(position).getId();
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = inflater.inflate(R.layout.logs_recapitulation, parent, false);
}
Logs log = logsList.get(position);
((TextView) convertView.findViewById(R.id.rec_log_sort)).setText(log.sort_id);
((TextView) convertView.findViewById(R.id.rec_log_class)).setText(log.grade);
((TextView) convertView.findViewById(R.id.rec_log_count)).setText(String.valueOf(logsList.size()));
((TextView) convertView.findViewById(R.id.rec_logs_mass)).setText(String.format("%.2f m3", log.getM3()));
if (log.receipt.priceType.equals("Na panju")) {
((TextView) convertView.findViewById(R.id.rec_log_price_default)).setText(String.valueOf(log.price.stumpPrice_kn));
} else {
((TextView) convertView.findViewById(R.id.rec_log_price_default)).setText(String.valueOf(log.price.roadPrice_kn));
}
if (log.receipt.priceType.equals("Na panju")) {
((TextView) convertView.findViewById(R.id.rec_calculated_price)).setText(String.format("%.2f KN", log.price.stumpPrice_kn * log.getM3()));
} else {
((TextView) convertView.findViewById(R.id.rec_calculated_price)).setText(String.format("%.2f KN", log.price.roadPrice_kn * log.getM3()));
}
return convertView;
}
}
This upper code is not so important, but more important to me is to made this recapitulation and group items by sort and in each sort by grade.
Question: How should I group items by sort and in each sort group them by grade (just like on image up)?
Any help is welcome!
I don't have access to your database schema, but the SQL query you might use to produce such a result where you group by "Sorts" is as follows:
SELECT Sort, Grade, COUNT(DISTINCT ID) AS Pieces, ROUND(SUM(Mass), 2) AS
TotalMass, Price, SUM(MassPrice) AS TotalMassPrice
FROM LogsTable
GROUP BY Sort, Grade
ORDER BY Sort DESC, Grade DESC;
producing a table like the following:
There's a problem with this query if the price for a given combination of Sort-Grade is not constant. In that case you might use AVG(Price) AS AveragePrice instead of using Price, as in the example.
The "grand total" row can be produced with a SQL query as such:
SELECT COUNT(DISTINCT ID) AS TotalPieces, ROUND(SUM(Mass), 2) AS GrandTotalMass, AVG(Price) AS AveragePrice, SUM(MassPrice) AS GrandTotalMassPrice
FROM LogsTable;
producing a table like the following:
It's clear to me that the number actually being reported in the grand total row in your screenshot is not the average price, but some other metric.
You can easily convert these SQL queries to your query framework of choice. I haven't done so as you seem to demonstrate knowledge of how to do this already.
Related
How can I make the phone number 3456781276 which is in my phone contacts appear at the very top of my listview, and then all other contacts below that as normal? I believe I pass that value into my custom adapter and into my getView() but not at all sure how to proceed. Can you help?
In my ListView I show all my phone contacts with the following code:
class LoadContact extends AsyncTask<Void, Void, Void> {
#Override
protected void onPreExecute() {
super.onPreExecute();
}
#Override
protected Void doInBackground(Void... voids) {
// we want to delete the old selectContacts from the listview when the Activity loads
// because it may need to be updated and we want the user to see the updated listview,
// like if the user adds new names and numbers to their phone contacts.
selectPhoneContacts.clear();
// we have this here to avoid cursor errors
if (cursor != null) {
cursor.moveToFirst();
}
try {
// get a handle on the Content Resolver, so we can query the provider,
cursor = getApplicationContext().getContentResolver()
// the table to query
.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
null,
null,
null,
// display in ascending order
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
// get the column number of the Contact_ID column, make it an integer.
// I think having it stored as a number makes for faster operations later on.
// get the column number of the DISPLAY_NAME column
int nameIdx = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME);
// get the column number of the NUMBER column
int phoneNumberofContactIdx = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);
cursor.moveToFirst();
// We make a new Hashset to hold all our contact_ids, including duplicates, if they come up
Set<String> ids = new HashSet<>();
do {
System.out.println("=====>in while");
// get a handle on the display name, which is a string
name = cursor.getString(nameIdx);
// get a handle on the phone number, which is a string
phoneNumberofContact = cursor.getString(phoneNumberofContactIdx);
//----------------------------------------------------------
// get a handle on the phone number of contact, which is a string. Loop through all the phone numbers
// if our Hashset doesn't already contain the phone number string,
// then add it to the hashset
if (!ids.contains(phoneNumberofContact)) {
ids.add(phoneNumberofContact);
System.out.println(" Name--->" + name);
System.out.println(" Phone number of contact--->" + phoneNumberofContact);
SelectPhoneContact selectContact = new SelectPhoneContact();
selectContact.setName(name);
selectContact.setPhone(phoneNumberofContact);
selectPhoneContacts.add(selectContact);
}
} while (cursor.moveToNext());
} catch (Exception e) {
Toast.makeText(NewContact.this, "what the...", Toast.LENGTH_LONG).show();
e.printStackTrace();
// cursor.close();
} finally {
}
if (cursor != null) {
cursor.close();
}
return null;
}
#Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
adapter = new SelectPhoneContactAdapter(selectPhoneContacts, NewContact.this);
// we need to notify the listview that changes may have been made on
// the background thread, doInBackground, like adding or deleting contacts,
// and these changes need to be reflected visibly in the listview. It works
// in conjunction with selectContacts.clear()
adapter.notifyDataSetChanged();
listView.setAdapter(adapter);
//this function measures the height of the listview, with all the contacts, and loads it to be that
//size. We need to do this because there's a problem with a listview in a scrollview.
justifyListViewHeightBasedOnChildren(listView);
}
}
My model, getters and setters, is like:
public class SelectPhoneContact {
String phone;
public String getPhone() {return phone;}
public void setPhone(String phone) {
this.phone = phone;
}
String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
And my custom adapter:
public class SelectPhoneContactAdapter extends BaseAdapter {
//define a list made out of SelectContacts and call it theContactsList
public List<SelectPhoneContact> theContactsList;
//define an array list made out of SelectContacts and call it arraylist
private ArrayList<SelectPhoneContact> arraylist;
Context _c;
//define a ViewHolder to hold our name and number info, instead of constantly querying
// findviewbyid. Makes the ListView run smoother
ViewHolder v;
public SelectPhoneContactAdapter(List<SelectPhoneContact> selectPhoneContacts, Context context) {
theContactsList = selectPhoneContacts;
_c = context;
this.arraylist = new ArrayList<SelectPhoneContact>();
this.arraylist.addAll(theContactsList);
Collections.sort(this.arraylist, new Comparator<SelectPhoneContact>() {
#Override
public int compare(SelectPhoneContact t1, SelectPhoneContact t2) {
if(t2.getPhone().equals ("3456781276")) { // put the phone number you want on top here
return 1;
} else {
return t1.getName().compareTo(t2.getName());
}
}
});
}
#Override
public int getCount() {
return arraylist.size();
}
#Override
public Object getItem(int i) {
return arraylist.get(i);
}
#Override
public long getItemId(int i) {
return i;
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
static class ViewHolder {
// In each cell in the listview show the items you want to have
// Having a ViewHolder caches our ids, instead of having to call and load each one again and again
CheckBox checkbox;
TextView title, phone, lookup;
// CheckBox check;
}
#Override
public View getView(int i, View convertView, ViewGroup viewGroup) {
//we're naming our convertView as view
View view = convertView;
if (view == null) {
//if there is nothing there (if it's null) inflate the layout for each row
LayoutInflater li = (LayoutInflater) _c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = li.inflate(R.layout.phone_inflate_listview, null);
//or else use the view (what we can see in each row) that is already there
} else {
view = convertView;
}
v = new ViewHolder();
// So, for example, title is cast to the name id, in phone_inflate_listview,
// phone is cast to the id called no etc
v.title = (TextView) view.findViewById(R.id.name);
// v.check = (CheckBox) view.findViewById(R.id.check);
v.phone = (TextView) view.findViewById(R.id.no);
// store the holder with the view
final SelectPhoneContact data = (SelectPhoneContact) arraylist.get(i);
v.title.setText(data.getName());
v.phone.setText(data.getPhone());
view.setTag(data);
return view;
}
}
What about using add(int index, E element)?
if (/* check your condition here: is it the number you are looking for? */) {
// insert the contact at the beginning
selectPhoneContacts.add(0, selectContact);
} else {
// insert it at the end (default)
selectPhoneContacts.add(selectContact);
}
Try to modify your adapter's constructor like this:
public SelectPhoneContactAdapter(List<SelectPhoneContact> selectPhoneContacts, Context context) {
theContactsList = selectPhoneContacts;
_c = context;
this.arraylist = new ArrayList<SelectPhoneContact>();
this.arraylist.addAll(theContactsList);
Collections.sort(this.arraylist, new Comparator<SelectPhoneContact>() {
#Override
public int compare(SelectPhoneContact t1, SelectPhoneContact t2) {
if(t2.getPhone().equals("3456781276")) { // put the phone number you want on top here
return 1;
} else {
return t1.getName().compareTo(t2.getName());
}
}
});
}
So we are basically sorting the ArrayList before the adapter starts using it.
So in this example, I am putting the phone number "3456781276" on top of everything else. If the phone number is NOT "3456781276", it will sort all the items by the name. (If you don't want to sort it by name, just remove the else statement.
Hope this helps.
EDIT:
in getView(), change:
final SelectPhoneContact data = (SelectPhoneContact) theContactsList.get(i);
to:
final SelectPhoneContact data = (SelectPhoneContact) arraylist.get(i);
Change getCount() method like this:
#Override
public int getCount() {
return arraylist.size();
}
Change getItem() method like this:
#Override
public Object getItem(int i) {
return arraylist.get(i);
}
You must use arraylist everywhere since that is the list we are sorting.
An easy way to achieve this, just use a view in XML which contains your phone number, and set it to invisible in default, if the list shows, set the view to be visible. I hope this post help you!!!
You can easily manipulate with items positions in ArrayList with Collections.swap(); by looping through your contacts and by simply checking is number matching your number if does put it on the top for example:
Collections.swap(myArrayList, i, 0);
Refering to: http://www.java2s.com/Code/Java/Collections-Data-Structure/SwapelementsofJavaArrayList.htm
I have 2 tables, Logs and Price. Content from table logs is displayed into textviews for each item. Now I would like to display some content form table Price into the same base adapter.
Is it possible and how should I done that?
This is my activity with base adapter in which i displayed content form table logs. How should I display here content form table Price?
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.display_logs_listview);
boolean sort = getIntent().getBooleanExtra("sort", false);
mainListView = (ListView) findViewById(R.id.ListViewItem);
final String place = (String) getIntent().getExtras().get("keyPlace");
dbHandler = new LogsDBHandler(this);
ArrayList<Logs> logsList = sort ? dbHandler.getAllLogsByPlace() : dbHandler.getAllLogs(place);
TextView result = (TextView) findViewById(R.id.LogMassResult);
double sum = 0.0;
for( int i=0; i<logsList.size(); i++) {
sum += logsList.get(i).getResult();
}
result.setText(String.format("%.2f", sum));
listAdapter = new LogsArrayAdapter(logsList);
mainListView.setAdapter(listAdapter);
}
private class LogsArrayAdapter extends BaseAdapter {
private LayoutInflater inflater;
private List<Logs> logsList;
private List<Price> priceList;
public LogsArrayAdapter(List<Logs> logsList) {
inflater = LayoutInflater.from(DisplayLogs.this);
this.logsList = logsList;
}
#Override
public int getCount() {
return logsList.size();
}
#Override
public Object getItem(int position) {
return logsList.get(position);
}
#Override
public long getItemId(int position) {
return logsList.get(position).getId();
}
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = inflater.inflate(R.layout.activity_display_logs, parent, false);
}
Logs log = logsList.get(position);
((TextView) convertView.findViewById(R.id.textPlace)).setText(log.getPlace());
((TextView) convertView.findViewById(R.id.textNumber)).setText(log.getPlate_number());
((TextView) convertView.findViewById(R.id.textSort)).setText(log.getSort_id());
((TextView) convertView.findViewById(R.id.textGrade)).setText(log.getGrade());
((TextView) convertView.findViewById(R.id.textDiameter)).setText(log.getDiameter());
((TextView) convertView.findViewById(R.id.textLength)).setText(log.getLength());
Log.d("Value", log.getCreatedAt());
try {
Date dt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(log.getCreatedAt());
((TextView) convertView.findViewById(R.id.textDate)).setText(new SimpleDateFormat("dd.MM.yyyy HH:mm").format(dt));
} catch (ParseException e) {
e.printStackTrace();
}
Log.d("Masa Trupca", String.format("%.2f", log.getResult()));
String final_result = String.format("%.2f", log.getResult());
((TextView) convertView.findViewById(R.id.textAmount)).setText(final_result);
return convertView;
}
}
and this is my dbQuery for getting price. I created this in my Logs class. Here I'm displaying price based on parameters in string.
public Cursor getPrice() {
Cursor cursor = db.query("Price", new String[]{"price_stump_kn", "price_stump_eur", "road_price_kn", "road_price_eur"}, "sort = ? AND grade = ? AND length = ? BETWEEN diameter_dg = ? AND diameter_gg = ?",
new String[]{getSort_id(), getGrade(), getLength(), getDiameter(), getDiameter()}, null, null, null);
if (cursor.moveToFirst()) {
do {
Price price = new Price();
price.setStumpPrice_kn(cursor.getString(0));
price.setStumpPrice_eur(cursor.getString(1));
price.setRoadPrice_kn(cursor.getString(2));
price.setRoadPrice_eur(cursor.getString(3));
} while (cursor.moveToNext());
}
return cursor;
}
So how should I display content from two tables inside one base adapter (listview)?
It depends on how the records in those two tables are related. Your remark that you included a DB query in the Logs class (which I suppose is a domain class, not a DAO), I suspect that your class structure is somewhat confusing. Therefore, I try to sketch a class structure for each of the two ways of mixing your logs and prices.
Solution A: Each log is connected to a price, and data of both are to be displayed in one item.
class LogDAO extends SQLiteOpenHelper {
...
public Log getLogs(some selection parameters) {
Get logs according to selection parameters, and for each log
call getPrice(selection parameter according to log just found)
log.setPrice(price just found)
...
Now, in your adapter, the items are Logs, and with log.getPrice() you can get the price attributes and are free to mix log and price attributes in your adapter to display them in your view item.
Solution B: There is a mixed list -- some items are logs, others are prices
The key to this is that you can dynamically decide, for each item, which layout to use in your adapter. So the structure will be:
class LogPriceDAO extends SQLiteOpenHelper {
...
public Object getLogsAndPrices(some selection parameters)
Get logs and prices in some sequence, according to your business
logic and the selection parameters (If logs and prices have some
common superclass, use that instead of Object)
...
class LogsAndPricesAdapter extends BaseAdapter {
...
#Override
public View getView (int i, View view, ViewGroup viewGroup) {
...
Object currObject = this.getItem(i); // or common superclass
...
View v;
if (currObject instanceOf Log) {
v = layoutInflater.inflate(R.layout.log_layout, null)
now fill fields of your log layout
...
} else {
if (currObject instanceOf Price) {
v = layoutInflater.inflate(R.layout.price_layout, null)
now fill fields of your price layout
...
return v
The instanceOf operator is considered bad style by some people. So the immaculate way is to define a common superclass for Log and Price that offers an operation public Boolean isLog() from which the caller can decide which type of object it got.
I'm trying to make a simple to-do list where you would long-press an item to mark it as 'done', in which case it will be greyed out and strikethrough.
I'm working on the strikethrough first and found some sample code here creating a strikethrough text in Android? . However the problem is that the setPaintFlags() method only seems to work on TextView whereas the items on my list are String. I can't cast a String to a TextView, and I found a workaround here but apparently it's highly discouraged to do it: Cast String to TextView . Also I looked up SpannableString but it doesn't seem to work for strings of varying length.
So I'm back at square one - is it at all possible to implement what I'm trying to do? Or will I have to store my list items differently instead?
Relevant code:
public class MainActivity extends ActionBarActivity {
private ArrayList<String> items;
private ArrayAdapter<String> itemsAdapter;
private ListView lvItems;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Setting what the ListView will consist of
lvItems = (ListView) findViewById(R.id.lvItems);
readItems();
itemsAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, items);
lvItems.setAdapter(itemsAdapter);
// Set up remove listener method call
setupListViewListener();
}
//Attaches a long click listener to the listview
private void setupListViewListener() {
lvItems.setOnItemLongClickListener(
new AdapterView.OnItemLongClickListener() {
#Override
public boolean onItemLongClick(AdapterView<?> adapter,
View item, int pos, long id) {
// Trying to make the onLongClick strikethrough the text
String clickedItem = items.get(pos);
//What do I do here??
// Refresh the adapter
itemsAdapter.notifyDataSetChanged();
writeItems();
// Return true consumes the long click event (marks it handled)
return true;
}
});
}
Let's take a step back and consider your app. You want to show a list of jobs to the user. Each job has a description. And each job has two possible states: 'done' or 'not done'.
So I would like to introduce a class 'Job'
class Job
{
private String mDescription;
private boolean mDone;
public Job(String description)
{
this.mDescription = description;
this.mDone = false;
}
// ... generate the usual getters and setters here ;-)
// especially:
public boolean isDone()
{
return mIsDone;
}
}
This way your ArrayList 'items' becomes be a ArrayList< Job >. Wether a job is done or not will be stored together with its description. This is important because you want to show the current state of the job to the user by changing the look of the UI element, but you need to keep track of the job's state on the data level as well.
The UI element - the TextView - will be configured to present information about the job to the user. One piece of information is the description. The TextView will store this as a String. The other piece of information is the state (done/ not done). The TextView will (in your app) store this by setting the strike-through flag and changing its color.
Because for performance reasons a ListView uses less elements than the data list ('items') contains, you have to write a custom adapter. For brevity's sake, I'm keeping the code very simple, but it's worth the time to read up on the View Holder pattern:
Let's use a layout file 'mytextviewlayout.xml' for the list rows:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="Medium Text"
android:id="#+id/textView"/>
</LinearLayout>
Now the code for the adapter looks like this:
EDIT changed from ArrayAdapter to BaseAdapter and added a view holder (see comments):
public class MyAdapter extends BaseAdapter
{
private ArrayList<Job> mDatalist;
private int mLayoutID;
private Activity mCtx;
private MyAdapter(){} // the adapter won't work with the standard constructor
public MyAdapter(Activity context, int resource, ArrayList<Job> objects)
{
super();
mLayoutID = resource;
mDatalist = objects;
mCtx = context;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View rowView = convertView;
if (rowView == null) {
LayoutInflater inflater = mCtx.getLayoutInflater();
rowView = inflater.inflate(mLayoutID, null);
ViewHolder viewHolder = new ViewHolder();
viewHolder.tvDescription = (TextView) rowView.findViewById(R.id.textView);
rowView.setTag(viewHolder);
}
ViewHolder vholder = (ViewHolder) rowView.getTag();
TextView tvJob = vholder.tvDescription;
Job myJob = mDatalist.get(position);
tvJob.setText(myJob.getJobDescription());
if (myJob.isDone())
{
// apply changes to TextView
tvJob.setPaintFlags(tvJob.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
tvJob.setTextColor(Color.GRAY);
}
else
{
// show TextView as usual
tvJob.setPaintFlags(tvJob.getPaintFlags() & (~Paint.STRIKE_THRU_TEXT_FLAG));
tvJob.setTextColor(Color.BLACK); // or whatever is needed...
}
return rowView;
}
#Override
public int getCount()
{
return mDatalist.size();
}
#Override
public Object getItem(int position)
{
return mDatalist.get(position);
}
#Override
public long getItemId(int position)
{
return position;
}
static class ViewHolder
{
public TextView tvDescription;
}
}
Due to the changed adapter,
in the MainActivity, you have to declare 'items' and 'itemsAdapter' as follows:
private ArrayList<Job> items;
private MyAdapter itemsAdapter;
...and in your 'onCreate()' method, you write:
itemsAdapter = new MyAdapter<String>(this, R.layout.mytextviewlayout, items);
Don't forget to change the 'readItems()' and 'writeItems()' methods because 'items' now is a ArrayList< Job >.
Then, finally, the 'onItemLongClick()' method:
EDIT use 'parent.getItemAtPosition()' instead of 'items.get()', see comments
#Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id)
{
// items.get(position).setDone(true);
Object o = parent.getItemAtPosition(position);
if (o instanceof Job)
{
((Job) o).setDone(true);
}
// and now indeed the data set has changed :)
itemsAdapter.notifyDataSetChanged();
writeItems();
return true;
}
I want to make a dynamic ListView with numbered items. Here I have retrieved values from SQLite database into the ListView and actually it is numbered according to their id's which are stored in database. But I'm using a dynamic list view, so I want to show numbered items starting from 1 in each time I load the ListView. For example people booking flight tickets for different dates and flight authorities will display a final ListView for the current date including persons who booked for that date.
consider today is 10-12-2014
person A booked ticket for the date 12-12-2014, so his "id" might be "1" in database.
person B booked ticket for the date 13-12-2014,so his "id" might be "2" in database.
person c booked ticket for the date 13-12-2014,so his "id" might be "3" in database.
but when the day "13-12-2014" comes person B's id should be "1"(no need to have any connection with database, just enough a numberd representation to show today's list is this.
like
1.person B
2.person C
thats all.
This is my displayadapter class:
public class DisplayAdapter extends BaseAdapter {
private Context mContext;
private ArrayList<String> id;
private ArrayList<String>name;
private ArrayList<String>phone;
public DisplayAdapter(Context c, ArrayList<String> id,ArrayList<String> name, ArrayList<String> phone) {
this.mContext = c;
this.id = id;
this.name = name;
this.phone = phone;
}
public int getCount() {
// TODO Auto-generated method stub
return id.size();
}
public Object getItem(int position) {
// TODO Auto-generated method stub
return null;
}
public long getItemId(int position) {
// TODO Auto-generated method stub
return 0;
}
public View getView(int pos, View child, ViewGroup parent) {
Holder mHolder;
LayoutInflater layoutInflater;
if (child == null) {
layoutInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
child = layoutInflater.inflate(R.layout.viewthem, null);
mHolder = new Holder();
mHolder.txt_id = (TextView) child.findViewById(R.id.d);
mHolder.txt_name = (TextView) child.findViewById(R.id.nm);
mHolder.txt_phone = (TextView) child.findViewById(R.id.ph);
child.setTag(mHolder);
} else {
mHolder = (Holder) child.getTag();
}
mHolder.txt_id.setText(id.get(pos));
mHolder.txt_name.setText(name.get(pos));
mHolder.txt_phone.setText(phone.get(pos));
return child;
}
public class Holder {
TextView txt_id;
TextView txt_name;
TextView txt_phone;
}
}
This is my dbhelper class:
mydb = new DBhelper(this);
SQLiteDatabase database = mydb.getWritableDatabase();
Cursor mCursor=database.rawQuery("SELECT * FROM contacts WHERE dt='"+d+"'", null);
userId.clear();
user_name.clear();
user_phone.clear();
if (mCursor.moveToFirst()) {
do {
userId.add(mCursor.getString(mCursor.getColumnIndex(DBhelper.CONTACTS_COLUMN_ID)));
user_name.add(mCursor.getString(mCursor.getColumnIndex(DBhelper.CONTACTS_COLUMN_NAME)));
user_phone.add(mCursor.getString(mCursor.getColumnIndex(DBhelper.CONTACTS_COLUMN_PHONE)));
} while (mCursor.moveToNext());
}
DisplayAdapter disadpt = new DisplayAdapter(token.this,userId, user_name, user_phone);
obj.setAdapter(disadpt);
disadpt.notifyDataSetChanged();
mCursor.close();
If you want the list to simply be numbered (as you said, without regard to the actual IDs in the database) you simply need to use the pos parameter you receive in getView(). And you'd probably want to use pos + 1 so the list will be 1-based (more user friendly).
Assuming mHolder.txtId is the TextView you want to use to display the numbers, as I said in my comment, this should work:
mHolder.txt_id.setText(String.valueOf(pos + 1));
If you get an error when trying this please explain exactly what error.
I have data in an SQLite table in the following format:
id|datetime|col1|col2
1|2013-10-30 23:59:59|aaa|aab
2|2013-10-30 23:59:59|abb|aba
3|2013-10-30 23:59:59|abb|aba
4|2013-10-31 23:59:59|abb|aba
5|2013-10-31 23:59:59|abb|aba
I would like to implement an ExpandableListView so that the data would grouped by datetime and shown like that:
> 2013-10-30 23:59:59 // Group 1
1|aaa|aab
2|abb|aba
3|abb|aba
> 2013-10-31 23:59:59 // Group 2
4|abb|aba
5|abb|aba
I have a custom CursorAdapter that I can easily use to populate ListView, showing date for every single item but I don't know how to "group" the data and populate it on an ExpandableListView - could you please give me any hints?
I have a custom CursorAdapter that I can easily use to populate
ListView, showing date for every single item but I don't know how to
"group" the data and populate it on an ExpandableListView - could you
please give me any hints?
Here is solution that currently i'm using in my projects:
You cannot (shouldn't) use CursorAdapter because it's not suitable for your solution. You need to create and implement own Adapter by extending from BaseExpandableListAdapter
Then since you want to create "your own grouping" you need to change your current application logic:
You need to create collection of objects returned from database (for
demonstrating i will use name Foo)
Solution:
So your Foo object should looks like (due to your requirements, name of variables is only created to explain idea of solution) this:
public class Foo {
private String title;
private List<Data> children;
public void setChildren(List<Data> children) {
this.children = children;
}
}
Where title will be date column from your database and children will be columns for specific (unique) date.
Due to your example:
id|datetime|col1|col2
1|2013-10-30 23:59:59|aaa|aab
2|2013-10-30 23:59:59|abb|aba
3|2013-10-30 23:59:59|abb|aba
4|2013-10-31 23:59:59|abb|aba
5|2013-10-31 23:59:59|abb|aba
One specific date (title property of Object Foo) have more associated rows so this will be simulated with defined collection of children in Foo object.
So now you need in your getAll() method (method that returns data from database usually called similarly like this) of your DAO object (object that comunicates with database, it's only terminology) create Foo objects in this logic.
Since you need to properly initialise collection of children for each unique date, you need to use two select queries. Your first select will return distinct dates - so if you have in database 40 rows with 10 different (unique) dates so your select will contain 10 rows with these unique dates.
OK. Now you have "groups" for your ListView.
Now you need to create for each created "group" its children. So here is comming second select that will select all rows and with correct condition you'll assign for each "group" Foo object own collection of children.
Here is pseudo-code #1:
String query = "select * from YourTable";
Cursor c = db.rawQuery(query, null);
List<Data> childen = new ArrayList<Data>();
if (c != null && c.moveToFirst()) {
for (Foo item: collection) {
// search proper child for current item
do {
// if current row date value equals with current item datetime
if (item.getTitle().equals(c.getString(2))) {
children.add(new Data(column3, column4)); // fetch data from columns
}
} while (c.moveToNext());
// assign created children into current item
item.setChildren(children);
// reset List that will be used for next item
children = null;
children = new ArrayList<Data>();
// reset Cursor and move it to first row again
c.moveToFirst();
}
}
// finally close Cursor and database
So now your collection is "grouped" and now the remaining work is on your implemented ListAdapter - it's not tricky.
All what you need is to properly implement getGroupView() and getChildView() methods.
In "group method" you will inflate and initialise rows with titles from collection of Foo objects. These rows will become groups in ListView.
In "child method" you'll do same things but you won't inflate titles from collection but children of current Foo object from collection. These rows will become childs of one specific group.
Notes:
Due to #1. I simplified source code for demostrating purposes. But "in action" you can change a few things:
Instead of c.getString(2) for getting second column is generally
recommended to use column name so you should use
c.getColumnIndex("columnName") instead.
Is good practise to wrap source-code to try-finally block and in
finnaly block close and release used sources like cursors and
databases.
Instead of "reusing" same collection of children how in example,
you can create public method in Foo class that will add item into
collection directly (snippet of code #2).
Snippet of code #2:
public class Foo {
private String title;
private List<Data> children = new ArrayList<Data>();
public void addChild(Data child) {
this.children.add(child);
}
...
}
Summary:
Hope you understood me (i tried to to explain things in as simple way as possible, unfortunetly personal communication face to face is the best but this solution is not available for us) and also i hope that i helped you to solve your problem.
Your kind of data modeling and representation can be modeled in Java code as a LinkedHashMap<String, List<Data>>, where Data could look like:
public class Data {
private int id;
private String col1;
private String col2;
public Data(int id, String col1, String col2) {
this.id = id;
this.col1 = col1;
this.col2 = col2;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getCol1() {
return col1;
}
public void setCol1(String col1) {
this.col1 = col1;
}
public String getCol2() {
return col2;
}
public void setCol2(String col2) {
this.col2 = col2;
}
#Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(id).append(", ").append(col1).append(", ").append(col2);
return sb.toString();
}
}
Why LinkedHasMap? Because you need to preserve the order in which you insert the data. So your SQLite reading method could look like this:
public LinkedHashMap<String, List<Data>> readData(SQLiteDatabase db) {
LinkedHashMap<String, List<Data>> result = new LinkedHashMap<String, List<Data>>();
Cursor cursor = null;
try {
cursor = db.query("MY_TABLE", new String[] {
"datetime", "id", "col1", "col2"
}, null, null, null, null, "datetime, id ASC");
while (cursor.moveToNext()) {
String dateTime = cursor.getString(cursor.getColumnIndex("datetime"));
int id = cursor.getInt(cursor.getColumnIndex("id"));
String col1 = cursor.getString(cursor.getColumnIndex("col1"));
String col2 = cursor.getString(cursor.getColumnIndex("col2"));
List<Data> list = null;
if (result.containsKey(dateTime)) {
list = result.get(dateTime);
} else {
list = new ArrayList<Data>();
result.put(dateTime, list);
}
list.add(new Data(id, col1, col2));
}
} catch (Exception ex) {
Log.e("TAG", null, ex);
} finally {
if (cursor != null) {
cursor.close();
}
}
return result;
}
A basic adapter would look like this:
public class ExpAdapter extends BaseExpandableListAdapter {
private LinkedHashMap<String, List<Data>> input;
private LayoutInflater inflater;
public ExpAdapter(LayoutInflater inflater, LinkedHashMap<String, List<Data>> input) {
super();
this.input = input;
this.inflater = inflater;
}
#Override
public Object getChild(int groupPosition, int childPosition) {
return getChildData(groupPosition, childPosition);
}
private Data getChildData(int groupPosition, int childPosition) {
String key = getKey(groupPosition);
List<Data> list = input.get(key);
return list.get(childPosition);
}
private String getKey(int keyPosition) {
int counter = 0;
Iterator<String> keyIterator = input.keySet().iterator();
while (keyIterator.hasNext()) {
String key = keyIterator.next();
if (counter++ == keyPosition) {
return key;
}
}
// will not be the case ...
return null;
}
#Override
public long getChildId(int groupPosition, int childPosition) {
return getChildData(groupPosition, childPosition).getId();
}
#Override
public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
View convertView, ViewGroup parent) {
TextView simpleTextView = null;
if (convertView == null) {
// inflate what you need, for testing purposes I am using android
// built-in layout
simpleTextView = (TextView) inflater.inflate(android.R.layout.simple_list_item_1,
parent, false);
} else {
simpleTextView = (TextView) convertView;
}
Data data = getChildData(groupPosition, childPosition);
simpleTextView.setText(data.toString());
return simpleTextView;
}
#Override
public int getChildrenCount(int groupPosition) {
String key = getKey(groupPosition);
return input.get(key).size();
}
#Override
public Object getGroup(int groupPosition) {
return getKey(groupPosition);
}
#Override
public int getGroupCount() {
return input.size();
}
#Override
public long getGroupId(int groupPosition) {
return 0;
}
#Override
public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
ViewGroup parent) {
TextView simpleTextView = null;
if (convertView == null) {
// inflate what you need, for testing purposes I am using android
// built-in layout
simpleTextView = (TextView) inflater.inflate(android.R.layout.simple_list_item_1,
parent, false);
} else {
simpleTextView = (TextView) convertView;
}
simpleTextView.setText(getKey(groupPosition));
return simpleTextView;
}
#Override
public boolean hasStableIds() {
// TODO Auto-generated method stub
return false;
}
#Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;
}
}
While its simple use in a basic activity would be something like this:
public class MyExpandableActivity extends FragmentActivity {
private ExpandableListView expListView;
#Override
protected void onCreate(Bundle savedInstance) {
super.onCreate(savedInstance);
setContentView(R.layout.expandable_layout);
expListView = (ExpandableListView) findViewById(R.id.exp_listview);
fillList();
}
private void fillList() {
LinkedHashMap<String, List<Data>> input = getMockList(); // get the collection here
ExpAdapter adapter = new ExpAdapter(LayoutInflater.from(this), input);
expListView.setAdapter(adapter);
}
}
Activity's simple layout:
<ExpandableListView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/exp_listview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
Makes sense?