I'm new to android programming, and I've been reading a lot about it lately. One of the features of ListView, if I understood it right, is that it recycle views and just replaces it with new data when an item is off the screen.
And just a few minutes ago, I was reading up about endless scrolling, and RecyclerView has been one of the popular choices to implement such a feature. So I looked up RecyclerView, and in this video, it is mentioned that RecyclerView recycles a view automatically to reuse it for new data (as a way to contrast its difference with ListView).
Did I misunderstand ListView about its recycling mechanism? Or if it does recycle, how do you actually implement (or how do you know you are implementing) it?
RecyclerView does recycling automatically. In order to make ListView recycle items you will need to do this modification inside of adapter class.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
//brand new
convertView = LayoutInflater.from(mContext).inflate(R.layout.days_list_item, null);
holder = new ViewHolder();
// below is variables that will be different in your case
holder.numberOfDays = (TextView) convertView.findViewById(R.id.eventDays);
holder.sinceOrUntil = (TextView) convertView.findViewById(R.id.eventType);
holder.eventTitle = (TextView) convertView.findViewById(R.id.eventTitle);
holder.daysText = (TextView) convertView.findViewById(R.id.DaysText);
convertView.setTag(holder);
}
else {
//reusing item
holder = (ViewHolder) convertView.getTag();
}
// rest of the code
}
For more details refer to this link.
Related
I'm using a custom list view adapter with a) view recycling and b) a view holder object.
I also successfully implemented an asynchronous image loader class to load bit maps to the list view for a smoother scrolling experience.
The problem now looks like the recycling, and correct if I'm wrong, but from a visual stand point it looks like the recycling is displaying previously loaded bit maps before the new ones are loaded such that there is a brief but noticeable flicker between the old image being replaced by the new one.
I hadn't noticed this effect before with the text views. Is this a recycling problem? Is there a way to scrape the old bit maps when the list item exits the visible screen area?
And yes, I am aware there are 3rd party libraries that account for all these effects. If I can't solve this problem by hand, then I will look into a library.
This is my list adapter
// Get the data item for this position
SongObject songObject = list.get(position);
// view lookup cache stored in tag
ViewHolder viewHolder;
// Check if an existing view is being reused, otherwise inflate the view
if (convertView == null) {
viewHolder = new ViewHolder();
LayoutInflater inflater = LayoutInflater.from(getContext());
convertView = inflater.inflate(R.layout.list_view_item, parent, false);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
// Set view holder references
viewHolder.album = (TextView) convertView.findViewById(R.id.album);
viewHolder.artist = (TextView) convertView.findViewById(R.id.artist);
viewHolder.title = (TextView) convertView.findViewById(R.id.title);
viewHolder.albumArt = (ImageView) convertView.findViewById(R.id.album_art);
viewHolder.duration = (TextView) convertView.findViewById(R.id.duration);
// Set values to referenced view objects
viewHolder.album.setText(songObject.album);
viewHolder.artist.setText(songObject.artist);
viewHolder.title.setText(songObject.title);
viewHolder.duration.setText(FormatTime(songObject.duration));
// Load album art asynchronously for smoother scrolling experience
new ImageLoader(viewHolder.albumArt).execute(songObject.albumArtURI);
// Return the converted view
return convertView;
Do you use recyclerView? Can you add code of recyclerViewAdapter implementation?
onBindViewHolder is a place where you should 'tune' you previously used (or not used) viewHolder before giving it to user
I have learned earlier about great approach to increase performance - Holder Pattern. This is good idea to speed up UI and animation.
It is clearly why and how to use it.
I have used it a lot , but now I am little bit confused about this.
When getView method is called it has three arguments one is converView. As I undertand it is previously inflated view of list item, so the are some questions about this.
If it is previously inflated view, why not just to use it, return it from method, of course check to null before.
How does this implemented,listview class has private array or another data structure that holds all inflated views ?
Why this feature is not implemented in adapters ?
Thanks in advance.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder =(ViewHolder) convertView.getTag();
}
If you would just use the convertView, you would need to get hold of your views with findViewById(). This is exactly what the ViewHolder pattern is trying to avoid. findViewById() is a surprisingly expensive method and can slow down your app, especially if you constantly call it when scrolling through lists.
Listviews reuse the layouts of the child items, to avoid having to inflate the same views over and over again.
Most adapters were already available to developers before people came up with the ViewHolder pattern. The latest new list view, RecyclerView, has an adapter that enforces the use of the ViewHolder pattern.
You can't call ViewHolder holder =(ViewHolder) convertView.getTag(); at the first line of getView. Because convertView could be null. Try again like this:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
ViewHoldler holder = null;
if (convertView == null) {
convertView = LayoutInflater.from(ctx).inflate(
R.layout.frag_home_gridview_item, null, false);
holder = new ViewHoldler();
holder.iv = (ImageView) convertView
.findViewById(R.id.gridview_item_label);
holder.tv = (TextView) convertView
.findViewById(R.id.gridview_item_name);
convertView.setTag(holder);
} else {
holder = (ViewHoldler) convertView.getTag();
}
holder.tv.setText(getItem(position));
holder.iv.setImageResource(this.ids[position]);
return convertView;
}
private class ViewHoldler {
ImageView iv;
TextView tv;
}
Because you most likely are not using same views. Say you have a row which has a TextView in it. The convertview is one of the recycled views and it's textview may be displaying different information that it should be.
It does keep as many views as there are visible, once you scroll down the top view get's recycled and you come back to answer 1.
I don't understand, your code is from the BaseAdapter class.
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.
I have a list of items. Each of them has a set of data displayed with TextViews. This data remains mostly unchanged. But I have a distance field, which I would like to update whenever I get new lock from location provider.
The question is: Should I just update my data and call notifyDataSetChanged() on my Adapter or is there a more efficient way?
Seems very expensive to reload all the lists (I have several of them in a ViewPager) just because one TextView in each list item needs to be updated.
Here is my getView() from my adapter. It might help:
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.eventrow, parent, false);
// Creates a ViewHolder and store references to the two children views
// we want to bind data to.
holder = new ViewHolder();
holder.title = (TextView) convertView.findViewById(R.id.eventTitle);
holder.distance = (TextView) convertView.findViewById(R.id.eventDistance);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.title.setText(((EventItem) getItem(position)).getTitle());
holder.distance.setText(String.valueOf(((EventItem) getItem(position)).getDistance()));
return convertView;
}
I also thought about directly referencing the holder.distance but it seems like a bad idea to do it outside getView().
The correct way to modify your data is to change your list item and then call notifyDataSetChanged().
The only alternative that comes to my mind is to set again the adapter on the list view which is way more expensive. There are no other ways.
So the answer is: you HAVE to go through notifyDataSetChanged().
I have a straight forward BaseAdapter for my ListView. It downloads a JSON feed and displays the data in the rows. There is a ViewHolder which contains the views and a data object called "Story". Everything works just fine.
However, after some scrolling of longer lists, I notice two things.
1) My log shows that the adapter is reloading the feed when scrolling further down. This is strange, as I put the whole JSON array into a variable, so why does it have to reload?
2) More importantly, after some scrolling back and forth, the rows contain the wrong "Story" objects. Here are the relevant parts of the getView routine:
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
Story story = stories.get(position);
if (convertView == null) {
//create holder
holder = new ViewHolder();
convertView = inflator.inflate(R.layout.story_list_item, parent, false);
holder.titleView = (TextView) convertView.findViewById(R.id.story_list_title);
holder.dateView = (TextView) convertView.findViewById(R.id.story_list_date);
holder.story = story;
holder.imageView = (ImageView) convertView.findViewById(R.id.story_list_image);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
// configure the view
holder.titleView.setText(story.title);
return convertView;
}
Simple enough. Now the strange thing is that I can fix the problem by eliminating the if statement if (convertView == null) (and, I presume, eliminating the row recycling as well).
But will I not run into memory problems this way? Why does the plain vanilla version not work?
Thanks for your help.
Regards,
S
You are aware that you're only assigning
holder.story = story
when convertView == null ? Consider moving holder.story = story to just after your convertView if-case and it should work a lot better. Btw, do you even need to store the "story" inside your view holder? Typically that pattern should only be used to store Views and view state information, not the data of the actual position.