Recycling views in custom array adapter: how exactly is it handled? - android

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;
}

Related

Error in ArrayAdapter - layout inflator thows error

Am getting the following warning in Eclipse:
"Unconditional layout inflation from view adapter: Should use View Holder pattern (use recycled view passed into this method as the second parameter) for smoother scrolling"
The code which i had used is:
class myadapter extends ArrayAdapter<String>
{
Context context;
int[] images;
String[] mytitle;
String[] mydescp;
myadapter(Context c, String[] tittle, int[] imgs, String[] desc)
{
super(c, R.layout.single_row, R.id.listView1, tittle);
this.context=c;
this.images=imgs;
this.mytitle= tittle;
this.mydescp=desc;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
LayoutInflater inflator = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View row = inflator.inflate(R.layout.single_row, parent, false);
ImageView myImage = (ImageView) row.findViewById(R.id.imageView1);
TextView myText = (TextView) row.findViewById(R.id.textView1);
TextView mydesc = (TextView) row.findViewById(R.id.textView2);
myImage.setImageResource(images[position]);
myText.setText(mytitle[position]);
mydesc.setText(mydescp[position]);
return row;
}
}
Am getting warning in the line : View row = inflator.inflate(R.layout.single_row, parent, false);
And it causes my android application to Force Close... What can i do it now??
Any Suggestions???
You need to recycle your views.What android as a system cares about is only the items that are visible.So you have to recycle the row items which are out of focus to be re-used for the newitems.
Or else imagine the amount of caching involved.
#Override
public View getView(int position, View row, ViewGroup parent) {
// TODO Auto-generated method stub
if(row==null){
LayoutInflater inflator = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
row = inflator.inflate(R.layout.single_row, parent, false);
}
ImageView myImage = (ImageView) row.findViewById(R.id.imageView1);
TextView myText = (TextView) row.findViewById(R.id.textView1);
TextView mydesc = (TextView) row.findViewById(R.id.textView2);
myImage.setImageResource(images[position]);
myText.setText(mytitle[position]);
mydesc.setText(mydescp[position]);
return row;
}
You should re-use the view instead of inflating again and again. This brings down performance.
From your code,
View row = inflator.inflate(R.layout.single_row, parent, false);
this will inflate everytime when you scroll. To maximize the performance, use it like
//re-use
if (row == null)
{
inflate code here
}
else
{
you already have a view `row`, just use it.
}
You can see, for the first time row will be null & it will inflate and store it in View row. But, from the next time, it's not going to inflate again and again instead it will use the View row. (Re-use)
"Unconditional layout inflation from view adapter: Should use View Holder pattern (use recycled view passed into this method as the second parameter) for smoother scrolling"
It's not the error it's just the warning for asking you to use ViewHolder Pattern. Let me explain you why it's important.
Without ViewHolder Pattern :
The first time it was loaded, convertView is null. We’ll have to inflate our list item layout and find the TextView via findViewById().
The second time it was loaded, convertView is not null, good! We don’t have to inflate it again. But we’ll use findViewById() again.
The following times it was loaded, convertView is definitely not null. But findViewById() is constantly called, it will work but, it slows down the performance especially if you have lots of items and Views in your ListView.
With the ViewHolder Design Pattern :
The first time it was loaded, convertView is null. We’ll have to inflate our list item layout, instantiate the ViewHolder, find the TextView via findViewById() and assign it to the ViewHolder, and set the ViewHolder as tag of convertView.
The second time it was loaded, convertView is not null, good! We don’t have to inflate it again. And here’s the sweet thing, we won’t have to call findViewById() since we can now access the TextView via its ViewHolder.
The following time it was loaded, convertView is definitely not null. The findViewById() is never called again, and that makes our smooth ListView scrolling.
Why to use?
Your code might call findViewById() frequently during the scrolling of ListView, which can slow down performance. Even when the Adapter returns an inflated view for recycling, you still need to look up the elements and update them. A way around repeated use of findViewById() is to use the view holder design pattern.
So, what is ViewHolder?
A ViewHolder object stores each of the component views inside the tag field of the Layout, so you can immediately access them without the need to look them up repeatedly. First, you need to create a class to hold your exact set of views.
How to use?
Make a separate class as ViewHolder & declare what you use like EditText,TextView etc..
static class ViewHolder {
TextView text;
TextView timestamp;
ImageView icon;
ProgressBar progress;
int position;
}
Then populate the ViewHolder and store it inside the layout.
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) { // if convertView is null
convertView = mInflater.inflate(R.layout.mylayout,
parent, false);
holder = new ViewHolder();
// initialize views
convertView.setTag(holder); // set tag on view
} else {
holder = (ViewHolder) convertView.getTag();
// if not null get tag
// no need to initialize
}
//update views here
return convertView;
}
Source :
Making ListView Scrolling Smooth from Android documentation
Android ViewHolder Pattern example
Hi Vinesh Senthilvel ,
Don't worry
Use my code below , It will definetely solve your problem,
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
LayoutInflater inflator = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View row = inflator.inflate(R.layout.single_row, null, false);
ImageView myImage = (ImageView) row.findViewById(R.id.imageView1);
TextView myText = (TextView) row.findViewById(R.id.textView1);
TextView mydesc = (TextView) row.findViewById(R.id.textView2);
myImage.setImageResource(images[position]);
myText.setText(mytitle[position]);
mydesc.setText(mydescp[position]);
return row;
}
If still problem persists then post logcat exception stack trace ,I will help you
There is a another approach , You just have to import android.view.LayoutInflater; and take the context of parent (ViewGroup) - parent.getContext() ,It will work
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
View v = LayoutInflater.from(parent.getContext()).inflate(
R.layout.single_row, parent, false);
You have to use this code
View row = convertView;
before this line,
View row = inflator.inflate(R.layout.single_row, parent, false);
Hope it works..

