Reputation: 8271
I'm trying to wrap my head around getView() and I don't think there's any topic in Android that creates more confusion and questions on StackOverflow and elsewhere. Everyone wants to know why it gets called so many times or in what order or not at all, but as Romain Guy says here , " there is absolutely no guarantee on the order in which getView() will be called nor how many times."
So I have a different question: I don't understand the convertView parameter.
I have a list of 15 items, 11 of which can fit on the screen. The very first time my app starts up getView() is called 48 times.
convertView is null for position 0 on the first call, the non-null for positions 1-11, then non-null for position 0 and null for positions 1-11, then null for position 0 and non-null for positions 1-11, and finally non-null for positions 0-11.
Could someone please explain why/when convertView is null versus non-null, how/why it starts off non-null for most positions, and why the same positions seem to bounce back and forth between these two states?
References to good tutorials, written in clear English, that explain convertView in detail would also be appreciated.
PS - my tests were done on a device running Android 2.3.5, if that matters. I know Google has changed ListActivity/adapter/getView stuff several times since then.
Per request, I'm including the Adapter code (I've obscured some proprietary names). Unfortunately I can't answer any "why did you do that?" questions, since I didn't write it
protected class PLxxxAdapter extends BaseAdapter {
public PLxxxAdapter(Context c) {
}
@Override
public int getCount() {
return listItems.size();
}
@Override
public Object getItem(int position) {
return listItems.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
boolean select;
if (convertView == null) {
LayoutInflater vi = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = vi.inflate(R.layout.PLxxxitem, null);
//This is still needed even though we point to an XML description
convertView.setLayoutParams(new ListView.LayoutParams(
ListView.LayoutParams.MATCH_PARENT,
ListView.LayoutParams.MATCH_PARENT));
holder = new ViewHolder();
//Get the views of the row
holder.itemView = (TextView)convertView.findViewById(R.id.post);
holder.cV1 = (CheckedTextView)convertView.findViewById(R.id.check1);
//Init the 'confirm' box listener
holder.cV1.setCompoundDrawablesWithIntrinsicBounds(0, R.layout.smallcb, 0, 0);
holder.cV1.setOnClickListener(new ConfBoxListener());
convertView.setTag(holder);
holder.cV1.setTag(holder); //These views need tags for onClick()
}
else {
holder = (ViewHolder)convertView.getTag(); // convertview NOT null
}
try {
int liSize = listItems.size();
if (position < liSize) {
holder.itemView.setText(listItems.get(position));
}
}
catch (Exception e) {
Log.e ("PLxxxActivity.getView Crash", "details " + e);
}
holder.cV1.setChecked(confirmed.get(position));
select = selected.get(position);
if (select == true) {
convertView.setBackgroundResource(R.color.colBlue);
}
else
convertView.setBackgroundResource(R.color.colGrey);
holder.position = position;
if (RemoteControlActivity.confCBs == true)
holder.cV1.setVisibility(View.VISIBLE);
else
holder.cV1.setVisibility(View.INVISIBLE);
return convertView;
} // end getView
} //end class PLxxxAdapter
Upvotes: 5
Views: 5949
Reputation: 11248
The problem, as hinted by others, is probably not in your adapter. I fairly certain of this because I just fought the same exact problem and came here looking for answers only to be disappointed. In my case, I set my list view's width & height to wrap_content instead of match_parent. When I set it to match_parent I got the desired behavior. It's extremely hard to uncover because the view would grow to accommodate the size of its contents and take up the entire size of its container thus showing multiple list entries, however the system "thought" it could only fit 1 item. So the getView method was being called repeatedly with the same convertView. (It was null only on the 1st call as in your case.) A possible approach (which I have not tried) would be to interrogate the parent list view and check its size. Make sure it has enough room to render multiple rows and adjust accordingly.
Upvotes: 0
Reputation: 134684
The convertView
parameter is a recycled instance of some View that you previously returned from getView()
. The first time that getView()
is called, convertView
is guaranteed to be null, as you have not yet created a View to be recycled. When one of your Views in the list have been scrolled offscreen and are no longer visible to the user, they are removed from the ViewGroup and added to the internal "recycling bin". You will then start receiving these recycled views as the convertView
parameter, once they are available.
You should always check if convertView is non-null, and if it is, you should attempt to re-use it. For example, if your getView() inflates a TextView, it is safe to cast convertView
to a TextView
instance if it is non-null. Then you should update its text to match whatever represents your item at the current position (getItem(position)
).
EDIT:
The only reason you need this:
convertView = vi.inflate(R.layout.PLxxxitem, null);
//This is still needed even though we point to an XML description
convertView.setLayoutParams(new ListView.LayoutParams(
ListView.LayoutParams.MATCH_PARENT,
ListView.LayoutParams.MATCH_PARENT));
is because of the way you're inflating the view. Instead of passing null, pass the parent with attachToRoot = false, i.e.:
convertView = vi.inflater(R.layout.PLxxxitem, parent, false);
Regardless, I don't think your problem lies in your adapter.
Upvotes: 0
Reputation: 100398
The first x
times, where x
is a number near the number of items visible on the screen, convertView
is null
. You need to instantiate a new View
to return.
When you scroll down, an existing View
is pushed upwards out of sight. Instead of destroying it, it can now be reused. You'll notice that, just before a new View
is pushed in from below, your getView
method is called, with a valid convertView
. This is exactly that View
that was pushed out of sight before (or maybe another one, there is some additional logic)!
Therefore, instead of re-instantiating your View
, which is costly, you can reuse the View
and adapt it to the new item
it represents. You will often see something like:
View view = convertView;
if(view == null){
view = LayoutInflater.from(getContext()).inflate(...);
}
// 'bind' view
return view;
The fact that your getView
method is called 48 times on startup, might actually be an issue with your code.
Upvotes: 6