I use the following SimpleCursorAdapter:
String campos[] = { "nome_prod", "codbar_prod",
"marca_prod", "formato_prod", "preco"};
int textviews[] = { R.id.textProdName, R.id.textProdCodBar, R.id.textProdMarca,
R.id.textProdFormato, R.id.textProdPreco };
CursorAdapter dataSource = new SimpleCursorAdapter(this, R.layout.listview,
c_list, campos, textviews, 0);
And this works fine. But "preco" from "campos[]" comes from a double value. Can I format this somehow so my cursor (which feeds a listview) will display this double with two digits after the point (like a money value)?
Can I do it in some simple way, like using "%.2f" somewhere, or will I have to subclass CursorAdapter?
Thanks in advance.
You don't need to subclass CursorAdapter. Simply create a ViewBinder and attach it to the adapter, it will transform the value of a specific column of the cursor. Like so:
dataSource.setViewBinder(new ViewBinder() {
public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
if (columnIndex == 5) {
Double preco = cursor.getDouble(columnIndex);
TextView textView = (TextView) view;
textView.setText(String.format("%.2f", preco));
return true;
}
return false;
}
});
Related
I've got an SQLite database with a units table. The units table is set up with only two columns:
create table units (_id INTEGER PRIMARY KEY, desc TEXT)
Example data for a row in this table is:
_id: 4
desc: "Helix #5 [2231]"
The "[2231]" substring is important, and I'd like to change its color to a medium gray color. Id also prefer to do this to the data in the desc column, as opposed to manipulating it with java.
So, I query for the data:
/**
* Get all unit records for display in spinner
*/
public Cursor getAllUnitRecords(){
String sql = "select * from units order by `desc`";
return db.rawQuery(sql, null);
}
My spinner looks like this:
<Spinner
android:id="#+id/UnitSpinner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:spinnerMode="dropdown" />
And I get the data to the spinner like this:
// Prepare unit dropdown
Cursor units = db.getAllUnitRecords();
MatrixCursor unitsMatrixCursor = new MatrixCursor(new String[] { "_id", "desc" });
unitsMatrixCursor.addRow(new Object[] { 0, "" });
MergeCursor unitsMergeCursor = new MergeCursor(new Cursor[] { unitsMatrixCursor, units });
String[] unitsFrom = new String[]{"desc"};
int[] unitsTo = new int[]{android.R.id.text1};
Spinner unitSpinner = (Spinner) findViewById(R.id.UnitSpinner);
SimpleCursorAdapter unitAdapter = new SimpleCursorAdapter(this, android.R.layout.simple_spinner_dropdown_item, unitsMergeCursor, unitsFrom, unitsTo, 0);
unitAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
unitSpinner.setAdapter(unitAdapter);
Since I'd like to color the "[2231]" substring a medium gray color, I thought I might be able to change the value of desc in the database, so that it looks like this:
"Helix #5 <font color='#6e737e'>[2231]</font>"
I did that only because I was searching the internet, and it seemed like it might work. Well, that doesn't work, as the tags are just output, instead of changing the color. What is wrong, and how can I fix it? I guess I'm open to a different solution if necessary, but this Android stuff is hard for me, as I don't work on it very often, so I was trying to go for the easiest solution.
UPDATE #1 ----------------------
So #MartinMarconcini was kind enough to point me in the right direction, and I copy and pasted his colorSpan method into my activity class to test it out. I then looked all around Stack Overflow for any clues as to how to modify the text of my spinner, and then how to modify the text that's in a SimpleCursorAdapter.
I found these questions with answers:
Android, using SimpleCursorAdapter to set colour not just strings
Changing values from Cursor using SimpleCursorAdapter
That gave me some ideas, so I tried to work with that:
// Prepare unit dropdown
Cursor units = db.getAllUnitRecords();
MatrixCursor unitsMatrixCursor = new MatrixCursor(new String[] { "_id", "desc" });
unitsMatrixCursor.addRow(new Object[] { 0, "" });
MergeCursor unitsMergeCursor = new MergeCursor(new Cursor[] { unitsMatrixCursor, units });
String[] unitsFrom = new String[]{"desc"};
int[] unitsTo = new int[]{android.R.id.text1};
Spinner unitSpinner = (Spinner) findViewById(R.id.UnitSpinner);
SimpleCursorAdapter unitAdapter = new SimpleCursorAdapter(this, android.R.layout.simple_spinner_dropdown_item, unitsMergeCursor, unitsFrom, unitsTo, 0);
unitAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
/* NEW CODE STARTS HERE */
unitAdapter.setViewBinder(new SimpleCursorAdapter.ViewBinder() {
public boolean setViewValue(View aView, Cursor aCursor, int aColumnIndex) {
if (aColumnIndex == 1) {
String desc = aCursor.getString(aColumnIndex);
TextView textView = (TextView) aView;
final Spannable colorized = colorSpan(desc);
textView.setText(TextUtils.isEmpty(colorized) ? desc + "a" : colorized + "b");
return true;
}
return false;
}
});
/* NEW CODE ENDS HERE */
unitSpinner.setAdapter(unitAdapter);
Notice I added the letter "a" if there was no text, and "b" if there was text. Sure enough, the "a" and "b" were added to my spinner items, but there was no color change! So, I am trying ... but could still use some help. Here is an image of what I'm seeing:
As mentioned in the comments, the presentation shouldn’t be tied to the logic. This is a presentation problem. You want to display a text and you want part of that text to be colored.
So, anywhere in your app where you need to display/present this text to the user, say…
someTextViewOrOtherWidget.setText(yourString);
…you’ll then have to call a method that does the coloring for you.
Example…
I’d move this code into a separate method/place for reuse and make it more re-usable by not hardcoding the [] and such,but this is how a simple example would look:
private Spannable colorSpan(final String text) {
if (TextUtils.isEmpty(text)) {
// can't colorize an empty text
return null;
}
// Determine where the [] are.
int start = text.indexOf("[");
int end = text.indexOf("]");
if (start < 0 || end < 0 || end < start) {
// can't find the brackets, can't determine where to colorize.
return null;
}
Spannable spannable = new SpannableString(text);
spannable.setSpan(
new ForegroundColorSpan(Color.BLUE)
, start
, end
, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
);
return spannable;
}
And you’d use it like…
String text = "Hello [123123] how are you?";
final Spannable colorized = colorSpan(text);
textView.setText(TextUtils.isEmpty(colorized) ? text : colorized);
I hope this gives you a better idea how to get started.
So, with a lot of help from #MartinMarconcini, I finally achieved what needed to be done, and so I wanted to leave "the answer" here, in case anyone else wants to see what needed to be done. I ended up making a custom cursor adapter, and although I'm still dumbfounded by the complexity of Android Studio, it works!
First, in the activity, the way SimpleCursorAdapter was being used ended up getting changed to the custom cursor adapter (which extends SimpleCursorAdapter).
These lines:
Spinner unitSpinner = (Spinner) findViewById(R.id.UnitSpinner);
SimpleCursorAdapter unitAdapter = new SimpleCursorAdapter(this, android.R.layout.simple_spinner_dropdown_item, unitsMergeCursor, unitsFrom, unitsTo, 0);
unitAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
unitSpinner.setAdapter(unitAdapter);
Were replaced with these lines:
Spinner customUnitSpinner = (Spinner) findViewById(R.id.UnitSpinner);
UnitSpinnerCursorAdapter customUnitAdapter = new UnitSpinnerCursorAdapter(this, R.layout.unit_spinner_entry, unitsMergeCursor, unitsFrom, unitsTo, 0);
customUnitSpinner.setAdapter(customUnitAdapter);
I put the custom cursor adapter in its own file, and I put Martin's colorSpan method in there too (for now):
package android.skunkbad.xxx;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Color;
import android.support.v4.widget.SimpleCursorAdapter;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.style.ForegroundColorSpan;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
public class UnitSpinnerCursorAdapter extends SimpleCursorAdapter {
private Context context;
private int layout;
public UnitSpinnerCursorAdapter(Context context, int layout, Cursor c, String[] from, int[] to, int flags) {
super(context, layout, c, from, to, flags);
this.context = context;
this.layout = layout;
}
/**
* newView knows how to return a new spinner option that doesn't contain data yet
*/
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
super.newView(context, cursor, parent);
Cursor c = getCursor();
final LayoutInflater inflater = LayoutInflater.from(context);
View v = inflater.inflate(layout, parent, false);
int descCol = c.getColumnIndex("desc");
String desc = c.getString(descCol);
final Spannable colorized = colorSpan(desc);
TextView unit_spinner_entry = (TextView) v.findViewById(R.id.custom_spinner_entry_desc);
if (unit_spinner_entry != null) {
unit_spinner_entry.setText(TextUtils.isEmpty(colorized) ? desc : colorized);
}
return v;
}
/**
* bindView knows how to take an existing layout and update it with the data pointed to by the cursor
*/
#Override
public void bindView(View view, Context context, Cursor cursor) {
super.bindView(view, context, cursor);
int descCol = cursor.getColumnIndex("desc");
String desc = cursor.getString(descCol);
final Spannable colorized = colorSpan(desc);
TextView unit_spinner_entry = (TextView) view.findViewById(R.id.custom_spinner_entry_desc);
if (unit_spinner_entry != null) {
unit_spinner_entry.setText(TextUtils.isEmpty(colorized) ? desc : colorized);
}
}
private Spannable colorSpan(final String text) {
if (TextUtils.isEmpty(text)) {
// can't colorize an empty text
return null;
}
// Determine where the [] are.
int start = text.indexOf("[");
int end = text.indexOf("]");
if (start < 0 || end < 0 || end < start) {
// can't find the brackets, can't determine where to colorize.
return null;
}
end++; /* Why do we even need this ? */
Spannable spannable = new SpannableString(text);
spannable.setSpan(
new ForegroundColorSpan(Color.rgb(100,100,100))
, start
, end
, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
);
return spannable;
}
}
Finally, I had to make a layout file for each entry in the spinner:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="50dp">
<TextView
android:id="#+id/custom_spinner_entry_desc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="16dp"
android:textColor="#FFFFFF"
android:layout_marginLeft="10dp" />
</LinearLayout>
Thanks Martin! It might seem like nothing to you, but it was hard for me, and I couldn't have done it without your help.
One note: I had to put end++; in your colorSpan method, because for some reason it wasn't coloring the closing bracket.
This is my first time working with dates in listviews. From looking through the SO and google, there are lots of resources and examples on listviews and binding dates to them. Many of the examples are of the date field only, but alluding to how to add them in with other fields. I'm either not understanding how to implement these, or everyone has a hard time with it, or both. So in that way I'm thinking starting with what I'm doing conceptually and then figuring out where my logic is wrong or can improve will help others.
I am working to display a listview with several textview fields, one of which is a formatted date. The data is stored in SQLite as yyyymmdd for sortability. (The listview is sorted by the date field using a good query in my dbadapter and that is working correctly)
I researched a bit on binding dates to listviews, and found that my simplecursoradapter wont work by itself but I need an additional CursorAdapter OR ViewBinder. Based on this example, I've started with a ViewBinder, but Not having my field populated with anything. To be clear, what I've done is kept my simplecursoradapter for the other fields, and they are functioning correctly. I then added an additional adapter with connection to the binder.
Here is the code from the activity where the listview exists:
private void fillTasks() {
Cursor tasksCursor = mDbHelper.fetchAllTasksforBatch(mRowId);
startManagingCursor(tasksCursor);
// Create an array to specify the fields we want to display in the list
String[] from = new String[]{
VDbAdapter.COLUMN_taskid,
VDbAdapter.COLUMN_tasktype,
VDbAdapter.COLUMN_taskSpecGrav,
VDbAdapter.COLUMN_taskacid
//VDbAdapter.COLUMN_taskdate
};
// and an array of the fields we want to bind those fields to
int[] to = new int[]{ R.id.TASKID,
R.id.TASKTYPE,
R.id.TASKGRAVITY,
R.id.TASKACID
//R.id.DATE
};
// Now create a simple cursor adapter and set it to display
SimpleCursorAdapter tasks = new SimpleCursorAdapter(this, R.layout.tasklist_layout, tasksCursor, from, to);
setListAdapter(tasks);
String[] fromdate = new String[]{
VDbAdapter.COLUMN_taskdate
};
// and an array of the fields we want to bind those fields to
int[] todate = new int[]{
R.id.DATE
};
// SimpleCursorAdapter tasksdates = new SimpleCursorAdapter(this, R.layout.tasklist_layout, tasksCursor, fromdate, todate);
final DateViewBinder binder = new DateViewBinder(formatforddisplay, R.id.DATE);
tasks.setViewBinder(binder);
}
Here is the code from the DateViewBinder, essentially the same as from the example link above(package name is correct of course).
import java.text.SimpleDateFormat;
import java.util.Date;
import android.database.Cursor;
import android.view.View;
import android.widget.SimpleCursorAdapter.ViewBinder;
import android.widget.TextView;
public class DateViewBinder implements ViewBinder {
private SimpleDateFormat mFormat;
private int mTargetViewId;
public DateViewBinder(SimpleDateFormat formatforddisplay, int targetViewId) {
mFormat = formatforddisplay;
mTargetViewId = targetViewId;
}
public void setBinding(int from) {
mTargetViewId = from;
}
public void setFormat(SimpleDateFormat format) {
mFormat = format;
}
#Override
public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
final int id = view.getId();
if (id == mTargetViewId) {
final String value = getLongFieldAsString(cursor, columnIndex);
if (view instanceof TextView)
setViewText((TextView)view, value);
else
throw new IllegalStateException(view.getClass().getName() + " is not a view that can be bound by this view binder (" +
DateViewBinder.class.getSimpleName() + ")");
return true;
}
return false;
}
I am getting no errors in logcat, so usually that helps me get pointed in the right direction. Any help is appreciated, with my problem and with how I've stated it (first time poster)
This was solved by passing the cursor to the DateViewBinder as a parameter. Here is the code with changes:
Fill Tasks Changes
SimpleCursorAdapter tasks =
new SimpleCursorAdapter(this, R.layout.tasklist_layout, tasksCursor, from, to);
final DateViewBinder binder = new DateViewBinder(formatforddisplay, R.id.DATE, tasksCursor);
tasks.setViewBinder(binder);
setListAdapter(tasks);
DateViewBinder Changes
public DateViewBinder(SimpleDateFormat formatfromBatchOverview, int targetViewId, Cursor taskscursor) {
mFormat = formatfromBatchOverview;
mTargetViewId = targetViewId;
cursor = taskscursor;
}
I have a ListView that's populated from a SimpleCursorAdapter each row containing 3 different TextViews. I only want to modify one of the TextViews for all rows with a ViewBinder (R.id.text65), however it keeps updating all 3 TextViews of every row. Here's my code:
cursorAdapter.setViewBinder(new SimpleCursorAdapter.ViewBinder() {
public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
sign = (TextView) view;
SharedPreferences currency = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
String currency1 = currency.getString("Currency", "$");
sign.setText(currency1);
return true;
}
});
P.S. I tried (TextView) findViewById(R.id.text65); and I got a Force close.
Solution 1:
You should check the column index in the viewbinder:
public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
if (columnIndex == cursor.getColumnIndexOrThrow(**??**)) // 0 , 1 , 2 ??
{
sign = (TextView) view;
SharedPreferences currency = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
String currency1 = currency.getString("Currency", "$");
sign.setText(currency1);
return true;
}
return false;
}
Note, the column index, is the DBcolumn index of the currency / the index of the column in whatever is your data source.
Solution 2:
You are probably defining an int[] for the fields to bind to in your listview for example :
// and an array of the fields we want to bind those fields to
int[] to = new int[] { R.id.field1, R.id.field2, R.id.Currency };
SimpleCursorAdapter entries = new SimpleCursorAdapter(this, R.layout.row, cursor, from, to);
... Conditionally, you can simply pass 0 instead of the layout ids of the fields that you donT want to be bound / shown.
int[] to = new int[] { 0, 0, R.id.Currency };
This way only the Currency field will be bound.
Also, the reason you get the force close is because, technically, there is no single text65 in your contentView but many. You cannot access it from the main layout level. It is unique only in the scope of a single row.
Update :
Solution 3:
Check the id of the view in the ViewBinder
public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
int viewId = view.getId();
Log.v("ViewBinder", "columnIndex=" + columnIndex + " viewId = " + viewId);
if(viewId == R.id.text65)
{
sign = (TextView) view;
SharedPreferences currency = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
String currency1 = currency.getString("Currency", "$");
sign.setText(currency1);
return true;
}
return false;
}
Could you try this?
Useful hint: you can use the Log.v to check certain values in your code without having to debug it.
Hope it helps.
I have a String in an Editext that I convert to a float like this :
float montantFac = Float.valueOf(etMontantFac.getText().toString());
I save it in a database and later, I get it back from the DB and I assign it again to this EditText.
etMontantFac.setText(facture.getString(facture.getColumnIndexOrThrow("montant_fa")));
My problem is that it displays some numbers with exponent.
For example, 1000000 is displayed like this : 1e+06
How can I do to ever display the values with numers only (no letter or + or anything else) ?
EDIT :
#Pratik : Thanks, that works fine!
I also need to do the same for an item of a ListView but I don't find how to use your solution in this case. Can you help me ?
Here is my ListView :
private void fillData(){
Cursor cuFactures = db.recupListeFacture(triFactures, argumentWhereVhc, choixVhc, argumentWhereGar, choixGar);
startManagingCursor(cuFactures);
ListAdapter adapter = new SimpleCursorAdapter(this,
R.layout.facture_ligne,
cuFactures,
new String[] {"libelle_fa", "nom_vhc", "montant_fa", "nom_garage", "date_fa"},
new int[] {R.id.listeNomFac, R.id.listeNomVhcFac, R.id.listeMontantFac, R.id.listeNomGarFac, R.id.listeDateFac});
setListAdapter(adapter);
The concerned field is always "montant_fa"
Thank you in advance!
check this link for format
http://download.oracle.com/javase/tutorial/i18n/format/decimalFormat.html
try this way
float val = facture.getFloat(facture.getColumnIndexOrThrow("montant_fa"));
NumberFormat nf = NumberFormat.getNumberInstance(loc);
DecimalFormat df = (DecimalFormat)nf;
df.applyPattern(pattern);
String output = df.format(value);
etMontantFac.setText(output);
I know this is old, but here is for the format data between sqlite and listview in case someone trying to do the same thing. I had similar problem and solved it with that.
here is my example
adapter = new SimpleCursorAdapter(....);
adapter.setViewBinder(new SimpleCursorAdapter.ViewBinder() {
#Override
public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
if (columnIndex == cursor.getColumnIndex("columnname")) {
TextView tv = (TextView) view;
NumberFormat nf = NumberFormat.getInstance(Locale.ENGLISH);
tv.setText(nf.format(cursor.getInt(columnIndex)));
// must return true to be applied
return true;
}
return false;
}
});
I have a Spinner which is to show a list of data fetched from database. The data is returned to a cursor from query, and the cursor gets passed to spinner's SimpleCursorAdapter. It is working fine as such, but I want to insert another item on top of this data. For example, the spinner is already showing a list of user created templates saved in DB, but I want to insert "New Template" and "Empty Template" on top of the list of templates, and it needs to be inserted into Cursor/SimpleCursorAdapter somehow.
I have considered using an arraylist and populating the arraylist from cursor, but cursor is better solution for me since it contains other related rows of data too. I searched internet for other solutions and found some answers asking to use CursorWrapper for this purpose, but I could not find a concrete example how to use CursorWrapper to accomplish what I want. How can I insert some rows in cursor or can someone please give a easy to follow CursorWrapper example!! Thanks in advance.
You can use a combination of MergeCursor and MatrixCursor with your DB cursor like this:
MatrixCursor extras = new MatrixCursor(new String[] { "_id", "title" });
extras.addRow(new String[] { "-1", "New Template" });
extras.addRow(new String[] { "-2", "Empty Template" });
Cursor[] cursors = { extras, cursor };
Cursor extendedCursor = new MergeCursor(cursors);
This is the method I tried.
MatrixCursor m = new MatrixCursor(c.getColumnNames());
Cursor c = DBHelper.rawQuery("Select values from your_table");
MatrixCursor m = new MatrixCursor(c.getColumnNames());
//Use MatrixCursor#addRow here to add before the original cursor
while (c.moveToNext()) {
//Use MatrixCursor#addRow here to add before the original row
DBHelper.insertRow(c, m);
//Use MatrixCursor#addRow here to add after the original row
}
//Use MatrixCursor#addRow here to add after the original cursor
m.addRow(new String[]{col1Val, col2Val, col3Val,..., //to match the number of columns needed});
DBHelper.insertRow()
public final static void insertRow(Cursor from, MatrixCursor to) {
final String columns[] = from.getColumnNames(), values[] = new String[columns.length];
final int size = columns.length;
for (int i = 0; i < size; i++) {
values[i] = getStringFromColumn(from, columns[i]);
}
to.addRow(values);
}
With this method, you can add any amount of rows anywhere in your cursor. Even though it is not making use of CursorWrapper, it can be used with CursorAdapters or SimpleCursorAdapters.
I tried the solution provided by #naktinis, but the result wasn't what I expected. What I myself wanted to achieve as an adapter in which new elements can be added at the top (index 0). However, with the solution given, new elements were indeed added at the top but only to the END of the MatrixCursor. In other words, when I added rows dynamically to the "extras" MatrixCursor, I got something like this:
"extras" row 1
"extras" row 2
"extras" row 3
"cursor" row 1
"cursor" row 2
"cursor" row 3.
However, what I really wanted to achieve was something like this:
"extras" row 3
"extras" row 2
"extras" row 1
"cursor" row 1
"cursor" row 2
"cursor" row 3.
In other words, most recent elements enter at the top (index 0).
I was able to achieve this manually by doing the follow. Note that I did not include any logic to handle dynamically removing elements from the adapter.
private class CollectionAdapter extends ArrayAdapter<String> {
/**
* This is the position which getItem uses to decide whether to fetch data from the
* DB cursor or directly from the Adapter's underlying array. Specifically, any item
* at a position lower than this offset has been added to the top of the adapter
* dynamically.
*/
private int mCursorOffset;
/**
* This is a SQLite cursor returned by a call to db.query(...).
*/
private Cursor mCursor;
/**
* This stores the initial result returned by cursor.getCount().
*/
private int mCachedCursorCount;
public Adapter(Context context, Cursor cursor) {
super(context, R.layout.collection_item);
mCursor = cursor;
mCursorOffset = 0;
mCachedCursorCount = -1;
}
public void add(String item) {
insert(item, 0);
mCursorOffset = mCursorOffset + 1;
notifyDataSetChanged();
}
#Override
public String getItem(int position) {
// return the item directly from underlying array if it was added dynamically.
if (position < mCursorOffset) {
return super.getItem(position);
}
// try to load a row from the cursor.
if (!mCursor.moveToPosition(position - mCursorOffset)) {
Log.d(TAG, "Failed to move cursor to position " + (position - mCursorOffset));
return null; // this shouldn't happen.
}
return mCursor.getString(INDEX_COLLECTION_DATA);
}
#Override
public int getCount() {
if (mCachedCursorCount == -1) {
mCachedCursorCount = mCursor.getCount();
}
return mCursorOffset + mCachedCursorCount;
}
}