I created the following CursorAdapter which shows messages from my SQL database, everything is added well until I scroll the list, I know that the objects are recycled, but in a wrong way. Here is my CursorAdapter class:
public class ChatAdapter extends CursorAdapter {
public ChatAdapter(Context context, Cursor cursor, int flags) {
super(context, cursor, 0);
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return LayoutInflater.from(context).inflate(R.layout.chat_item, parent,
false);
}
#Override
public void bindView(View view, Context context, Cursor cursor) {
// Find fields to populate in inflated template
TextView left = (TextView) view.findViewById(R.id.lefttext);
TextView right = (TextView) view.findViewById(R.id.righttext);
LinearLayout rightBubble = (LinearLayout) view
.findViewById(R.id.right_bubble);
LinearLayout leftBubble = (LinearLayout) view
.findViewById(R.id.left_bubble);
TextView leftDate = (TextView) view.findViewById(R.id.leftdate);
TextView rightDate = (TextView) view.findViewById(R.id.rightdate);
// Extract properties from cursor
String from = cursor.getString(cursor.getColumnIndexOrThrow("from"));
String txt = cursor.getString(cursor.getColumnIndexOrThrow("message"));
String date = cursor.getString(cursor.getColumnIndexOrThrow("t"));
String id = cursor.getString(cursor.getColumnIndexOrThrow("id"));
// Parse time
long datevalue = Long.valueOf(date) * 1000;
Date dateformat = new java.util.Date(datevalue);
String convert = new SimpleDateFormat("HH:mm").format(dateformat);
// Populate fields with extracted properties
if (from.equals("me")) {
right.setText(txt);
left.setText("");
rightBubble
.setBackgroundResource(R.drawable.balloon_outgoing_normal);
leftBubble.setBackgroundDrawable(null);
rightDate.setText(convert);
leftDate.setVisibility(View.GONE);
}
else {
left.setText(txt);
right.setText("");
leftBubble
.setBackgroundResource(R.drawable.balloon_incoming_normal);
rightBubble.setBackgroundDrawable(null);
leftDate.setText(convert);
rightDate.setVisibility(View.GONE);
}
}
}
Unfortenately, after scrolling the list, dates from the rightDate and leftDate dissapears after moving back. I think it't due the .setVisibility(View.GONE)
Any suggestions to fix this?
when the view is recycled, it is in the previous state, android did not clear the status for you.
To fix your problem, you have to set the view in question to VISIBLE when needed
Edit:
like this, add the 2 lines
if (from.equals("me")) {
// your original code
rightDate.setVisibility(View.VISIBLE); //add this
}
else {
// your original code
leftDate.setVisibility(View.VISIBLE); //add this
}
Related
My objective is to convert a working spinner (populated via a cursor adapter) to have alternating backgrounds. Similar to :-
Currently I have this, where everything works fine :-
This is the relevant working code within the cursor adpater (i.e. with the plain dropdowns) :-
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return LayoutInflater.from(context).inflate(R.layout.activity_aisle_shop_list_selector, parent, false);
}
#Override
public void bindView(View view,Context context, Cursor cursor) {
determineViewBeingProcessed(view,"BindV",-1);
TextView shopname = (TextView) view.findViewById(R.id.aaslstv01);
shopname.setText(cursor.getString(shops_shopname_offset));
}
I have tried adding an override of the getDropDownView (code as below). I get the alternating row colors as I want but the dropdown views are blank. However, if I click outside of the selector, then they get populated with data (hence how I managed to get the screen shot, shown above, of what I want). Selection sets the correct Item.
If I remove the return after inflating the layout, then the dropdown views are populated but with data from other rows (however,selection selects the correct item)
public View getDropDownView(int position, View convertview, ViewGroup parent) {
View v = convertview;
determineViewBeingProcessed(v,"GetDDV",position);
if( v == null) {
v = LayoutInflater.from(parent.getContext()).inflate(R.layout.activity_aisle_shop_list_entry, parent, false);
return v;
}
Context context = v.getContext();
TextView shopname = (TextView) v.findViewById(R.id.aasletv01);
shopname.setText(getCursor().getString(shops_shopname_offset));
if(position % 2 == 0) {
v.setBackgroundColor(ContextCompat.getColor(context,R.color.colorlistviewroweven));
} else {
v.setBackgroundColor(ContextCompat.getColor(context,R.color.colorlistviewrowodd));
}
return v;
}
The clues were they I just didn't think hard enough. The issue is with the cursor being in the wrong position because the cursor needs to be obtained via getCursor().
Additionally, the return after the inflate, is premature (this has been commented out).
Adding getCursor().moveToPosition(position); before accessing data from the cursor resolves the problem.
Alternately (perhaps more correctly, comments appreciated on whether or not one method is more correct than the other). Adding:-
Cursor cursor = getCursor();
cursor.moveToPosition(position);
and then replacing subsequent getCursor() with cursor (not mandatory) also works.
So the final code for getDropDownView method could be:-
public View getDropDownView(int position, View convertview, ViewGroup parent) {
View v = convertview;
determineViewBeingProcessed(v,"GetDDV",position);
if( v == null) {
v = LayoutInflater.from(parent.getContext()).inflate(R.layout.activity_aisle_shop_list_entry, parent, false);
//return v;
}
Context context = v.getContext();
Cursor cursor = getCursor();
cursor.moveToPosition(position);
TextView shopname = (TextView) v.findViewById(R.id.aasletv01);
shopname.setText(cursor.getString(shops_shopname_offset));
if(position % 2 == 0) {
v.setBackgroundColor(ContextCompat.getColor(context,R.color.colorlistviewroweven));
} else {
v.setBackgroundColor(ContextCompat.getColor(context,R.color.colorlistviewrowodd));
}
return v;
}
I don't know what's wrong with my code, that it always returns null when I use getParseObject().
I'm using parse.com to save my data, and in one table I used one file as a pointer. I have a Game class that has ImgName as a Pointer<Gallery> to a gallery class.
Now I want to retrieve the ImgName value, so this is what I did:
public Adapter(Context context) {
super(context, new ParseQueryAdapter.QueryFactory<ParseObject>() {
public ParseQuery create() {
ParseQuery query = new ParseQuery("Game");
query.include("ImgName");
return query;
}
});
}
// Customize the layout by overriding getItemView
#Override
public View getItemView(final ParseObject object, View v, ViewGroup parent) {
if (v == null) {
v = View.inflate(getContext(), R.layout.list_item_landing_cards, null);
}
ParseObject gallery = object.getParseObject("ImgName");
String name=gallery.getString("name");
TextView nameTextView = (TextView) v.findViewById(R.id.text);
nameTextView.setText(name);
But I'm getting null all the time.
Any suggestions?
Use this for the re-use issue:
ParseObject gallery = object.getParseObject("ImgName");
if (gallery != null) {
String name=gallery.getString("name");
TextView nameTextView = (TextView) v.findViewById(R.id.text);
nameTextView.setText(name);
} else {
nameTextView.setText(""); // or any other default value you want to set
}
NOTE:
The cell re-use issue is not on Parse. Cell re-use is a general concept used by the ListView. The cells are recycled for performance by Android. We just have to protect it from re-using old values.
In one of my android activity, I've a ListView lvFeedsList.
Each row element in the listView will contain 3 textViews - RSSFeedName, publishDate & feedLength
The contents of the feeds is retrived from a HTTPRsponse.
I'm fetching this response in an AsyncTask.
So, in the doInBackground(), I've send the HTTPRequest & received & parsed the response & prepared the ArrayList containing 3 above mentioned information.
Then inside the doInBackground() only, I'm creating the customized ArrayAdapter for forming the 3 TextViews in row element.
My intetions are to set this adapter on ListView in onPostExecute().
But, when I run the application, the ListView does not display anything.
I tried to debug & it seems like getView() in the ArrayAdapter class is not getting called. (But I'm not sure if this is the reason).
Here is the code, sorry for the length...it seemed necessary.
Activity Code:
public class GenericFeedsActivity extends Activity{
private ListView lvFeedsList;
private ArrayList<FeedsClass> feedList;
protected void onCreate(Bundle savedInstanceState) {
...
lvFeedsList = (ListView) findViewById(R.id.lvFeedsList);
lvFeedsList.setOnItemClickListener(this);
lvFeedsList.setEnabled(false);
...
new AsyncResponseHandler(this).execute();
}
class AsyncResponseHandler extends AsyncTask {
Context context;
FeedListAdapter adapter;
public AsyncResponseHandler(Context c) {
this.context = c;
}
#Override
protected Object doInBackground(Object... params) {
...
/*
* Sending HTTPRequest to a URL & getting list of feeds
* Saving this list of feeds in a ArrayList -feedList, containing elements of type FeedsClass (declared above)
* Below line parses the HTTPResponse XML & stores various information in feedList.
*/
feedList = utils.parseRssResponseXML(in); // Working fine, geeting elements
adapter = new FeedListAdapter(
GenericFeedsActivity.this, feedList);
in.close();
return null;
}
protected void onPostExecute(Object e) {
// Setting Arrayadapter
lvFeedsList.setAdapter(adapter);
lvFeedsList.setEnabled(true);
}
}
}
Adapter Code:
public class FeedListAdapter extends ArrayAdapter {
private Context context;
private ArrayList<FeedsClass> feedList;
public FeedListAdapter(Context c, ArrayList<FeedsClass> data) {
super(c, R.layout.rowlayout);
this.context = c;
this.feedList = data;
}
class ViewHolder {
TextView tvFeedName;
TextView tvFeedPubDate;
TextView tvFeedLength;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
ViewHolder holder = null;
if (row == null) {
LayoutInflater inflator = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
row = inflator.inflate(R.layout.rowlayout, null);
holder = new ViewHolder();
holder.tvFeedName = (TextView) row.findViewById(R.id.tvFeedName);
holder.tvFeedPubDate = (TextView) row.findViewById(R.id.tvFeedPubDate);
holder.tvFeedLength = (TextView) row.findViewById(R.id.tvFeedLength);
row.setTag(holder);
} else {
holder = (ViewHolder) row.getTag();
}
// Getting values of feedName, publishDate & feedLength
String feedName = feedList.get(position).getTitle();
String feedDate = feedList.get(position).getPublishDate();
String feedLength = feedList.get(position).getStreamLength();
holder.tvFeedName.setText(feedName);
holder.tvFeedPubDate.setText(feedDate);
holder.tvFeedLength.setText(feedLength);
}
return row;
}
}
The issue is that you are subclassing ArrayAdapter. This doesn't work because ArrayAdapter internally thinks you do not have any elements in your data; it doesn't just magically know to look in the lvFeedsList variable because the data set it uses is internal.
Instead, in your constructor make sure to call this constructor instead:
Adapter code:
public FeedListAdapter(Context c, ArrayList<FeedsClass> data) {
super(c, R.layout.rowlayout, data); // add 'data'
this.context = c;
this.feedList = data;
}
Which will make everything work correctly.
adapter.notifyDataSetChanged()
could help at the end on of AsyncResponseHandler.onPostExecute(). If not - check whether ArrayList which hold data for adapter is empty or not.
I've been evaluating NOSTRA's Universal-Image-Loader library to asynchronously download images and show them in ListView. So far it works fine except for one problem.
Sometimes Bitmaps from memory cache get attached to wrong ImageViews when the list is being scrolled. After scrolling is stopped, correct images are attached. This situation is quite rare and I couldn't find a 100% way to reproduce it. I shot a video last time it happened.
Here is the ArticleAdapter code, both the UIL config and the bindView() method can be found there.
public class ArticleAdapter extends CursorAdapter {
private LayoutInflater inflater;
private ViewHolder holder;
public ArticleAdapter(Context context, Cursor cursor, boolean autoRequery) {
super(context, cursor, autoRequery);
imageLoader = ImageLoader.getInstance();
DisplayImageOptions options = new DisplayImageOptions.Builder()
.showStubImage(R.drawable.download_progress_thumb)
.cacheInMemory()
.cacheOnDisc()
.imageScaleType(ImageScaleType.IN_SAMPLE_POWER_OF_2)
.build();
ImageLoaderConfiguration configuration = new ImageLoaderConfiguration.Builder(context)
.threadPriority(Thread.NORM_PRIORITY - 2)
.threadPoolSize(4)
.discCache(new UnlimitedDiscCache(Utils.getCacheDirectory(context)))
.defaultDisplayImageOptions(options)
.build();
imageLoader.init(configuration);
titleIndex = cursor.getColumnIndex(Articles.TITLE);
descriptionIndex = cursor.getColumnIndex(Articles.DESCRIPTION);
isUnreadIndex = cursor.getColumnIndex(Articles.IS_UNREAD);
isNewIndex = cursor.getColumnIndex(Articles.IS_NEW);
urlIndex = cursor.getColumnIndex(Articles.URL);
hostIndex = cursor.getColumnIndex(Articles.HOST);
timeIndex = cursor.getColumnIndex(Articles.PUBLISH_TIME);
bkgUnreadArticle = context.getResources().getColor(R.color.list_bkg_unread_article);
bkgReadArticle = context.getResources().getColor(R.color.list_bkg_read_article);
textUnreadTitle = context.getResources().getColor(R.color.list_text_unread_title);
textReadTitle = context.getResources().getColor(R.color.list_text_read_title);
inflater = LayoutInflater.from(context);
}
#Override
public void bindView(View view, Context context, Cursor cursor) {
String date = Utils.format(cursor.getLong(timeIndex), Utils.DATE);
holder = (ViewHolder) view.getTag();
holder.titleView.setText(cursor.getString(titleIndex));
holder.descriptionView.setText(date);
int isNew = cursor.getInt(isNewIndex);
if (isNew == 1)
holder.isNewView.setVisibility(View.VISIBLE);
else
holder.isNewView.setVisibility(View.INVISIBLE);
int isUnread = cursor.getInt(isUnreadIndex);
if (isUnread == 1){
holder.titleView.setTextColor(textUnreadTitle);
holder.rowLayout.setBackgroundColor(bkgUnreadArticle);
} else {
holder.titleView.setTextColor(textReadTitle);
holder.rowLayout.setBackgroundColor(bkgReadArticle);
}
String url = cursor.getString(urlIndex);
String host = cursor.getString(hostIndex);
if (host.equalsIgnoreCase(Consts.HOST_LENTA) || host.equalsIgnoreCase(Consts.HOST_REALTY)) {
holder.thumbView.setVisibility(View.VISIBLE);
imageLoader.displayImage(Utils.makeImageUrl(url, Utils.THUMBNAIL), holder.thumbView);
} else
holder.thumbView.setVisibility(View.GONE);
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
View v = inflater.inflate(R.layout.articlelist_item, null);
ViewHolder holder = new ViewHolder();
holder.titleView = (TextView) v.findViewById(R.id.list_title);
holder.descriptionView = (TextView) v.findViewById(R.id.list_description);
holder.thumbView = (ImageView) v.findViewById(R.id.list_thumb);
holder.isNewView = (TextView) v.findViewById(R.id.list_read_unread);
holder.rowLayout = (LinearLayout) v.findViewById(R.id.list_row);
v.setTag(holder);
return v;
}
}
I would really appreciate any help on this matter.
For ListViews, GridViews and other lists which are used view re-using in its adapters you should call .resetViewBeforeLoading() in DisplayImageOptions to prevent this effect.
Also documentation says:
Init ImageLoader with configuration only once
Do you do it only once? Adapter's constructor isn't good place for it.
UPD: Sorry, my answer isn't useful. .resetViewBeforeLoading() doesn't help because you use .showStubImage(...). So you should have correct UIL work but you don't. And it's very strange.
I had this problem on a regular basis, even though I was only initiating the ImageLoader once, I wasn't doing it only when I needed it (in the adaptor), after I changed the init() part in Application class it worked brilliantly. I haven't even had to use restartViewOnLoading() or setStubImage(). Here's the code if necessary.
import android.content.Context;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
public class Application extends android.app.Application {
private static Context mContext;
#Override
public void onCreate() {
super.onCreate();
mContext = getApplicationContext();
DisplayImageOptions imgOptions = new DisplayImageOptions.Builder()
.cacheInMemory(true)
.showImageOnLoading(R.drawable.default_picture)
.build();
ImageLoaderConfiguration imgConfig = new ImageLoaderConfiguration.Builder(mContext)
.defaultDisplayImageOptions(imgOptions)
.build();
ImageLoader.getInstance().init(imgConfig);
}
public static Context getAppContext(){
return mContext;
}
}
EDIT: You can check this conversation here for a deeper understanding of the issue.
Basically there are 3 solutions
1) Set android:layout_width and android:layout_height parameters for ImageViews in dips ('wrap_content' and 'match_parent' are not acceptable)
2) Call ImageLoader after ImageView was drawn (in imageView.post(...):
imageView.post(new Runnable() {
#Override
public void run() {
imageLoader.displayImage(imageUri, imageView);
}
});
3) Pass ImageViewAware (instead of ImageView) which doesn't consider actual view size:
Intead of:
imageLoader.displayImage(imageUri, imageView);
do following:
ImageAware imageAware = new ImageViewAware(imageView, false)
imageLoader.displayImage(imageUri, imageAware);
Just see how to set Holders because I think you have written faulty logic inside your Adapter thats why it is repeating views.
There is also Custom Cursor Adapter with Holder and Get View & BindView discussion.
Add this line in your code ::
holder.thumbView.setTag(Utils.makeImageUrl(url, Utils.THUMBNAIL).get(position));
imageLoader.displayImage(Utils.makeImageUrl(url, Utils.THUMBNAIL), view_holder.image);
I have same problem and fixed it. It is not because Universal-Image-Loader library. It is because you use holder in wrong logic to load image.
Try to replace
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
View v = inflater.inflate(R.layout.articlelist_item, null);
ViewHolder holder = new ViewHolder();
holder.titleView = (TextView) v.findViewById(R.id.list_title);
holder.descriptionView = (TextView) v.findViewById(R.id.list_description);
holder.thumbView = (ImageView) v.findViewById(R.id.list_thumb);
holder.isNewView = (TextView) v.findViewById(R.id.list_read_unread);
holder.rowLayout = (LinearLayout) v.findViewById(R.id.list_row);
v.setTag(holder);
return v;
}
With
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
View v = inflater.inflate(R.layout.articlelist_item, null);
ViewHolder holder = new ViewHolder();
holder.titleView = (TextView) v.findViewById(R.id.list_title);
holder.descriptionView = (TextView) v.findViewById(R.id.list_description);
ImageView thumbView = (ImageView) v.findViewById(R.id.list_thumb);
imageLoader.displayImage("Your image URL", thumbView);
holder.isNewView = (TextView) v.findViewById(R.id.list_read_unread);
holder.rowLayout = (LinearLayout) v.findViewById(R.id.list_row);
v.setTag(holder);
return v;
}
And remember to remove imageloader in your bindView function
So I had my listview working perfectly then I decided to add a context menu. As soon as I did that whenever I normal clicked an item in my listview, the entire list gets inverted on the first click. Subsequent clicks do nothing to the order, but when the first item is de-selected again the list returns to normal. When I take out the context menu logic that I added, the list view problem does not go away.
I've attached a debugger and the elements in my list adapter are never reordered, and the ListView itself is never set to reverse with .setStackFromBottom()
Here is my onClick listener registered to handle the click events of the list view items:
public void onClick(View v) {
ViewHolder holder = (ViewHolder) v.getTag();
CheckBox b = holder.box;
Boolean check = b.isChecked();
b.setChecked(!check);
if (!check) {
mChecked.add(holder.fu);
if (mChecked.size() == 1) {
buttonLayout.setVisibility(View.VISIBLE);
}
} else {
mChecked.remove(holder.fu);
if (mChecked.size() == 0) {
buttonLayout.setVisibility(View.GONE);
}
}
}
The viewholder class just holds references to objects I use in the listview for optimizations. I cannot figure out why this is causing my list to invert when displayed, I've tried moving the listener to a different view in the layout, I've tried re-writing the listener, nothing seems to work! Any advice would be appreciated.
Edit: here is the code for the view holder
/** Class to provide a holder for ListViews. Used for optimization */
private class ViewHolder {
TextView date;
TextView gallons;
TextView cost;
TextView cpg;
TextView mpg;
CheckBox box;
FillUp fu;
}
as well as the adapter:
public class FillUpAdapter extends BaseAdapter {
ArrayList<FillUp> mElements;
ArrayList<FillUp> mChecked;
Context mContext;
public FillUpAdapter(Context c, ArrayList<FillUp> data) {
mContext = c;
mElements = data;
mChecked = new ArrayList<FillUp>();
}
public void clearChecked() {
mChecked.clear();
}
public ArrayList<FillUp> getChecked() {
return mChecked;
}
public boolean remove(FillUp f) {
mChecked.remove(f);
return mElements.remove(f);
}
#Override
public int getCount() {
return mElements.size();
}
#Override
public Object getItem(int arg0) {
return mElements.get(arg0);
}
#Override
public long getItemId(int arg0) {
return mElements.get(arg0).getId();
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
LinearLayout layout;
ViewHolder holder;
if (convertView != null) {
layout = (LinearLayout) convertView;
holder = (ViewHolder) layout.getTag();
} else {
layout = (LinearLayout) LayoutInflater.from(mContext).inflate(
R.layout.fillup_list_item, parent, false);
holder = new ViewHolder();
holder.cost = (TextView) layout
.findViewById(R.id.fillUpListTotalValue);
holder.cpg = (TextView) layout
.findViewById(R.id.fillUpListCostPerGal);
holder.gallons = (TextView) layout
.findViewById(R.id.fillUpListGalValue);
holder.mpg = (TextView) layout
.findViewById(R.id.fillUpMPGText);
holder.date = (TextView) layout
.findViewById(R.id.fillUpListDate);
holder.box = (CheckBox) layout
.findViewById(R.id.fillUpListCheckBox);
holder.fu = (FillUp) getItem(position);
layout.setTag(holder);
}
holder.date.setText(holder.fu.getDate());
holder.gallons.setText(holder.fu.getGallonsText());
holder.cpg.setText(holder.fu.getCostText());
holder.cost.setText(holder.fu.getTotalText());
holder.mpg.setText(String.format("%03.1f MPG",holder.fu.getMPG()));
if (convertView != null) {
holder.box.setChecked(mChecked.contains(holder.fu));
}
layout.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
ViewHolder holder = (ViewHolder) v.getTag();
CheckBox b = holder.box;
Boolean check = b.isChecked();
b.setChecked(!check);
if (!check) {
mChecked.add(holder.fu);
if (mChecked.size() == 1) {
buttonLayout.setVisibility(View.VISIBLE);
}
} else {
mChecked.remove(holder.fu);
if (mChecked.size() == 0) {
buttonLayout.setVisibility(View.GONE);
}
}
}
});
return layout;
}
}
UPDATE:
Ok, so I've narrowed it down to the visibility change on the buttonLayout view, which is a linear layout of buttons on the bottom of the Activity's layout, underneath the ListView. Whenever I change that view's visibility to View.VISIBLE (which happens when the first item is checked) the list's order is reversed. The order is restored when the view's visibility is set to View.GONE
I have no idea what would cause that though :(
After narrowing the scope a bit more, I discovered the problem was not the changing of the visibility of my button bar, but actually the passing around of FillUp objects in holder.fu of my ViewHolder class. By changing that to instead reference the adapter's getItem(position) method, everything seemed to work out. Quite an odd bug, since the adapter itself was not having the order of the elements changed, but passing around a reference to the object made it very unhappy.
If your listview background color changes when you click on it, I think it is about your theme. Just play with the cache color parameters of your listview, here is an example:
<ListView
android:layout_width="fill_parent"
android:id="#android:id/list"
android:scrollingCache="true"
android:persistentDrawingCache="all"
android:cacheColorHint="#0000"
android:layout_height="wrap_content"
android:fastScrollEnabled="true"
android:stackFromBottom="true"
android:smoothScrollbar="true"
android:paddingTop="115dip">
</ListView>