Как добавить несколько заголовков в один ListView с помощью addHeaderView()?

Можно ли использовать Android addHeaderView() для добавления нескольких заголовков в один список ListView? Может ли кто-нибудь дать пример того, как это сделать?

Я смог выполнить то, что хотел, манипулируя классом IconicAdapter... есть ли какая-то причина, почему я не должен этого делать? Я чувствую, что это может быть изменено для более продвинутых реализаций. В моем случае я знаю, что у меня будет два раздела с заголовком + 2 строки в каждом разделе.

class IconicAdapter extends ArrayAdapter<String> {
    IconicAdapter() {
        super(ContactTabProfileResource.this, R.layout.row_iconic, mArrayList);
    }


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

        LayoutInflater inflater = getLayoutInflater();
        View row = null;

        if(position == 1 || position == 5) { // phone 
            row = inflater.inflate(R.layout.row_iconic, parent, false);
            TextView label =(TextView)row.findViewById(R.id.label);
            label.setText(mArrayList.get(position));
            ImageView icon = (ImageView)row.findViewById(R.id.rowicon);
            icon.setImageResource(R.drawable.icon_phone);
        } else if (position == 2 || position == 6) { // email
            row = inflater.inflate(R.layout.row_iconic, parent, false);
            TextView label =(TextView)row.findViewById(R.id.label);
            label.setText(mArrayList.get(position));
            ImageView icon = (ImageView)row.findViewById(R.id.rowicon);
            icon.setImageResource(R.drawable.icon_email);
        } else if (position == 0 || position == 4) { // section header
            row = inflater.inflate(R.layout.row_header, parent, false);
            TextView label =(TextView)row.findViewById(R.id.label);
            label.setText(mArrayList.get(position));
            label.setBackgroundColor(Color.GRAY);   
        } else if (position == 3) { // section divider
            row = inflater.inflate(R.layout.row_header, parent, false);
            TextView label =(TextView)row.findViewById(R.id.label);
            label.setText(" ");
        }

        return(row);

    }
}

Затем я создал два разных XML-макета. row_header.xml для строк заголовка, а row_iconic.xml - для строк без заголовка, которые содержат значок.

row_header.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:orientation="horizontal"
  android:gravity="right"
>    

  <TextView
    android:id="@+id/label"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:textSize="20sp"
    android:paddingTop="10dp"
    android:paddingBottom="10dp"
    android:paddingRight="10dp"
    android:paddingLeft="10px"
    android:gravity="left"
    android:textStyle="bold"
    />    
</LinearLayout>

row_iconic.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:orientation="horizontal"
  android:gravity="right"
>    

  <TextView
    android:id="@+id/label"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:textSize="16sp"
    android:paddingTop="10dp"
    android:paddingBottom="10dp"
    android:paddingRight="10dp"
    android:paddingLeft="44px"
    />  
    <ImageView
    android:id="@+id/rowicon"
    android:layout_width="40dp"
    android:paddingRight="10dp"
    android:paddingTop="10dp"
    android:layout_height="30dp"        
    android:src="@drawable/icon"
    />    
</LinearLayout>

Ответы

Ответ 1

Что вы хотите, чтобы разработчики Android могли вызвать разделитель списков или подзаголовок ( "заголовки" и "нижние колонтитулы" живут только в верхней или нижней части списка). Суть в том, как вы можете это сделать, - использовать ListAdapter, который обертывает другие ListAdapters и достаточно умен, чтобы возвращать тип заголовка для определенных строк и отслеживать смещения, или обертывает эти представления разделителей в их собственных мини-адаптерах.

Взгляните на Марка Мерфи SectionedListAdapter, GPL, который использует первый подход (на основе кода Джеффа Шарки) или его MergeAdapter и см. этот вопрос SO.

Это далеко от грациозной обработки подзаголовков смарт-списка на iPhone, но довольно просто использовать MergeAdapter и очень гибко, как только вы обернете свою голову вокруг того, что происходит внутри адаптеров.

Ответ 2

Если вы хотите вернуть разные макеты (например, элементы и заголовки), вы должны использовать getItemViewType(int position). Таким образом, ваш код адаптера должен выглядеть следующим образом:

private static final int TYPE_HEADER = 0;
private static final int TYPE_ICONIC = 1;

@Override
public int getViewTypeCount() {
    return 2;   // we have two types, so just return 2
}

@Override
public int getItemViewType(int position) {
    // TODO: return TYPE_HEADER here if this position is a header, otherwise return TYPE_ICONIC
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    // TODO: return the appropriate layout
    // hint: you might call getItemViewType(position) yourself here to know of which type the layout is
}

В принципе, как это работает. Чтобы гарантировать, что заголовки не могут быть выбраны, вы можете выбросить ArrayAdapter и использовать ListAdapter вместо этого, переопределив isEnabled(int position), чтобы вернуть false для ваших заголовков,; -)

Ответ 3

Вы можете добавить столько заголовков, сколько хотите, вызвав addHeaderView() несколько раз. Вы должны сделать это, прежде чем устанавливать адаптер в список.

Ответ 4

Вы также можете использовать этот класс в качестве примера. Он поддерживает "разделы" и "действия", где "разделы" представляют собой просто группы "действия" (элементы), они имеют не кликабельный и неизбираемый заголовок элемента. "Действия" могут указывать обработчик onClick, который будет вызываться, когда это "действие" будет нажато, если оно существует. Он также содержит ImageLoader для получения изображений для "действий" из Интернета.