So I am trying to use the Picasso Library for image downloading and caching. In order to get the contactUri to pass to Picasso I need to make a query to the Contacts Content Provider. Since I don't want to block the main UI thread to get the contactId, I have put this in an AsyncTask. And once I get that contactId, I make the call to Picasso in the onPostExecute() method of the AsyncTask.
However, I am noticing a flickering that shows up when I scroll through my ListView quickly. It seems to me that there is an issue with the ViewHolder since the recycled views are displaying the previous image before setting the appropriate image. Is there anyway to avoid this?
public class ConversationThreadsCursorAdapter extends SimpleCursorAdapter {
// region Constants
private static final int RECIPIENT_IDS_COLUMN_INDEX = 3;
private static final int ID2_COLUMN_INDEX = 0;
private static final int ADDRESS_COLUMN_INDEX = 1;
// endregion
// region Variables
private final String DEBUG_TAG = getClass().getSimpleName().toString();
private Context mContext;
protected Drawable mDefaultPicDrawable;
protected ContentResolver mContentResolver;
protected LinearLayout.LayoutParams mContactPicLayoutParams;
// endregion
// region Constructors
public ConversationThreadsCursorAdapter(Context context, int layout,
Cursor c, String[] from, int[] to, int flags) {
super(context, layout, c, from, to, flags);
mContext = context;
mDefaultPicDrawable = mContext.getResources().getDrawable(
R.drawable.ic_contact_picture);
mContactPicLayoutParams = new LinearLayout.LayoutParams(
mDefaultPicDrawable.getIntrinsicWidth(),
mDefaultPicDrawable.getIntrinsicHeight());
}
// endregion
public View getView(final int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder = null;
if (convertView == null) {
convertView = mLayoutInflater.inflate(R.layout.simple_message, null);
// Creates a ViewHolder and store references to the children
// views we want to bind data to.
viewHolder = setupViewHolder(convertView);
convertView.setTag(viewHolder);
} else {
// Get the ViewHolder back to get fast access to the TextView
// and the ImageView.
viewHolder = (ViewHolder) convertView.getTag();
viewHolder.task.cancel(true);
}
mCursor = getCursor();
mCursor.moveToPosition(position);
viewHolder.position = position;
String recipient_ids = mCursor.getString(RECIPIENT_IDS_COLUMN_INDEX);
String[] recipients = recipient_ids.split(" ");
viewHolder.task = new AddressFetcherTask(viewHolder, position);
viewHolder.task.execute(recipients);
return convertView;
}
// region Helper Methods
private ViewHolder bindUIElements(View convertView) {
ViewHolder viewHolder = new ViewHolder();
viewHolder.contactBadge = (QuickContactBadge) convertView.findViewById(R.id.contact_pic);
return viewHolder;
}
private ViewHolder setupViewHolder(View convertView) {
ViewHolder viewHolder = bindUIElements(convertView);
viewHolder.contactBadge.setLayoutParams(mContactPicLayoutParams);
return viewHolder;
}
// endregion
// region Inner Classes
private class ViewHolder {
QuickContactBadge contactBadge;
int position;
}
private class AddressFetcherTask extends AsyncTask < String[], Void, Integer > {
private ViewHolder mViewHolder;
private int mPosition;
public AddressFetcherTask(ViewHolder viewHolder, int position) {
mViewHolder = viewHolder;
mPosition = position;
}
#Override
protected Integer doInBackground(String[]...recipients) {
String recipient = recipients[0][0];
Log.d(DEBUG_TAG, "recipient is " + recipient);
Cursor c = mContentResolver.query(
Uri.parse("content://mms-sms/canonical-addresses"), null, "_id = " + recipient, null, null);
String _id = "";
String address = "";
while (c.moveToNext()) {
_id = c.getString(ID2_COLUMN_INDEX);
address = c.getString(ADDRESS_COLUMN_INDEX);
}
c.close();
int contactId;
if (address != null) {
contactId = ContactsUtils.getContactId(mContext, address, "address");
} else {
contactId = Integer.valueOf(address);
}
return contactId;
}
#Override
protected void onPostExecute(Integer contactId) {
if (mViewHolder.position == mPosition) {
Picasso.with(mContext)
.load(getContactUri(contactId))
.placeholder(R.drawable.ic_contact_picture)
.into(mViewHolder.contactBadge);
}
}
}
// endregion
}
Just set the imageview to null in within getView and it should remove what you are experiencing for the most part you'll be right.
The other tiny tiny corner case aspect is that when your asynctask arrives at postExecute, the view might still exist, but it might have already been assigned a different contact to load up (it's been recycled).
You need to put some kind of tag in the viewholder, and then check that it is still the same when you go to set it in postexecute.
To remove the fade in, you need to remove the asynctask from the getview. You need to be able to call picasso within getview, which means having your data ready before arriving at getview.
The below, not quite sure if it will compile, I've done it in a text editor.
But bassically I'm caching results in mCachedContactIds and just reloading the whole table if I need a new one. I've typically found this to be robust. But you can also call the picasso code which I've commented out
public class ConversationThreadsCursorAdapter extends SimpleCursorAdapter {
// region Constants
private static final int RECIPIENT_IDS_COLUMN_INDEX = 3;
private static final int ID2_COLUMN_INDEX = 0;
private static final int ADDRESS_COLUMN_INDEX = 1;
private HashMap<String, Integer> mCachedContactIds = new HashMap<String, Integer>();
// endregion
// region Variables
private final String DEBUG_TAG = getClass().getSimpleName().toString();
private Context mContext;
protected Drawable mDefaultPicDrawable;
protected ContentResolver mContentResolver;
protected LinearLayout.LayoutParams mContactPicLayoutParams;
// endregion
// region Constructors
public ConversationThreadsCursorAdapter(Context context, int layout,
Cursor c, String[] from, int[] to, int flags) {
super(context, layout, c, from, to, flags);
mContext = context;
mDefaultPicDrawable = mContext.getResources().getDrawable(
R.drawable.ic_contact_picture);
mContactPicLayoutParams = new LinearLayout.LayoutParams(
mDefaultPicDrawable.getIntrinsicWidth(),
mDefaultPicDrawable.getIntrinsicHeight());
}
// endregion
public View getView(final int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder = null;
if (convertView == null) {
convertView = mLayoutInflater.inflate(R.layout.simple_message, null);
// Creates a ViewHolder and store references to the children
// views we want to bind data to.
viewHolder = setupViewHolder(convertView);
convertView.setTag(viewHolder);
} else {
// Get the ViewHolder back to get fast access to the TextView
// and the ImageView.
viewHolder = (ViewHolder) convertView.getTag();
viewHolder.task.cancel(true);
viewHolder.contactBadge.setImageDrawable(mDefaultPicDrawable);
}
mCursor = getCursor();
mCursor.moveToPosition(position);
viewHolder.position = position;
String recipient_ids = mCursor.getString(RECIPIENT_IDS_COLUMN_INDEX);
String[] recipients = recipient_ids.split(" ");
String recipient = recipients[0];
if(mCachedContactIds.get(recipient) != null){
Picasso.with(mContext)
.load(getContactUri(mCachedContactIds.get(recipient)))
.placeholder(R.drawable.ic_contact_picture)
.into(mViewHolder.contactBadge);
} else {
viewHolder.task = new AddressFetcherTask(viewHolder, position);
viewHolder.task.execute(recipients);
}
return convertView;
}
// region Helper Methods
private ViewHolder bindUIElements(View convertView) {
ViewHolder viewHolder = new ViewHolder();
viewHolder.contactBadge = (QuickContactBadge) convertView.findViewById(R.id.contact_pic);
return viewHolder;
}
private ViewHolder setupViewHolder(View convertView) {
ViewHolder viewHolder = bindUIElements(convertView);
viewHolder.contactBadge.setLayoutParams(mContactPicLayoutParams);
return viewHolder;
}
// endregion
// region Inner Classes
private class ViewHolder {
QuickContactBadge contactBadge;
int position;
AddressFetcherTask task;
}
private class AddressFetcherTask extends AsyncTask < String[], Void, Integer > {
private ViewHolder mViewHolder;
private int mPosition;
private String mRecipient;
public AddressFetcherTask(ViewHolder viewHolder, int position) {
mViewHolder = viewHolder;
mPosition = position;
}
#Override
protected Integer doInBackground(String[]...recipients) {
mRecipient = recipients[0][0];
Log.d(DEBUG_TAG, "recipient is " + recipient);
Cursor c = mContentResolver.query(
Uri.parse("content://mms-sms/canonical-addresses"), null, "_id = " + mRecipient, null, null);
String _id = "";
String address = "";
while (c.moveToNext()) {
_id = c.getString(ID2_COLUMN_INDEX);
address = c.getString(ADDRESS_COLUMN_INDEX);
}
c.close();
int contactId;
if (address != null) {
contactId = ContactsUtils.getContactId(mContext, address, "address");
} else {
contactId = Integer.valueOf(address);
}
return contactId;
}
#Override
protected void onPostExecute(Integer contactId) {
if (mViewHolder.position == mPosition) {
mCachedContactIds.put(mRecipient, contactId);
Picasso.with(mContext)
.load(getContactUri(mCachedContactIds.get(recipient)))
.placeholder(R.drawable.ic_contact_picture)
.into(mViewHolder.contactBadge);
}
}
}
// endregion
}
Or if all that's bugging you left is the fade from picasso, then add noFade() to the request.
Related
I have a very specific problem. I'm creating a word list app that basically lets me enter some words and their meanings and stores them in an SQLite database and then it shows them back to me. I've made my own content provider to handle all these interactions. I store the words in one table and the definitions in another table and these two tables are linked together through the books' unique id. I'm also using a CursorLoader and a CursorAdapter to populate my listView in my MainActivity. Now my problem is when I get the list of the words that I have in my database, some of the words have more than one definition and I have to query against the database as I'm drawing each list item in my listView to find the definitions and put them on the list. This has caused my listView to stutter. I've even tried to use AsyncTask but I don't want to start a new thread every time a word is getting added to my listView at runtime. Is there any efficient, right way to do this?
This is my CursorAdapter class where I believe is the source of the problem.
public class WordAdapter extends CursorAdapter {
public static final String[] definitionsProjection = {
Definitions._ID,
Definitions.COLUMN_TEXT,
Definitions.COLUMN_POS
};
public static final String[] examplesProjection = {
Examples._ID,
Examples.COLUMN_TITLE,
Examples.COLUMN_TEXT,
Examples.COLUMN_URL
};
public static final int DEFINITIONS_COLUMN_TEXT = 1;
public static final int DEFINITIONS_COLUMN_POS = 2;
public static final int EXAMPLES_COLUMN_TITLE = 1;
public static final int EXAMPLES_COLUMN_TEXT = 2;
public static final int EXAMPLES_COLUMN_URL = 3;
private static final int VIEW_TYPE_TODAY = 0;
private static final int VIEW_TYPE_OLD = 1;
public WordAdapter(Context context, Cursor c, int flags) {
super(context, c, flags);
}
#Override
public int getViewTypeCount() {
return 2;
}
#Override
public int getItemViewType(int position) {
if(position == 0) {
return VIEW_TYPE_TODAY;
} else {
return VIEW_TYPE_OLD;
}
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
int viewType = getItemViewType(cursor.getPosition());
int layoutId = -1;
if(viewType == VIEW_TYPE_TODAY) {
layoutId = R.layout.list_item_today;
} else if(viewType == VIEW_TYPE_OLD) {
layoutId = R.layout.list_item_word;
}
View view = LayoutInflater.from(context).inflate(layoutId, parent, false);
ViewHolder viewHolder = new ViewHolder(view);
view.setTag(viewHolder);
return view;
}
#Override
public void bindView(View view, Context context, Cursor cursor) {
ViewHolder viewHolder = (ViewHolder) view.getTag();
int viewType = getItemViewType(cursor.getPosition());
String wordId = cursor.getString(MainFragment.WORDS_ID);
viewHolder.wordView.setText(wordId + " / " + cursor.getString(MainFragment.WORDS_COLUMN_WORD));
FetchDefinitions fetchDefinitions = new FetchDefinitions(context, view);
fetchDefinitions.execute(wordId);
switch (viewType) {
case VIEW_TYPE_TODAY:
{
viewHolder.noteView.setText(cursor.getString(MainFragment.WORDS_COLUMN_NOTE));
Cursor exampleCursor = context.getContentResolver().query(
Examples.CONTENT_URI.buildUpon().appendPath(wordId).build(),
examplesProjection,
null,
null,
null
);
exampleCursor.moveToFirst();
viewHolder.exampleOneView.setText(exampleCursor.getString(EXAMPLES_COLUMN_TITLE));
if(exampleCursor.getCount() > 1) {
exampleCursor.moveToNext();
viewHolder.exampleTwoView.setText(exampleCursor.getString(EXAMPLES_COLUMN_TITLE));
} else {
viewHolder.exampleTwoView.setVisibility(View.GONE);
}
exampleCursor.close();
break;
}
}
viewHolder.flagView.setText("Flag " + Utility.flagsToString(cursor.getInt(MainFragment.WORDS_COLUMN_FLAG)));
viewHolder.sourceView.setText("Source " + cursor.getString(MainFragment.WORDS_COLUMN_SOURCE));
viewHolder.dateView.setText(Utility.convertDateToUXFormat(cursor.getInt(MainFragment.WORDS_COLUMN_DATE)));
}
private static class ViewHolder {
public final TextView wordView;
public final TextView noteView;
public final TextView exampleOneView;
public final TextView exampleTwoView;
public final TextView flagView;
public final TextView sourceView;
public final TextView dateView;
private ViewHolder(View view) {
wordView = (TextView) view.findViewById(R.id.word_textView);
noteView = (TextView) view.findViewById(R.id.note_textView);
exampleOneView = (TextView) view.findViewById(R.id.example_1_textView);
exampleTwoView = (TextView) view.findViewById(R.id.example_2_textView);
flagView = (TextView) view.findViewById(R.id.flag_textView);
sourceView = (TextView) view.findViewById(R.id.source_textView);
dateView = (TextView) view.findViewById(R.id.date_textView);
}
}
private class FetchDefinitions extends AsyncTask<String, Void, Cursor> {
private Context bgContext;
private View bgView;
public TextView posOneView;
public TextView posTwoView;
public TextView definitionOneView;
public TextView definitionTwoView;
public FetchDefinitions(Context context, View view) {
this.bgContext = context;
this.bgView = view;
}
#Override
protected Cursor doInBackground(String... params) {
String wordId = params[0];
return bgContext.getContentResolver().query(
Definitions.CONTENT_URI.buildUpon().appendPath(wordId).build(),
definitionsProjection,
null,
null,
null
);
}
#Override
protected void onPostExecute(Cursor cursor) {
if(cursor != null) {
cursor.moveToFirst();
posOneView = (TextView) bgView.findViewById(R.id.pos_1_textView);
definitionOneView = (TextView) bgView.findViewById(R.id.definition_1_textView);
posOneView.setText(cursor.getString(DEFINITIONS_COLUMN_POS));
definitionOneView.setText(cursor.getString(DEFINITIONS_COLUMN_TEXT));
posTwoView = (TextView) bgView.findViewById(R.id.pos_2_textView);
definitionTwoView = (TextView) bgView.findViewById(R.id.definition_2_textView);
if(cursor.getCount() > 1) {
cursor.moveToNext();
posTwoView.setText(cursor.getString(DEFINITIONS_COLUMN_POS));
definitionTwoView.setText(cursor.getString(DEFINITIONS_COLUMN_TEXT));
} else {
posTwoView.setVisibility(View.GONE);
definitionTwoView.setVisibility(View.GONE);
}
}
}
}
}
I would like to ask some question about AdapterView.
In my application, there is an activity which retrieve data from database and display them in AdapterView.
However, when i install the application in different devices, I found that the part I have just mentioned could only function on some devices. The others cannot show the database results.
Here is my code:
private void showResults(String query) {
Cursor cursor = searchCustByInputText(query);
if (cursor == null) {
//
} else {
// Specify the columns we want to display in the result
String[] from = new String[] {
"cust_code",
"chinese_name"};
// Specify the Corresponding layout elements where we want the columns to go
int[] to = new int[] {
R.id.scust_code,
R.id.schinese_name};
// Create a simple cursor adapter for the definitions and apply them to the ListView
SimpleCursorAdapter customers = new SimpleCursorAdapter(this,R.layout.cust_list_item, cursor, from, to);
mListView.setAdapter(customers);
// Define the on-click listener for the list items
mListView.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Cursor c = (Cursor) mListView.getItemAtPosition(position);
String cust_code = c.getString(c.getColumnIndex("cust_code"));
if (callFromAct.equals("Main")) {
String pay_term = c.getString(c.getColumnIndex("pay_term"));
String chinese_name = c.getString(c.getColumnIndex("chinese_name"));
String english_name = c.getString(c.getColumnIndex("english_name"));
String address_1 = c.getString(c.getColumnIndex("address_1"));
String address_2 = c.getString(c.getColumnIndex("address_2"));
String address_3 = c.getString(c.getColumnIndex("address_3"));
String address_4 = c.getString(c.getColumnIndex("address_4"));
String contact = c.getString(c.getColumnIndex("contact"));
String telephone = c.getString(c.getColumnIndex("telephone"));
String last_order_date = c.getString(c.getColumnIndex("last_order_date"));
//Pass data to another Activity
Intent it = new Intent(CustEnqActivity.this, CustEnqDetailsActivity.class);
Bundle bundle = new Bundle();
bundle.putString("cust_code", cust_code);
bundle.putString("pay_term", pay_term);
bundle.putString("chinese_name", chinese_name);
bundle.putString("english_name", english_name);
bundle.putString("address_1", address_1);
bundle.putString("address_2", address_2);
bundle.putString("address_3", address_3);
bundle.putString("address_4", address_4);
bundle.putString("contact", contact);
bundle.putString("telephone", telephone);
bundle.putString("last_order_date", last_order_date);
it.putExtras(bundle);
startActivity(it);
}
else {
returnToCallingAct(cust_code);
}
//searchView.setQuery("",true);
}
});
}
}
Besides, I discovered there were two warnings in my logcat.
The constructor SimpleCursorAdapter(Context, int, Cursor, String[], int[]) is deprecated
AdapterView is a raw type. References to generic type AdapterView should be parameterized
Are they related to the problem?
Try to create a class that extends BaseAdapter and use ViewHolders for performance
eg:
public class MyBaseAdapter extends BaseAdapter {
ArrayList<ListData> myList = new ArrayList<ListData>();
LayoutInflater inflater;
Context context;
public MyBaseAdapter(Context context, ArrayList<ListData> myList) {
this.myList = myList;
this.context = context;
inflater = LayoutInflater.from(this.context); // only context can also be used
}
#Override
public int getCount() {
return myList.size();
}
#Override
public ListData getItem(int position) {
return myList.get(position);
}
#Override
public long getItemId(int position) {
return 0;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
MyViewHolder mViewHolder;
if(convertView == null) {
convertView = inflater.inflate(R.layout.layout_list_item, null);
mViewHolder = new MyViewHolder();
convertView.setTag(mViewHolder);
} else {
mViewHolder = (MyViewHolder) convertView.getTag();
}
mViewHolder.tvTitle = detail(convertView, R.id.tvTitle, myList.get(position).getTitle());
mViewHolder.tvDesc = detail(convertView, R.id.tvDesc, myList.get(position).getDescription());
mViewHolder.ivIcon = detail(convertView, R.id.ivIcon, myList.get(position).getImgResId());
return convertView;
}
// or you can try better way
private TextView detail(View v, int resId, String text) {
TextView tv = (TextView) v.findViewById(resId);
tv.setText(text);
return tv;
}
private ImageView detail(View v, int resId, int icon) {
ImageView iv = (ImageView) v.findViewById(resId);
iv.setImageResource(icon); //
return iv;
}
private class MyViewHolder {
TextView tvTitle, tvDesc;
ImageView ivIcon;
}
}
More info/example:
http://www.pcsalt.com/android/listview-using-baseadapter-android/#sthash.lNGSCiyB.dpbs
Folks.
I've a problem with my simplecursorAdapter. Everything works perfectly except when scrolling the listview it just mixes up the Favorite icon for the rows which included in the custom layout and it just appears randomly while scrolling. Can you guide me what's wrong in my code?
Thanks in advance!
public class AlternateRowCursorAdapter extends SimpleCursorAdapter {
int layoutn;
Cursor localCursor, test;
Bitmap bitImg;
Context localContext;
Bitmap Avatar;
ImageView one, two, three, four, five;
LayoutInflater mInflater;
SQLiteDatabase mDb;
MyDbHelper mHelper;
public static final String TABLE_NAME = "MSGS";
public static final String COL_MsgID = "msgIdc";
public static final String COL_MsgCat = "msgCatC";
public static final String COL_MsgTit = "msgtitleC";
public static final String COL_MsgFavor = "msgFavorC";
public AlternateRowCursorAdapter (Context context, int layout, Cursor c,
String[] from, int[] to) {
super(context, R.layout.listtype, c, from, to);
this.localContext = context;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View row = super.getView(position, convertView, parent);
final Cursor cursbbn = getCursor();
if (row == null)
{
row = ((LayoutInflater) localContext
.getSystemService(Context.LAYOUT_INFLATER_SERVICE))
.inflate(R.layout.listtype, null);
}
final String Title;
String SandID;
final String MsgID;
final String MsgFav;
Typeface tf = Typeface.createFromAsset(localContext.getAssets(),"fonts/khalaadsara.ttf");
Title = cursbbn.getString(2);
SandID = cursbbn.getString(1);
MsgID=cursbbn.getString(0);
MsgFav=cursbbn.getString(4);
TextView titler = (TextView) row.findViewById(R.id.Sandtit);
titler.setTypeface(tf);
titler.setText(Title);
one = (ImageView) row.findViewById(R.id.imageView5);
two = (ImageView) row.findViewById(R.id.imageView4);
three = (ImageView) row.findViewById(R.id.ImageView03);
four = (ImageView) row.findViewById(R.id.ImageView02);
five = (ImageView) row.findViewById(R.id.imageView1);
if(MsgFav.contentEquals("YES"))
{
one.setImageResource(R.drawable.favorpress);
}
return row;
}
}
Edit : Here is my code to refresh the values in Onresume event :
private void refreshvalues() {
mDb = mHelper.getWritableDatabase();
curs = mDb.query(MyDbHelper.TABLE_NAME, columns, null, null, null,
null, null,
null);
cursF = mDb.query(TABLE_NAME, columns, COL_MsgFavor + "=" + "?",
new String[] { "YES" }, null, null, COL_MsgTit + " ASC");
String[] headers = new String[] {MyDbHelper.COL_MsgTit ,MyDbHelper.COL_MsgID};
mAdapter = new AlternateRowCursorAdapter(this, R.layout.listtype, curs,
headers, new int[] { R.id.Sandtit});
fAdapter = new AlternateRowCursorAdapter(this, R.layout.listtype,
cursF, headers, new int[] { R.id.Sandtit });
mList.setAdapter(mAdapter);
fList.setAdapter(fAdapter);
curs.moveToFirst();
cursF.moveToFirst();
mAdapter.notifyDataSetChanged();
fAdapter.notifyDataSetChanged();
mList.invalidateViews();
fList.invalidateViews();
curs.requery();
cursF.requery();
}
First of all, you are customizing the ListView which needs to implement the custom adapter that extends any of the SimpleCustomAdapter, ArrayAdapter, and BaseAdapter.
Now, you get your listview messed up because, you are not holding the values in the views. You need to write a class ViewHolder that holds your views of the ListView in the same state, before it was scrolled. And Inside getView() method, you need to implement the else part of the if statement.
This is the efficient way to use ListView. Check below, how it can be implemented:
For example:
I made a class Task which contains the values from the database as:
static class Task {
int task_id;
String task_brief;
String task_priority;
String is_completed = "false";
Task(int tmp_task_id, String tmp_task_brief, String tmp_task_priority,
String tmp_task_is_completed) {
task_id = tmp_task_id;
task_brief = tmp_task_brief;
task_priority = tmp_task_priority;
is_completed = tmp_task_is_completed;
}
int get_task_id() {
return task_id;
}
String get_task_brief() {
return task_brief;
}
String get_task_priority() {
return task_priority;
}
String get_task_is_completed() {
return is_completed;
}
void set_task_is_completed(String tmp_task_is_completed) {
is_completed = tmp_task_is_completed;
}
}
Now, we create a class TaskViewHolder that holds the view:
static class TaskViewHolder {
TextView tv_task_brief;
ImageView iv_task_is_completed;
public TaskViewHolder(TextView tmp_tv_task_brief,
ImageView tmp_iv_task_is_completed) {
tv_task_brief = tmp_tv_task_brief;
iv_task_is_completed = tmp_iv_task_is_completed;
}
TextView get_tv_task_brief() {
return tv_task_brief;
}
ImageView get_iv_task_is_completed() {
return iv_task_is_completed;
}
}
And after that, implement custom adapter as below:
static class TaskAdapter extends ArrayAdapter<Task> {
LayoutInflater inflater;
public TaskAdapter(Context context, List<Task> tmp_al_task) {
super(context, R.layout.single_row_home,
R.id.textViewSingleRowHome, tmp_al_task);
inflater = LayoutInflater.from(context);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
Task task = (Task) this.getItem(position);
final ImageView imageView;
final TextView textView;
if (convertView == null) {
convertView = inflater.inflate(R.layout.single_row_home, null);
imageView = (ImageView) convertView
.findViewById(R.id.imageViewSingleRowHome);
textView = (TextView) convertView
.findViewById(R.id.textViewSingleRowHome);
convertView.setTag(new TaskViewHolder(textView, imageView));
} else {
TaskViewHolder viewHolder = (TaskViewHolder) convertView
.getTag();
imageView = viewHolder.get_iv_task_is_completed();
textView = viewHolder.get_tv_task_brief();
}
imageView.setTag(task);
textView.setText(task.get_task_brief());
if(task.get_task_priority().equals("High"))
textView.setTextColor(Color.RED);
else if(task.get_task_priority().equals("Medium"))
textView.setTextColor(Color.GREEN);
else
textView.setTextColor(Color.BLUE);
if (task.get_task_is_completed().equals("true")) {
imageView.setImageResource(R.drawable.action_cancel_icon);
textView.setPaintFlags(textView.getPaintFlags()
| Paint.STRIKE_THRU_TEXT_FLAG);
} else {
imageView.setImageResource(R.drawable.action_cancel_icon_2);
textView.setPaintFlags( textView.getPaintFlags() & (~ Paint.STRIKE_THRU_TEXT_FLAG));
}
imageView.setFocusable(false);
imageView.setFocusableInTouchMode(false);
imageView.setClickable(true);
imageView.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Task task = (Task) imageView.getTag();
if (task.get_task_is_completed().equals("false")) {
imageView.setImageResource(R.drawable.action_cancel_icon);
ContentValues values = new ContentValues();
values.put("is_completed", "true");
database.update("task_info", values, "task_id=?",
new String[] { task.get_task_id() + "" });
values.clear();
textView.setPaintFlags(textView.getPaintFlags()
| Paint.STRIKE_THRU_TEXT_FLAG);
task.set_task_is_completed("true");
} else {
imageView.setImageResource(R.drawable.action_cancel_icon_2);
ContentValues values = new ContentValues();
values.put("is_completed", "false");
database.update("task_info", values, "task_id=?",
new String[] { task.get_task_id() + "" });
values.clear();
textView.setPaintFlags( textView.getPaintFlags() & (~ Paint.STRIKE_THRU_TEXT_FLAG));
task.set_task_is_completed("false");
}
}
});
return convertView;
}
}
Note: One important thing, if your ListView contains items that has click events, then that items should be set to view.setFocusable(false); view.setFocusableInTouchMode(false); and view.setClickable(true);. We need this because ListView takes the click event of any of the view as its own click event. Also, the view's click event should be separated from ListView's Click event.
ListView recycles list item Views. So make sure to always call one.setImageResource().
if (MsgFav.contentEquals("YES")) {
one.setImageResource(R.drawable.favorpress);
} else {
one.setImageResource(R.drawable.default_drawable_as_specified_in_layout);
}
I am using SQLiteDatabase in android. I am not able to refersh my listview after deleting a row.
My code is as follows:
public void delete(String i) {
// TODO Auto-generated method stub
String [] columns= new String[]{KEY_ROW_ID,KEY_FHR,KEY_ID,KEY_NAME,KEY_AGE};
Cursor c= our_db.query(KEY_TABLE, columns, null, null,null, null, null);
our_db.delete(KEY_TABLE, KEY_NAME+ "="+"\'"+i+"\'", null) ;
c.requery();
}
I call it from a viewholder in efficientadapter. Below is the code where I call it:
holder.del.setOnClickListener(new OnClickListener() {
private int pos=position;
public void onClick(View v) {
// TODO Auto-generated method stub
final long newpos;
sql_get db = new sql_get(c);
db.open();
db.delete(DATA[pos]);
notifyDataSetChanged();
db.close();
}
});
Can anyone help me finding out the problem. It doesn't give any error. it just deleted the row but doesn't update the view.
Here is the adapter i used:
private static class EfficientAdapter extends BaseAdapter {
private LayoutInflater mInflater;
private Bitmap mIcon1;
private Bitmap mIcon2;
private Bitmap mIcon3;
private Bitmap mIcon4;
Context c;
int window;
public EfficientAdapter(Context context) {
// Cache the LayoutInflate to avoid asking for a new one each time.
mInflater = LayoutInflater.from(context);
this.c=context;
// Icons bound to the rows.
mIcon1 = BitmapFactory.decodeResource(context.getResources(), R.drawable.icon48x48_1);
mIcon2 = BitmapFactory.decodeResource(context.getResources(), R.drawable.icon48x48_2);
mIcon3 = BitmapFactory.decodeResource(context.getResources(), R.drawable.del1);
mIcon4 = BitmapFactory.decodeResource(context.getResources(), R.drawable.edit);
}
public int getCount() {
return DATA.length;
}
public Object getItem(int position) {
return position;
}
public long getItemId(int position) {
return position;
}
public View getView(final int position, View convertView, ViewGroup parent) {
// A ViewHolder keeps references to children views to avoid unneccessary calls
// to findViewById() on each row.
ViewHolder holder;
//int i=0;
convertView = mInflater.inflate(R.layout.list_item_icon_text, null);
// Creates a ViewHolder and store references to the two children views
// we want to bind data to.
holder = new ViewHolder();
holder.text = (TextView) convertView.findViewById(R.id.text);
holder.icon = (ImageView) convertView.findViewById(R.id.icon);
holder.del=(ImageView)convertView.findViewById(R.id.icon1);
holder.edit=(ImageView)convertView.findViewById(R.id.icon2);
window=position;
holder.text.setOnClickListener(new OnClickListener() {
private int pos=position;
//private int pos = position;
public void onClick(View v) {
System.out.println(window);
sql_get db = new sql_get(c);
db.open();
String ret_id= db.getid(DATA[pos]);
String ret_name = db.getname(DATA[pos]);
String ret_age= db.getage(DATA[pos]);
String ret_fhr= db.getfhr(DATA[pos]);
String[] result = {ret_id,ret_name,ret_age,ret_fhr};
db.close();
Bundle b=new Bundle();
b.putStringArray("key",result);
Intent i =new Intent(c,Tabs.class);
i.putExtras(b);
c.startActivity(i) ;
Toast.makeText(c, getItemId(position)+""+" Click- text " +pos+" "+ret_name, Toast.LENGTH_SHORT).show();
}
});
holder.del.setOnClickListener(new OnClickListener() {
private int pos=position;
public void onClick(View v) {
// TODO Auto-generated method stub
final long newpos;
sql_get db = new sql_get(c);
db.open();
db.delete(DATA[pos]);
notifyDataSetChanged();
db.close();
Toast.makeText(c, "deleting " + DATA[pos], Toast.LENGTH_SHORT).show();
}
});
convertView.setTag(holder);
}
holder.text.setText(DATA[position]);
holder.icon.setImageBitmap((position & 1) == 1 ? mIcon1 : mIcon2);
holder.del.setImageBitmap(mIcon3);
holder.edit.setImageBitmap(mIcon4);
return convertView;
static class ViewHolder {
ImageView del;
TextView text;
ImageView icon;
ImageView edit;
}
}
You need to delete the row also from the object holding the data for the list view..
I think you also have to remove the same data from adapter and then use your_adapter.notifyDataSetChanged() method. That worked for me...
I found the solution. As said above, I had to change the DATA object.
I used the following commands to achieve it.
db.open();
db.delete(DATA[pos]);
DATA=db.getdata();
DATA_NAME=db.getname();
DATA_AGE=db.getage();
DATA_FHR=db.getfhr();
db.close();
I'm writing a screen that displays a row's worth of information from a DB. Basically it's a Detail Fragment that represents information pertaining to one 'row' in a table. I want to understand the best practice for binding data from a cursor (one unique row from a table) to a layout of textviews, checkboxes, etc.
Is AdapterView the ticket?
#JoeMalin suggested:
Then write an adapter between a cursor and an array of text views.
Which boils down my question. What's the right way to hook a series of text views to a cursor?
If you want to do processing on some of the cursor data before you move it to the text views, then you're going beyond the adapter pattern, which assumes that "recasting" the form of a data structure to another data structure without any intermediate processing. The virtue of an adapter is that, for two data structures A and B linked by an adapter, it's assumed that B automatically changes whenever A changes.
Of course, you can redefine the idea of adapter to insert your own intermediate operation, such as converting dates, or you could make the conversion an aspect of the view that's displaying the data. I am guessing that the "processing" is really formatting, which you do for display purposes. That's an attribute of the text view, not the data; write something that extends text view and converts dates as needed. Then write an adapter between a cursor and an array of text views.
I recently implemented my own data adapter class that may be in the ball park.
public class NoteImageDataAdapter {
private final View mMainView;
private Cursor mCursor;
private ViewHolder holder;
private ContentObserver mContentObserver;
public static class ViewHolder {
public TextView title;
public TextView text;
public ImageView image;
}
public NoteImageDataAdapter(View mainView, Cursor c) {
if (mainView == null) {
throw new IllegalArgumentException("View mainView cannot be null");
}
if (c == null) {
throw new IllegalArgumentException("Cursor c cannot be null");
}
mMainView = mainView;
mCursor = c;
holder = new ViewHolder();
holder.title = (TextView) mMainView.findViewById(R.id.title);
holder.text = (TextView) mMainView.findViewById(R.id.text);
holder.image = (ImageView) mMainView.findViewById(R.id.myImageView);
mContentObserver = new ImageNoteContentObserver(new Handler());
mCursor.registerContentObserver(mContentObserver);
bindView();
}
class ImageNoteContentObserver extends ContentObserver {
public ImageNoteContentObserver(Handler handler) {
super(handler);
}
#Override
public boolean deliverSelfNotifications() {
return true;
}
#Override
public void onChange(boolean selfChange) {
Log.d("NoteImageDataAdapter", "ImageNoteContentObserver.onChange( "
+ selfChange + ")");
super.onChange(selfChange);
mCursor.requery();
bindView();
}
}
public void bindView() {
Log.d("NoteImageDataAdapter", "bindView");
mCursor.moveToFirst();
holder.text.setText(Note.getText(mCursor));
holder.title.setText(Note.getTitle(mCursor));
Uri imageUri = Note.getImageUri(mCursor);
if (imageUri != null) {
assignImage(holder.image, imageUri);
} else {
Drawable d = Note.getImageThumbnail(mCursor);
holder.image.setImageDrawable(d);
holder.image.setVisibility(View.VISIBLE);
}
}
private static final int MAX_IMAGE_PIXELS = 1024*512;
private void assignImage(ImageView imageView, Uri imageUri){
if (imageView != null && imageUri != null){
ContentResolver cr = imageView.getContext().getContentResolver();
Display display = ((WindowManager) imageView.getContext()
.getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay();
int width = (int) (display.getWidth() * 0.9);
int height = (int) (display.getHeight() * 0.9);
int minSideLength = Math.min(height, width);
Bitmap b = Util.makeBitmap(minSideLength, MAX_IMAGE_PIXELS, imageUri, cr, false);
if (b == null){
b = Util.makeBitmap(minSideLength, MAX_IMAGE_PIXELS/2, imageUri, cr, false);
}
if (b != null){
imageView.setImageBitmap(b);
imageView.setAdjustViewBounds(true);
imageView.setVisibility(View.VISIBLE);
}
}
}
}
and in your activity
private NoteImageDataAdapter mAdapter;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.note_image_view_layout);
wireDataAdapter();
}
private void wireDataAdapter() {
final String[] COLUMNS = new String[] {
Note.Columns.TITLE,
Note.Columns.TEXT,
Note.Columns.IMAGE_URI,
Note.Columns.IMAGE_THUMBNAIL,
Note.Columns._ID };
// the uri for the note row
Uri contentUri = getIntent().getData();
Cursor cur = managedQuery(contentUri, COLUMNS, null, null, null);
View mainLayout = this.findViewById(R.id.noteImageViewLayout);
mAdapter = new NoteImageDataAdapter(mainLayout, cur);
}
From the activity use:
Adpater adapter = new Adapter(Activity.this or context , Cursor);
setListAdapter(adapter) in case of List Activity;
Otherwise
listViewObj.setAdpater(adapter)
public class CustomCursorAdapter extends CursorAdapter {
private LayoutInflater mInflater;
private Context activityContext;
private ViewHolder holder;
public ContactsAdapter(Context aContext,Cursor cursor) {
super(mContext, cursor);
mInflater = LayoutInflater.from(mContext);
activityContext = aContext;
}
public static class ViewHolder{
public TextView textView1;
// View Group on Row inflate lyaout that need to be used
public ImageView imageView;
}
#Override
public void bindView(View v, Context context, Cursor c) {
holder=(ViewHolder)v.getTag();
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
final LayoutInflater inflater = LayoutInflater.from(context);
View v = inflater.inflate(R.layout.item_inflate_layout, parent, false);
holder = new ViewHolder();
holder.textView1 = (TextView) v.findViewById(R.id.TEXTVIEW1);
// Other Id that need to be used and are available on item_inflate_layout
holder.imageView = (ImageView) v.findViewById(R.id.IMAGEVIEW);
v.setTag(holder);
bindView(v, context, cursor);
return v;
}
}