user1457227
user1457227

Reputation: 71

Is View regenerated as ListView scrolls?

I have a custom baseadapter that I am binding to a ListView. I am loading a list of objects from a custom class.

I am altering the layout of some of the rows, when the data changes, to create headers (yeah I know there is some logic I still have to fix but it works).

My problem is, when I scroll the listview past what is orginally-visible, the application crashes with a ClassCastException error on HeaderHolder (which I see if I set a breakpoint in my catch handler). I am thinking this is due to the View being destroyed and recreated, not sure. Can someone confirm this?

public class MyCustomBaseAdapter extends BaseAdapter {
 private static ArrayList<Appointment> searchArrayList;

 private LayoutInflater mInflater;

 public MyCustomBaseAdapter(Context context, ArrayList<Appointment> results) {
  searchArrayList = results;
  mInflater = LayoutInflater.from(context);
 }

 public int getCount() {
  return searchArrayList.size();
 }

 public Object getItem(int position) {
  return searchArrayList.get(position);
 }

 public long getItemId(int position) {
  return position;
 }

 public String lastDate = null;
 public String thisItemDate = null;

 public View getView(int position, View convertView, ViewGroup parent) 
 {
     try
     {
         thisItemDate = searchArrayList.get(position).GetDTStart().toString().substring(0,10);
         if(!thisItemDate.equals(lastDate))
         {
             HeaderHolder holder;
             if (convertView == null) 
             {
                 convertView = mInflater.inflate(R.layout.calendar_item_header, null);

                 holder = new HeaderHolder();

                 holder.txtHeader = (TextView) convertView.findViewById(R.id.header);

                 convertView.setTag(holder);
                 convertView.setPadding(2,2,2,2);
             } 
             else 
             {
                 holder = (HeaderHolder) convertView.getTag();
             }

             holder.txtHeader.setText(thisItemDate);

             lastDate = thisItemDate;
             return convertView;                 
         }
     }
     catch (Exception e)
     {  //Catch exception if any
        System.err.println("Error: " + e.getMessage());
     }


     ViewHolder holder;
     if (convertView == null) 
     {
         convertView = mInflater.inflate(R.layout.calendar_item, null);

         holder = new ViewHolder();
         holder.txtAttendee = (TextView) convertView.findViewById(R.id.attendee);
         holder.txtSummary = (TextView) convertView.findViewById(R.id.summary);
         holder.txtStarts = (TextView) convertView.findViewById(R.id.starts);
         holder.txtEnds = (TextView) convertView.findViewById(R.id.ends);

         convertView.setTag(holder);
         convertView.setPadding(4, 4, 4, 16);
     } 
     else 
     {
         holder = (ViewHolder) convertView.getTag();
     }

     holder.txtAttendee.setText(searchArrayList.get(position).GetAttendee());
     holder.txtSummary.setText(">" + searchArrayList.get(position).GetSummary());
     String st = searchArrayList.get(position).GetDTStart().toString();
     String en = searchArrayList.get(position).GetDTEnd().toString();

     holder.txtStarts.setText(st.substring(0,16));
     holder.txtEnds.setText(en.substring(0,16));

     return convertView;         
 }

 static class ViewHolder 
 {
     TextView txtAttendee;
     TextView txtSummary;
     TextView txtStarts;
     TextView txtEnds;
 }

 static class HeaderHolder
 {
     TextView txtHeader;

Upvotes: 0

Views: 284

Answers (2)

user1457227
user1457227

Reputation: 71

Well, I'm almost there... it works fine with one exception: when I scroll down past the bottom of the listview, the entries start getting out of order. Using the debugger, I have verified that the arraylist is loaded in exactly the correct order and that the date labels are right where they should be. However, once I start scrolling, things get jumbled out of order (for instance, listview entries move around and the labels move up and/or down. I think the problem is due to a nullpointer exception I am getting (in the catch block with the note to the right of it). But I'm not sure why this is occurring, and only when the list is scrolled enough so that some elements pop off and others need to come into view.

public class MyCustomBaseAdapter extends BaseAdapter {
 //private static ArrayList<Appointment> searchArrayList;
 private static ArrayList<Object> searchArrayList;

 private LayoutInflater mInflater;

 //public MyCustomBaseAdapter(Context context, ArrayList<Appointment> results) {
 public MyCustomBaseAdapter(Context context, ArrayList<Object> results) {

  searchArrayList = results;
  mInflater = LayoutInflater.from(context);
 }

 public int getCount() {
  return searchArrayList.size();
 }

 public Object getItem(int position) {
  return searchArrayList.get(position);
 }

 public long getItemId(int position) {
  return position;
 }