Large listview issues

I have a huge listview , with more then 100,000 items. What I did now..
PostPaid accountItem = (PostPaid)arr.get(position);
ViewHolder holder;
if (convertView == null) {
LayoutInflater mInflater = (LayoutInflater)
context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
convertView = mInflater.inflate(R.layout.list_support_item20, null);
holder = new ViewHolder();
holder.text = (TextView) convertView.findViewById(R.id.accountType);
convertView.setTag(holder);
}
else {
holder = (ViewHolder) convertView.getTag();
}
holder.text.setText(accountItem.getTitle());
What I did originally
LayoutInflater mInflater = (LayoutInflater) context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
convertView = mInflater.inflate(R.layout.list_support_item, null);
TextView accountType = (TextView) convertView.findViewById(R.id.accountType);
accountType.setText(accountItem.getTitle());
I want to ask about the differences between two, and which on preferable? And how can I improve performance even more. I am testing on kitat nexus and works perfectly in both cases, but am trying to figure out gingerbread issues
I want to ask about the differences between two, and which on preferable?
with a large of data to represent is always preferable to use the ViewHolder pattern. As you have surely realised, if you do not use this pattern, you have to call findViewById for every view that belongs to the layout you inflated. With this pattern you look for those only once.
Performance Tips for Android’s ListView
use the ViewHolder pattern.
Understand how listview recycling works
How ListView's recycling mechanism works
Quoting docs
Your code might call findViewById() frequently during the scrolling of ListView, which can slow down performance. Even when the Adapter returns an inflated view for recycling, you still need to look up the elements and update them. A way around repeated use of findViewById() is to use the "view holder" design pattern.
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) { // if convertView is null
convertView = mInflater.inflate(R.layout.mylayout,
parent, false);
holder = new ViewHolder();
// initialize views
convertView.setTag(holder); // set tag on view
} else {
holder = (ViewHolder) convertView.getTag();
// if not null get tag
// no need to initialize
}
//update views here
return convertView;
}
So using ViewHolder improves performance as you avoid initializing views everytime. You initialize only when view is null (if(convertView==null) {. Helps in smooth scrolling.
You missed the important part convertView.setTag(holder) and holder = (ViewHolder) ConvertView.getTag()
Android Doc of Smooth scrolling in lisview

Android ListView Layout inflater

We are working with list views in college at the moment. My lecturer gave us a simple application that displays mail messages in a list and when the user selects one it displays the content of the message in a new activity. I understand pretty much all of what is going on but there are a few grey areas I want to clear up!
Basically I am wondering what this section of code does?
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v == null) {
LayoutInflater vi = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.inbox_row, null);
}
This method is located within a class that extends ArrayAdapter. Am I right in thinking that it is some form of recycling? for when views go on and off the screen?....
Any help is much appreciated. thanks.
it's exactly what you said, a form of recycling.
Inflating a layout takes a lot of memory and a lot of time, so for the efficiency sake, the system passes to you that just went off the screen and you can simply update its text and images and give them back to the UI.
So for example, if your list view is showing 6 items on its list (due to the height of it), it will only inflate 6 items and during scroll it just keeps recycling them.
there's some extra optimisations tricks that you should use and I'm sure that the video link that the commenter posted will explain them.
edit
that example is an ArrayAdapter of Store items, but you can make it to whatever you need.
the adapter does the match and separation layer between UI and data.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null)
convertView = newView();
// Store is the type of this ArrayAdapter
Store store = getItem(position);
Holder h = (Holder) convertView.getTag();
// And here I get the data and address them to the UI
// as you can see, if the convertView is not null,
// I'm not creating a new one, I'm just changing text, images, etc
h.storeName.setText(store.getStoreName());
h.address.setText(store.getAddressLine1());
h.postcode.setText(store.getPostCode());
h.distance.setText(store.getDistance());
return convertView;
}
// I like to separate in a different method when the convertView is null
// but that's me being organisation obsessive
// but it also makes easy to see which methods are only being called the 1st time
private View newView() {
LayoutInflater inf = LayoutInflater.from(getContext());
View v = inf.inflate(R.layout.map_result_list, null);
Holder h = new Holder();
// here we store that holder inside the view itself
v.setTag(h);
// and only call those findById on this first start
h.storeName = (TextView) v.findViewById(R.id.txtLine1);
h.address = (TextView) v.findViewById(R.id.txtLine2);
h.postcode = (TextView) v.findViewById(R.id.txtLine3);
h.distance = (TextView) v.findViewById(R.id.txtDistance);
return v;
}
// this class is here just to hold reference to the UI elements
// findViewById is a lengthy operation so this is one of the optimisations
private class Holder {
TextView storeName;
TextView address;
TextView postcode;
TextView distance;
}

