0

Problem occurs when I try to reuse views in ListView having several custom layouts. When I change number of items I get wrong convertView being passed to getView method. In details: I have a header layout, a row layout and a footer layout. In the header I can change number of rows. Example for 1 row: Header row1 Footer When I reuse views I get wrong result (after adding another row). This happens because previously created footer view is passed for position = 2 and since it is not null I don't recreate it: Header row1 Footer Footer

Instead of expected: Header row1 row2 Footer

What am I doing wrong? Or is it by design and I just should recreate views instead of reusing them? Here goes my code:

public class NewProgramAdapter extends BBBaseAdapter {

public static int TYPE_HEADER = 1;
public static int TYPE_FORM = 2;
public static int TYPE_FOOTER = 3;

protected String mDays;
protected String mProgramId;
protected int currentDay = 0;
protected int numberOfExercises = 1;
protected JSONArray items = new JSONArray();

public NewProgramAdapter(Activity a) {
    super(a);
    // TODO Auto-generated constructor stub
}

@Override
public int getCount() {
    return numberOfExercises + 2; // header and footer
}

@Override
public Object getItem(int position) {
    return position;
}

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

@Override
public View getView(int position, View convertView, ViewGroup parent) {

    if (position > 0 && position < getCount() - 1) {
        return getFormView(position, convertView, parent);
    } else if (position == 0) {
        return getHeaderView(position, convertView, parent);
    } else {
        return getFooterView(position, convertView, parent);
    }
}

private View getFooterView(int position, View convertView, ViewGroup parent) {
    View v = convertView;
    if (v == null) {
        LayoutInflater inflater = mA.getLayoutInflater();
        v = inflater.inflate(R.layout.new_exercise_footer, null);
    }
    return v;
}

private View getHeaderView(int position, View convertView, ViewGroup parent) {
    View v = convertView;
    if (v == null) {
        LayoutInflater inflater = mA.getLayoutInflater();
        v = inflater.inflate(R.layout.new_exercise_header, null);
        Spinner sp = (Spinner) v.findViewById(R.id.spinnerExercises);
        sp.setOnItemSelectedListener(new OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parentView,
                    View selectedItemView, int position, long id) {
                int n = position + 1;
                setNumberOfExercises(n);
            }

            @Override
            public void onNothingSelected(AdapterView<?> parentView) {
                // your code here
            }

        });
    }
    return v;
}

private View getFormView(int position, View convertView, ViewGroup parent) {

    View v = convertView;
    if (v == null) {
        LayoutInflater inflater = mA.getLayoutInflater();
        v = inflater.inflate(R.layout.new_exercise_form, null);
    }
    return v;
}

@Override
public int getItemViewType(int position) {
    if (position > 0 && position < getCount() - 1) {
        return TYPE_FORM;
    } else if (position == 0) {
        return TYPE_HEADER;
    } else {
        return TYPE_FOOTER;
    }
}

@Override
public void updateEntries(Object data) {
    items = (JSONArray) data;
    notifyDataSetChanged();
}

public void next() {
    currentDay++;
    notifyDataSetChanged();
}

public void setNumberOfExercises(int n) {
    numberOfExercises = n;
    notifyDataSetChanged();
}

public void setDays(String string) {
    mDays = string;
}

public void setProgramId(String string) {
    mProgramId = string;
}

}

Oleksii Rudenko
  • 1,277
  • 12
  • 23
  • why are you working with header and footer in the adapter if the listView has a method to set them? – David Dec 28 '12 at 20:18
  • I deal with footer and header in adapter because I may want to put some data inside of header or footer and that data comes with HTTP request and result is passed to the adapter later, after I created it in my activity. – Oleksii Rudenko Dec 28 '12 at 20:26

1 Answers1

3

It looks like you forgot to override getViewTypeCount() which tells the Adapter to use more than one row layout.

However if you only want a header, footer, and row layout then you should use ListView#addHeaderView() and ListView#addFooterView(). Don't try to control the headers and footers is your Adapter, the ListView already does that for you.

Sam
  • 86,580
  • 20
  • 181
  • 179
  • Thanks, it seems I've missed getViewTypeCount(). I added it with return 3; statement. But I get an error: 12-28 21:25:18.163: E/AndroidRuntime(1128): FATAL EXCEPTION: main 12-28 21:25:18.163: E/AndroidRuntime(1128): java.lang.ArrayIndexOutOfBoundsException: length=3; index=3 12-28 21:25:18.163: E/AndroidRuntime(1128): at android.widget.AbsListView$RecycleBin.addScrapView(AbsListView.java:6437) may you suggest something? – Oleksii Rudenko Dec 28 '12 at 20:28
  • 1
    I've managed to solve this with help of http://stackoverflow.com/questions/2596547/arrayindexoutofboundsexception-with-custom-android-adapter-for-multiple-views-in View types must start with 0. That was a problem. – Oleksii Rudenko Dec 28 '12 at 20:35
  • You got it, the Adapter's RecycleBin expects a zero-based index. Glad I could help (with the original question)! – Sam Dec 28 '12 at 20:51