 // One way for doing that would be to back the adapter with an ArrayList<Object> instead of ArrayList<Appointment>. 
 // That way you can have both Appointment objects and section title strings in the same list.
 // Both methods getView and getItemViewType need to fetch the ArrayList item at the requested position and check the item object for its type:

public int getItemViewType(int position) {
    Object item = getItem(position);

    if(item instanceof Appointment) {
        return 0;
    } else {
        // It's a section title:
        return 1;
    }
}

public View getView(int position, View convertView, ViewGroup parent) 
{
    Object item = getItem(position);

    if(convertView == null) 
    {
        // Create item view for first time
        if(item instanceof Appointment) 
        {   // inflate appointment list view item layout
            convertView = mInflater.inflate(R.layout.calendar_item, null);
        } 
        else 
        {   // inflate title section list view item layout
            convertView = mInflater.inflate(R.layout.calendar_item_header, null); 
        }
    }

    // Update list view item view according to type:
    if(item instanceof Appointment) 
    {
        Appointment a = (Appointment) item;
        // Retrieve TextViews etc from convertView, cache it as Tag in a ViewHolder
        // and update these views based on Appointment a
        ViewHolder holder;

        holder = new ViewHolder();
        holder.txtAttendee = (TextView) convertView.findViewById(R.id.attendee);
        holder.txtSummary = (TextView) convertView.findViewById(R.id.summary);
        holder.txtStarts = (TextView) convertView.findViewById(R.id.starts);
        holder.txtEnds = (TextView) convertView.findViewById(R.id.ends);
        holder.txtCategories = (TextView) convertView.findViewById(R.id.categories);

        convertView.setTag(holder);
        convertView.setPadding(4, 4, 4, 16);

        try
        {
            holder.txtAttendee.setText(a.GetAttendee());
            holder.txtSummary.setText(">" + a.GetSummary());
            String st = a.GetDTStart().toString();
            String en = a.GetDTEnd().toString();

            holder.txtStarts.setText(st.substring(0,16));
            holder.txtEnds.setText(en.substring(0,16)); 

            String cat = a.GetCategories();
            holder.txtCategories.setText(cat);



        }
        catch (Exception e)
        {
            String err = e.getMessage();  // here's the error location

        }
    } 
    else 
    {
        // Item is a section header string:
        String label = (String) item;
        // Retrieve label TextView from convertView... etc...
        HeaderHolder holder;

        holder = new HeaderHolder();

        holder.txtHeader = (TextView) convertView.findViewById(R.id.header);

        convertView.setTag(holder);
        convertView.setPadding(2,2,2,2);
        convertView.setClickable(false);
        convertView.setFocusable(false);

        try
        {
            holder.txtHeader.setText(" " + label);
        }
        catch (Exception e)
        {

        }
    }

    return convertView;
}

 static class ViewHolder 
 {
     TextView txtAttendee;
     TextView txtSummary;
     TextView txtStarts;
     TextView txtEnds;
     TextView txtCategories;
 }

 static class HeaderHolder
 {
     TextView txtHeader;

 }

Upvotes: 0

tiguchi
tiguchi

Reputation: 5410

You must tell ListView through your Adapter the amount of view types it internally creates / updates and what view type to find at which list position. This is done through the methods:

EDIT: Your implementation is a bit complicated. You should redesign your adapter to explicitly include the section titles as data items of your internal list data structure.

One way for doing that would be to back the adapter with an ArrayList<Object> instead of ArrayList<Appointment>. That way you can have both Appointment objects and section title strings in the same list.

Both methods getView and getItemViewType need to fetch the ArrayList item at the requested position and check the item object for its type:

public int getItemViewType(int position) {
    Object item = getItem(position);

    if(item instanceof Appointment) {
        return 0;
    } else {
        // It's a section title:
        return 1;
    }
}

You would proceed similarly in your getView method:

public View getView(int position, View convertView, ViewGroup parent) {
    Object item = getItem(position);

    if(convertView == null) {
        // Create item view for first time

        if(item instanceof Appointment) {
            convertView = ... // inflate appointment list view item layout
        } else {
            convertView = ... // inflate title section list view item layout
        }
    }

    // Update list view item view according to type:
    if(item instanceof Appointment) {
        Appointment a = (Appointment) item;
        // Retrieve TextViews etc from convertView, cache it as Tag in a ViewHolder
        // and update these views based on Appointment a
    } else {
        // Item is a section header string:
        String label = (String) item;
        // Retrieve label TextView from convertView... etc...
    }

    return convertView;
}

There is actually nothing more to it.

Upvotes: 3

Related Questions