How to implement a view holder? - android

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).

Related

What is the difference between 2 code of Listview adapter getView method

I know recycle view is new but I want to know what is difference in 2 codes in list view. I have already tried to search a lot but not get specific answer. I know First one is more faster then the second because of memory consumption but why second code is slow then the first one what is the internal process can any one enplane it.
This is the first Code
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
final HashMap<String ,String > item = lst.get(position);
ViewHolderItem viewHolder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.shadow_request_row, parent, false);
viewHolder = new ViewHolderItem();
viewHolder.title = (TextView)convertView.findViewById(R.id.item_name);
viewHolder.msg = (TextView)convertView.findViewById(R.id.message);
convertView.setTag(viewHolder);
}else{
viewHolder = (ViewHolderItem) convertView.getTag();
}
viewHolder.title.setText(item.get(Const.USERNAME));
viewHolder.msg.setText(item.get(Const.GET_MESSAGE));
return convertView;
}
This is the Second Code
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
final HashMap<String ,String > item = lst.get(position);
ViewHolderItem viewHolder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.shadow_request_row, parent, false);
viewHolder = new ViewHolderItem();
viewHolder.title = (TextView)convertView.findViewById(R.id.item_name);
viewHolder.msg = (TextView)convertView.findViewById(R.id.message);
}
viewHolder.title.setText(item.get(Const.USERNAME));
viewHolder.msg.setText(item.get(Const.GET_MESSAGE));
return convertView;
}
The second example is missing the part about saving the viewHolder as the tag of the created view if the view is just being inflated and reusing the viewHolder if the view already exists.
List View items are re created whenever it wants. (This happens when you scroll up and down). Whenever a ListView needs to re create an item it calls the getView() of the adapter with the required position. Inside the getView() method you have the logic to generate the item View required for that position.
The method findViewById() that you use to find a View inside the XML, is CPU extensive. You might see a considerable lag if your XML contains a long sequence of children and the getView() contains lots of findViewById() calls.
This is where a ViewHolder comes handy. A ViewHolder is a class that can hold the View items. You can use the already created ViewHolder objects instead of calling findViewById() every time.
To make use of this you have to save a ViewHolder object associated with a particular position. You do it like this.
ViewHolder viewHolder;
if(convertView==null){
//the view is created for the first time
//you have to make the View HOlder object here
viewHOlder=new ViewHOlder(convertView);
//ViewHOlder constructor can find the required view elements and store it in variables
//now you have to save this View Holder object for future reference
//you save it as a tag
convertView.setTag(viewHolder);
}
Now you have a defined View Holder for the specific item position. Here is how to re use it.
When the ListView adapter wnats to re use it the convertView given to getItem() is not null. So the re use occurs in the else statement of the above if.
else{
//you already have a pre created View holder. Retrieve it.
viewHOlder=(ViewHolder)convertView.getTag();
//now you can get access to your View elements easily
}
In you second example, you create the ViewHOlder but you never re use it. So it makes not improvement.

ListView alternate of using ViewHolder

I have created a ListView and its custom Adapter. But due to some reason I am not allowed to get items from ViewHolder.
In my case ViewHolder has only one variable and that is of LinearLayout. LinearLayout contains the other child views(which is decided and created at run time). When I use ViewHolder and set the tag of holder object, on scroll I am getting the same views again.
Is there any other way to stop adapter to create views while scrolling ?
Or, while scrolling how can we clear the references of views ?
I have find this but I don't think this will work.
setRecyclerListener(new RecyclerListener() {
#Override
public void onMovedToScrapHeap(View view) {
//from here can we use this to clean the memory
}
});
ViewHolder is meant as a holder to contain ids of listitem layout.
It is optimization to avoid calling findViewById everytime new listitem is created for display by going through data container e.g. arrayList.
You cannot stop adapter in between creating item views.
Only items on display are created.
convertView acts as object being recycled for creating subsequent view while scrolling up/down.
You will not be able to use view holder for the purpose you are trying to achieve.
Sample usage as below.
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
View v = convertView;
ViewHolder viewHolder = null;
if(convertView == null)
{
v = LayoutInflater.from(StockDetailsActivity.this).inflate(R.layout.stock_details_list_item, null);
viewHolder = new ViewHolder();
viewHolder.model_name_tv = (TextView) v.findViewById(R.id.model_name);
viewHolder.model_type_iv = (ImageView) v.findViewById(R.id.model_type_icon);
viewHolder.model_type_tv = (TextView) v.findViewById(R.id.model_type_desc);
viewHolder.model_stock_tv = (TextView) v.findViewById(R.id.model_stock_value);
v.setTag(viewHolder);
}
else
viewHolder = (ViewHolder) v.getTag();
stockCursor.moveToPosition(position);
// logic to update data to views as appropriate goes here
return v;
}
public class ViewHolder{
public TextView model_name_tv;
public ImageView model_type_iv;
public TextView model_type_tv;
public TextView model_stock_tv;
}

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

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

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!!!

