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.
Related
I'm working with a custom ListView with custom Adapter which only has EditText and TextView in each row, the problem is in scroll, when there is something in the first row, for example, it doesn't matter how long is the text in the field, if it goes out of the screen while scrolling, then if I get back to it, it looses the text it had
#Override
public View getView(int i, View view, ViewGroup viewGroup) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View itemView = inflater.inflate(R.layout.campos, viewGroup, false);
TextView tv= itemView.findViewById(R.id.lblNumeroCampo);
EditText txtCampo = itemView.findViewById(R.id.txtCampo);
txtCampo.setText(elementos.get(i));
tvNumero.setText("" + (i +1));
return itemView;
}
I just want not to lose text that is in any field of the list, thanks to those who want to help me.
I think you don't need to call the findViewById every time the getview action fired.
You can also use a holder class.
Your UI classes should be called once when the view class is null.
The view class can save the holder class with setTag method then reuse them by getTag.
Here is my suggestion.
class ViewHolder
{
TextView tvNumero;
EditText txtCampo;
}
public View getView(int position, View view, ViewGroup viewGroup) {
ViewHolder holder;
if(view == null)
{
view = inflater.inflate(resource, viewGroup, false);
holder = new ViewHolder();
holder.tvNumero= view.findViewById(R.id.lblNumeroCampo);
holder.txtCampo = view.findViewById(R.id.txtCampo);
view.setTag(holder);
} else {
holder = (ViewHolder) view.getTag();
}
holder.txtCampo.setText(elementos.get(i));
holder.tvNumero.setText("" + (i +1));
return view;
}
Have a good coding.
This happens because android calls getView() everytime the view goes out of visible space and appears again. Since you are creating a new view in your getView() method, there is no recycling happening. Everytime the getView() method is called, a new view is returned with no text in it.
As mentioned by #tommybee, you can use a view holder class that checks to see if the view has already been initialized and return the initialized view if called.
Alternatively, I would suggest you to use RecyclerView instead of ListView, which makes using ViewHolder pattern mandatory. Reason is explained clearly here
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
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 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 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().