how to change listview's row properties without layout?

Good time!
My Android app has so feature that I use ListView in the one of the page of TabHost without layout for ListView. Like that:
ListView lv = new ListView(this);
lv.setAdapter(myAdapter);
So I'd like to change some row's properties like text size in the row. So, how can I get access to the properties of the explicit row of ListView to change text size, for example?
Use BaseAdapter and modify font size in getView call.
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.custom_layout, 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);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
// Change text size
holder.text.setTextAppearance(context,R.style.customStyle);
return convertView;
}
static class ViewHolder {
TextView text;
}
And you can use position variable in getView call to change specific row. Hope this help!!!

How to implement a view holder?

i am using a viewholder to display from a dynamic arrayadapter.it works but the data displayed changes irregularly when i scroll the List.i want my List View to be populated only once ,Not all the time when i scroll my list. Any suggestion?
Here is my Code
public View getView(int position, View convertView, ViewGroup parent) {
// A ViewHolder keeps references to children views to avoid unneccessary calls
// to findViewById() on each row.
ViewHolder holder;
// When convertView is not null, we can reuse it directly, there is no need
// to reinflate it. We only inflate a new View when the convertView supplied
// by ListView is null.
if (convertView == null) {
convertView = mInflater.inflate(R.layout.sample, null);
// Creates a ViewHolder and store references to the two children views
// we want to bind data to.
holder = new ViewHolder();
holder.name = (TextView) convertView.findViewById(R.id.text);
holder.icon = (ImageView) convertView.findViewById(R.id.icon);
convertView.setTag(holder);
} else {
// Get the ViewHolder back to get fast access to the TextView
// and the ImageView.
holder = (ViewHolder) convertView.getTag();
}
// Bind the data efficiently with the holder.
if(_first==true)
{
if(id<myElements.size())
{
holder.name.setText(myElements.get(id));
holder.icon.setImageBitmap( mIcon1 );
id++;
}
else
{
_first=false;
}
}
//holder.icon.setImageBitmap(mIcon2);
/*try{
if(id<myElements.size())
id++;
else
{
id--;
}
}
catch(Exception e)
{
android.util.Log.i("callRestService",e.getMessage());
}*/
return convertView;
}
static class ViewHolder {
TextView name;
ImageView icon;
}
when the list is loaded it looks like this : http://i.stack.imgur.com/NrGhR.png after scrolling some data http://i.stack.imgur.com/sMbAD.png it looks like this, and again if i scroll to the beginning it looks http://i.stack.imgur.com/0KjMa.png
P.S : my list have to be in alphabetic order
Have you tried this?
public View getView(int position, View convertView, ViewGroup parent) {
// A ViewHolder keeps references to children views to avoid unneccessary calls
// to findViewById() on each row.
ViewHolder holder;
// When convertView is not null, we can reuse it directly, there is no need
// to reinflate it. We only inflate a new View when the convertView supplied
// by ListView is null.
if (convertView == null) {
convertView = mInflater.inflate(R.layout.sample, null);
// Creates a ViewHolder and store references to the two children views
// we want to bind data to.
holder = new ViewHolder();
holder.name = (TextView) convertView.findViewById(R.id.text);
holder.icon = (ImageView) convertView.findViewById(R.id.icon);
convertView.setTag(holder);
} else {
// Get the ViewHolder back to get fast access to the TextView
// and the ImageView.
holder = (ViewHolder) convertView.getTag();
}
// Bind the data efficiently with the holder.
holder.name.setText(myElements.get(id));
holder.icon.setImageBitmap( mIcon1 );
return convertView;
}
static class ViewHolder {
TextView name;
ImageView icon;
}
If yes, what's wrong with it?
I don't think loading all the rows at once is a good idea. You will end up having plenty of useless Views in memory that are going to slow the application down for nothing.
Views and operations on views (like inflate, findViewById, getChild..) are expensive, you should try to reuse them as much as possible. That's why we use ViewHolders.
You would need to write you own version of ListView to do that (which is bad). If the ListView doesn't work properly, it probably means that you are doing something wrong.
Where does the id element come from? You are getting the position in your getView() method, so you don't need to worry about exceeding list bounds. The position is linked to the element position in your list, so you can get the correct element like this:
myElements.get(position);
When the data in your list changes, you can call this:
yourAdapter.notifyDataSetChanged()
That will rebuild your list with new data (while keeping your scrolling and stuff).

Categories

Resources