简体   繁体   中英

ConvertView in GetView()

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

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.

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, ie:

convertView = vi.inflater(R.layout.PLxxxitem, parent, false);

Regardless, I don't think your problem lies in your adapter.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM