I have an Adapter with ViewHolder. Let's say I want to set data only once to the ViewHoler inside the getView() method, what's actually happened is, its set the data every time I'm scrolling the view.
How can I check if the ViewHolder was already init or not?
public View getView(final int position, View convertView, ViewGroup parent) {
final ViewHolder holder;
if (convertView == null) {
holder = new ViewHolder();
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
Deal deal = dealsArr.get(position);
holder.textView.setText(deal.getPrice);
// Here is my issue. how can I do it only once?
changePrice(deal, true);
}
I asssume you already have the basic understanding of Android Adapters & working of getCount() & getView() if not see this
Either adapter returns a null View or an Inflated View; findViewbyId(R.id.xmlID) is always executed;
ViewHolder is used to avoid frequent call and ensure the smooth scrolling of your listview.
Excellent Explanation here
First spend sometime of understand Listview's recycling mechanism!
The link I had shared also has a link to that.!
public View getView(final int position, View convertView, ViewGroup parent) {
//rest of the code goes here
}
focus on the parameters of this method,
the convertViewwhich is for recycling will be null initially because there is no recycling done yet
so this part of the code will be executed;
if (convertView == null) { //convertView is null when recycling isn't done
holder = new ViewHolder();
convertView.setTag(holder);
}
so we initialize the view-holder & refer all our Views(Buttons, TextViews etc) using findview by id.
the next time getView() is called; it executes this part of the code;
else {
holder = (ViewHolder) convertView.getTag();
}
I'd strongly recommend to first understand working of Android Adapters & List View Recyling Mechanism without using ViewHolders in your code. first you understand how & when the methods are invoked then use ViewHolder for more optimization
Related
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.
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 defined a Adapter which extends BaseAdapter when I use ListView to display something.I overrided View getView (int position, View convertView, ViewGroup parent) method to reuse View component, in the method, I also wrote if(convertView == null) {System.out.println("test");} block. There are 50 rows data in ListView and the screen only can display about 20 rows data. when I ran the application, LogCat printed less than 50 rows of "test" though I slided the screen to make sure all data are loaded.But why ? I think it should print 50 rows data. Here is the key code:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null) {
convertView = inflater.inflate(resource, null);
System.out.println(++count + "convertView == null:" + convertView);
}
}
someone help me please, I am a newbie.... thanks
Android does not inflate a View for every item in your adapter. It reuses inflated views previously used for other items.
The pattern for binding views in a adapter is something like this:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
if(convertView == null) {
view = inflater.inflate(resource, null);
} else {
view = convertView;
}
// bind data to view here
return view;
}
In fact you normally would use a ViewHolder class. But first fix your basic adapter before reading about that.
Because convertView will be null only if there isn't a previously returned view that can be recycled (i.e. it's no longer on screen).
The views you return from an adapter's getView() can be recycled in later calls to getView(). The framework passes such recyclable views in via the convertView arg.
So your convertView == null branch only gets run enough times to fill the listview screen once and after that, when scrolling, these old views get recycled.
You missing return view.
ListView recycles view. How ListView's recycling mechanism works
Use a ViewHolder pattern
http://developer.android.com/training/improving-layouts/smooth-scrolling.html
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if(convertView == null) {
convertView = inflater.inflate(resource, null);
holder = new ViewHolder();
holder.tv = (TextView)convertView.findViewById(R.id.textView1);
//initialize views
convertView.setTag(holder);
} else {
holder =(ViewHolder) convertView.getTag();
}
// update your view here
holder.tv.setText("hi");
return convertView;
}
static class ViewHolder {
// YourViews Declaration
TextView tv; // an example
}
I have a question that after searching SO and Google I can't find an answer to. When working with an adapter in Android its good practice to reuse the list item view by using the convertView parameter in the getView() method. My question is this, if I make a change to convertView, will that change persist to future calls to getView()?
For example:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
ViewHolder holder = new ViewHolder();
convertView = LayoutInflater.from(context).inflate(R.layout.list_item, null);
convertView.setTag(holder);
// if i call this method here, will all future views passed into convertView
// also have this set??
// From what I know about Java and objects I would guess yes
// but I'm not 100% sure how Android processes the convertView behind the scenes
((ViewGroup) convertView).setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
}
ViewHolder holder = (ViewHolder) convertView.getTag();
// currently setDescendantFocusability is called here,
// I want to move it to where it above to help improve performance
return convertView;
}
I think so.
The converted views you operate will be reused in future.
When you get a converted view passed by getView() method, the specific converted view might have been used before, so make sure to update all the attributes which might be dirty for current converted view.
Should be kind of below code:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
if(view == null){
view = createSpecificView();
}
updateSpecificView(view);//update all attributes here.
return view;
}
Hopefully can help you.
The ONLY reason you need to return the convertView at the end of getView() is for the situation where it was null and/or you create a new instance. When an object is passed as a parameter, you can modify the underlying object's state but cannot change or create a new object. You can't change which object convertView points to.
So, you also return the convertView at the end of the method. This covers the situation where you create a new object.
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.