ListView with ArrayAdapter and ViewHolder adding icons to the wrong item

I have a dynamic ListView which uses an ArrayAdapter. When a name is selected from a spinner, the name together with an icon showing whether they are male or female gets added to the ListView.
Mostly everything is good (the name gets added to the list correctly, together with an icon). But the icon showing the sex gets added to the wrong item in the ListView. The name gets added to the bottom of the list, but the icon gets placed at the name at the top of the list. I don't know if it's the way I'm using ViewHolder but there is zero documentation on it in the Android website.
// Listview inflater
inflater = (LayoutInflater) (this).getSystemService(LAYOUT_INFLATER_SERVICE);
// List Array.
mAdapter = new ArrayAdapter<String>(this, R.layout.player_simple_list,
R.id.label, mStrings) {
#Override
public View getView(int position, View convertView, ViewGroup parent) {
Log.i("ANDY","View getView Called");
// A ViewHolder keeps references to children views to
// avoid unneccessary calls to findViewById() on each row.
ViewHolder holder;
if (null == convertView) {
Log.i("ANDY","Position not previously used, so inflating");
convertView = inflater.inflate(R.layout.player_simple_list, 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.label);
holder.icon = (ImageView) convertView.findViewById(R.id.icon);
if (sexmale == true) {
holder.icon.setImageBitmap(maleicon);
}
else {
holder.icon.setImageBitmap(femaleicon);
}
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.text.setText(getItem(position));
// Change icon depending is the sexmale variable is true or false.
Log.i("ANDY","getCount = "+mAdapter.getCount());
return convertView;
}
};
setListAdapter(mAdapter);
You have to set the icons after if-else-if for creating or binding a holder. Otherwise, the icons would be rightly displayed only in first few items in the list i.e until the ListView is not filled.
public View getView(int position, View convertView, ViewGroup parent) {
Log.i("ANDY","View getView Called");
// A ViewHolder keeps references to children views
// to avoid unneccessary calls to findViewById() on each row.
ViewHolder holder;
if (null == convertView) {
Log.i("ANDY","Position not previously used, so inflating");
convertView = inflater.inflate(R.layout.player_simple_list, 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.label);
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.text.setText(getItem(position));
// Change icon depending is the sexmale variable is true or false.
if (sexmale == true) {
holder.icon.setImageBitmap(maleicon);
}
else {
holder.icon.setImageBitmap(femaleicon);
}
Log.i("ANDY","getCount = "+mAdapter.getCount());
return convertView;
}
You have to move from the if a few lines of data after the comment, as in this question is explained
// Bind the data efficiently with the holder.
so it will look like this
if (null == convertView) {
Log.i("ANDY","Position not previously used, so inflating");
convertView = inflater.inflate(R.layout.player_simple_list, null);
// Creates a ViewHolder and store references to the two children views
// we want to bind data to.
holder = new ViewHolder();
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.text = (TextView) convertView.findViewById(R.id.label);
holder.icon = (ImageView) convertView.findViewById(R.id.icon);
if (sexmale == true) {
holder.icon.setImageBitmap(maleicon);
}
else {
holder.icon.setImageBitmap(femaleicon);
}
holder.text.setText(getItem(position));
Update: ViewHolder is only meant to hold references to the component views inside the item layout. This helps to avoid the overhead of calling findViewById for rendering each component inside complex item layouts with multiple components(Like the TextView, and ImageView in this case).
I fixed it by using a routine (called getSex) to retrieve the sex data and setting all the view data including icons outside the if-else blocks.
The working code now looks like this:
if (null == convertView) {
Log.i("ANDY","Position not previously used, so inflating");
convertView = inflater.inflate(R.layout.player_simple_list, 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.label);
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.text.setText(getItem(position));
// Change icon depending is the sexmale variable is true or false.
if (getSex (getItem(position)) == true) {
holder.icon.setImageBitmap(maleicon);
}
else {
holder.icon.setImageBitmap(femaleicon);
}
return convertView;

Categories

Resources