i have a big problem with my ListViewAdapter.
My listview shows 2 entrys at the same time. Each entry should get a different picture from my server.
The first and the second entrys working fine, but if i'm scrolling down, the next entrys will have the same pictures.
My Code looks like this:
if (viewHolder.imgPic != null) {
String strUrl = mainUrl+list.get(position).getUrl();
new ImageDownload(viewHolder.imgPic).execute(strUrl);
}
I'm checking the view and just doing it, if it's null.
Can someone help me?
Thanks
from your question I can assume that you don't know about the ListView recycling mechanisem
basically, view that that not visible anymore (after user scrolled it away from sight) it been recycled to displayed new item that need to shown. that's the convertView parameter at the getView() method...
probably you are see the same image because the recycled view stays with the same image..
also there is the issue of the asynchronous task (your ImageDownload class) that can finish it execute when the original item that started the request already been recycled.
I recommend you to "dig" as dipper as you can to understand all about ListView - this is one of the most complex and important UI component. reading the post I linked you is a good start.
also this video is very important:
http://www.youtube.com/watch?v=wDBM6wVEO70
Here is my GetView:
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
View view = null;
if(rowResourceId!=R.layout.search_empty_list) {
if (convertView == null) {
LayoutInflater inflator = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflator.inflate(rowResourceId, null);
final ViewHolder viewHolder = new ViewHolder();
viewHolder.textName = (TextView) view.findViewById(R.id.textView1);
viewHolder.imgPic = (ImageView) view.findViewById(R.id.imgPic);
if (viewHolder.imgPic != null) {
String strUrl = mainUrl+list.get(position).getUrl();
new ImageDownload(viewHolder.imgPic).execute(strUrl);
}
view.setTag(viewHolder);
} else {
view = convertView;
}
ViewHolder holder = (ViewHolder) view.getTag();
holder.textName.setText(list.get(position).getName());
} else {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(rowResourceId, parent, false);
TextView textView1 = (TextView) view.findViewById(R.id.textView1);
textView1.setText(list.get(0).getName());
}
return view;
}
Related
I have seen similar questions to that, but mine is a little bit different:
I have a listview and each row has an imageview that loads a picture from url. Here is my adapter:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder=null;
if(convertView==null){
LayoutInflater inflater = (LayoutInflater) this.getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.bestof_list_row, parent, false);
holder = new ViewHolder();
holder.textviewRowNumber = (TextView) convertView.findViewById(R.id.textview_row_number);
holder.imageviewUserPic = (RemoteImageView) convertView.findViewById(R.id.imageview_user_profile_pic);
holder.textviewUsername = (TextView) convertView.findViewById(R.id.textview_username);
holder.textviewSubtextBold = (TextView) convertView.findViewById(R.id.textview_subtext_bold);
holder.textviewSubtextNotBold = (TextView) convertView.findViewById(R.id.textview_subtext_not_bold);
}else{
holder = (ViewHolder) convertView.getTag();
}
BestOfSubTabListItem item = getItem(position);
holder.textviewRowNumber.setText(String.valueOf(position+1));
holder.imageviewUserPic.setRemoteURI(item.getUserAvatarURL());
holder.imageviewUserPic.loadImage();
holder.textviewUsername.setText(item.getUsername());
if(feedType == SocialFeedType.hero2){
holder.textviewSubtextBold.setVisibility(View.GONE);
holder.textviewSubtextNotBold.setVisibility(View.GONE);
}else{
holder.textviewSubtextBold.setVisibility(View.VISIBLE);
holder.textviewSubtextNotBold.setVisibility(View.VISIBLE);
holder.textviewSubtextBold.setText(item.getSubtextBold());
holder.textviewSubtextNotBold.setText(item.getSubTextNotBold());
}
convertView.setTag(holder);
return convertView;
}
static class ViewHolder{
public TextView textviewRowNumber;
public RemoteImageView imageviewUserPic;
public TextView textviewUsername;
public TextView textviewSubtextBold;
public TextView textviewSubtextNotBold;
}
When i scroll the listview fast, the images in imageviews change, they are put to wrong positions. One solution is removing the
if(convertview==null)
but this time, listview does not scroll smoothly. Can anyone help me to fix this problem?
Thanks
When you're using convertviews, it means that listview will try to recycle views. In your case it means that the same view will be used in different positions in listview, and you're actually trying to load different images into same imageview. To prevent this, try using Picasso or Volley, they have this view reusing thing sorted out.
If you want to do it manually, you have to watch when your view goes off the screen and cancel image pulling request to make that imageview available to handle proper image for that position.
http://square.github.io/picasso/
I am having an unclear issue concerning the recycling of views in a getView method of a custom array adapter.
I understand that elements are reused, but how do I know exact what to implement in the first part of the if statement, and what in the second?
Right now I am having following code. I came to this question due to dropping the code in the second part of the statement which results in a list of the first 9 elements, which are repeated numberous times instead of all elements. I didn't really know what is causing this exactly...
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
if (row == null) {
LayoutInflater inflater = ((Activity) context).getLayoutInflater();
row = inflater.inflate(layoutResourceId, parent, false);
title = getItem(position).getTitle();
size = calculateFileSize(position);
txtTitle = (TextView) row.findViewById(R.id.txtTitle);
tvFileSize = (TextView) row.findViewById(R.id.tvFileSize);
txtTitle.setText(title);
tvFileSize.setText(size);
} else {
title = getItem(position).getTitle();
size = calculateFileSize(position);
txtTitle = (TextView) row.findViewById(R.id.txtTitle);
tvFileSize = (TextView) row.findViewById(R.id.tvFileSize);
txtTitle.setText(title);
tvFileSize.setText(size);
}
return row;
}
It's easy. The first time no row is created, so you have to inflate them. Afterwards, the Android os may decide to recycle the views that you already inflated and that are not visible anymore. Those are already inflated and passed into the convertView parameter, so all you have to do is to arrange it to show the new current item, for example placing the right values into the various text fields.
In short, in the first part you should perform the inflation AND fill the values, in the second if (if convertView != null) you should only overwrite the field because, given the view has been recycled, the textviews contain the values of the old item.
This post and this are good starting points
I understand that elements are reused, but how do I know exact what to implement in the first part of the if statement, and what in the second?
The organization is quite simple once you get the hang of it:
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
/* This is where you initialize new rows, by:
* - Inflating the layout,
* - Instantiating the ViewHolder,
* - And defining any characteristics that are consistent for every row */
} else {
/* Fetch data already in the row layout,
* primarily you only use this to get a copy of the ViewHolder */
}
/* Set the data that changes in each row, like `title` and `size`
* This is where you give rows there unique values. */
return convertView;
}
For detailed explanations of how ListView's RecycleBin works and why ViewHolders are important watch Turbo Charge your UI, a Google I/O presentation by Android's lead ListView programmers.
You want to create a ViewHolder class in your MainActivity. Something like
static class ViewHolder
{
TextView tv1;
TextView tv2;
}
then in your getView, the first time you get your Views from your xml in the if and reuse them after that in the else
View rowView = convertView;
if (rowView == null)
{
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
rowView = inflater.inflate(R.layout.layout_name_to_inflate, parent, false);
holder = new ViewHolder();
holder.tv1= (TextView) rowView.findViewById(R.id.textView1);
holder.tv2 = (RadioGroup) rowView.findViewById(R.id.textView2);
rowView.setTag(holder);
}
else
{
holder = (ViewHolder) rowView.getTag();
}
I would recommend that you use the View holder and convertview pattern to create your listView as it will be more efficient.Here is a good explanation of how it works with a re-use strategy. This will answer your question on how re-cycling works. If you want to refer to a code sample, I have it on GitHub.
Hope this helps.
The last part of the question I really couldn't grasp without a picture of the effect but for the first part "what to implement in the first part of the if statement, and what in the second" I think I've found the this implementation very common.
You would find the view references first and store them to a static class ViewHolder which then you attach to the tag of the new inflated view. As the listview recycles the views and a convertView is passed getView you get the ViewHolder from the convertView's tag so you don't have to find the references again (which greatly improves performance) and update the view data with that of your object at the position given.
Technically you don't care what position the view was since all you care for is the references to the views you need to update which are held within it's ViewHolder.
#Override
public View getView(int position, View convertView, ViewGroup container) {
ViewHolder holder;
Store store = getItem(position);
if (convertView == null) {
convertView = mLayoutInflater.inflate(R.layout.item_store, null);
// create a holder to store references
holder = new ViewHolder();
// find references and store in holder
ViewGroup logoPhoneLayout = (ViewGroup) convertView
.findViewById(R.id.logophonelayout);
ViewGroup addressLayout = (ViewGroup) convertView
.findViewById(R.id.addresslayout);
holder.image = (ImageView) logoPhoneLayout
.findViewById(R.id.image1);
holder.phone = (TextView) logoPhoneLayout
.findViewById(R.id.textview1);
holder.address = (TextView) addressLayout
.findViewById(R.id.textview1);
// store holder in views tag
convertView.setTag(holder);
} else {
// Retrieve holder from view
holder = (ViewHolder) convertView.getTag();
}
// fill in view with our store (at this position)
holder.phone.setText(store.phone);
holder.address.setText(store.getFullAddress());
UrlImageViewHelper.setUrlDrawable(holder.image, store.storeLogoURL,
R.drawable.no_image);
return convertView;
}
private static class ViewHolder {
ImageView image;
TextView phone;
TextView address;
}
I have a ListView that I put through a complex bit of coding. The list changes often with different types of data that require different views. On rare occasion, I'll end up with 1 view being reused by Android for a row that's supposed to look different. It seems to only happen when the data being displayed radically changes. I was hoping there was a way to programmatically wipe the ListView's memory clean. Is this possible?
Here is the beginning of my getView:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
SearchHolder holder = null;
int type = getItemViewType(position);
if (null == convertView) {
holder = new SearchHolder();
if (type == SEARCH_TYPE_FREETEXT) {
convertView = mInflater.inflate(R.layout.layout_search_item_freetext, null);
holder.txtText = (TextView) convertView.findViewById(R.id.search_itemname);
holder.vHeaderWrapper = (LinearLayout) convertView.findViewById(R.id.search_headerwrapper);
holder.txtHeader = (TextView) convertView.findViewById(R.id.search_header);
}
else {
if (items.get(position).mData == null) {
convertView = mInflater.inflate(R.layout.layout_loadmoreresults_white, null);
}
else {
convertView = mInflater.inflate(R.layout.layout_search_item, null);
holder.txtText = (TextView) convertView.findViewById(R.id.search_itemname);
holder.vHeaderWrapper = (LinearLayout) convertView.findViewById(R.id.search_headerwrapper);
holder.txtHeader = (TextView) convertView.findViewById(R.id.search_header);
}
}
convertView.setTag(holder);
}
else {
holder = (SearchHolder)convertView.getTag();
}
...
ListView does something called "recycling" when you scroll through a list, and what you will need to do is override the getView() method to update the individual listView item that is being recycled. By default android does not clear out these views. Check out the following link on ListView recycling:
http://mobile.cs.fsu.edu/the-nuance-of-android-listview-recycling-for-n00bs/
Without code it is hard to tell if you already know about this or not, but this is the cause of such problems in my experience.
If you use more than one layout for your list items then I suggest to inflate the appropriate layout from the xml every time in the getView() method.
Currently, I have a custom list adapter that has some logic that hide/show a certain ImageView in a row depending on a variable.
Initially, the logic works when the app first launches. (ImageView is hidden/shown accordingly).
But once I scroll the screen up and down, eventually, all the row's ImageView is hidden forever.
Does anyone know how to solve this problem?
here is my adapter:
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
PostHolder holder = null;
if(row == null)
{
LayoutInflater inflater = ((Activity)context).getLayoutInflater();
row = inflater.inflate(layoutResourceId, parent, false);
holder = new PostHolder();
holder.postThumb = (ImageView)row.findViewById(R.id.post_Thumb);
holder.postComments = (TextView)row.findViewById(R.id.post_comments);
holder.postInfo = (TextView)row.findViewById(R.id.item_subtitle);
holder.postScore = (TextView)row.findViewById(R.id.post_score);
holder.postTitle = (TextView)row.findViewById(R.id.item_title);
holder.postThumbHolder = (LinearLayout)row.findViewById(R.id.post_Thumb_holder);
row.setTag(holder);
}
else
{
holder = (PostHolder)row.getTag();
}
HashMap<String, String> post = data.get(position);
if(post.get("thumbnail").equals("default") || post.get("thumbnail").equals("self")){
holder.postThumbHolder.setVisibility(View.GONE);
}
holder.postComments.setText(post.get("comments"));
holder.postInfo.setText(post.get("info"));
holder.postScore.setText(post.get("score"));
holder.postTitle.setText(post.get("title"));
return row;
}
static class PostHolder
{
LinearLayout postThumbHolder;
ImageView postThumb;
TextView postComments;
TextView postScore;
TextView postTitle;
TextView postInfo;
}
It has to do with how android makes the background of the list items into bitmaps as soon as you start scrolling. To make sure your image views are visible after scrolling you must also set that property everytime the adapters getView is called do this in the else part of your variable check
if(post.get("thumbnail").equals("default") || post.get("thumbnail").equals("self")){
holder.postThumbHolder.setVisibility(View.GONE);
} else {
holder.postThumbHolder.setVisibility(View.VISIBLE);
}
Set visibility View.VISIBLE to postThumbHolder somewhere. In the else block of your
if (post.get...
clause.
I've a custom adapter for my ListView where I send to it a List, and if the position is in the list then the imageview (that is in the custom row) change its src to another.. Here is the GetView method:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.start_row, null); // line
// 47
holder = new ViewHolder();
holder.tv_SuraName = (TextView) convertView.findViewById(R.id.Start_Name);
holder.tv_SuraName.setTypeface(Font);
holder.tv_PageNumber = (TextView) convertView.findViewById(R.id.Start_Numbering);
holder.im_Audio = (ImageView) convertView.findViewById(R.id.Start_ImageView);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.tv_SuraName.setText(SuraList_SuraNamesCode[position]);
holder.tv_PageNumber.setText(Integer.toString(PageNumber[position]));
holder.im_Audio.setOnClickListener(new imageViewClickListener(position));
if (TilawaAvailable.contains(position))
holder.im_Audio.setImageResource(R.drawable.quran_list_audioavailable);
return convertView;
}
I send a List with 1 position.If I scroll the ListView slowly, this works good and only 1 imageview gets changed as it should be. But if I scroll fast, other imagesviews that are close to the correct position also gets changed!
Can anyone tell me why?
You don't ever set the imageResource to something else if position isn't contained in your list. When the view with the custom image leaves the screen it is probably getting placed at a lower position in the list and getting reused.
Try changing this:
if (TilawaAvailable.contains(position))
holder.im_Audio.setImageResource(R.drawable.quran_list_audioavailable);
To this:
if (TilawaAvailable.contains(position))
holder.im_Audio.setImageResource(R.drawable.quran_list_audioavailable);
else
holder.im_audio.setImageResource(r.drawable.SOME_THING_ELSE);