Android ListView-Header

122

Ich habe ListView, das eine Art von Ereignissen enthält. Ereignisse werden nach Tag sortiert, und ich möchte für jeden Tag eine Kopfzeile mit Datum haben, und dann hören die Ereignisse unten zu.

So fülle ich diese Liste:

ArrayList<TwoText> crs = new ArrayList<TwoText>();

crs.add(new TwoText("This will be header", event.getDate()));

for (Event event : events) {
    crs.add(new TwoText(event.getStartString() + "-" + event.getEndString(), event.getSubject()));
}

arrayAdapter = new TwoTextArrayAdapter(this, R.layout.my_list_item, crs);
lv1.setAdapter(arrayAdapter);

und so sieht meine Klasse TwoText aus:

public class TwoText {
    public String classID;
    public String state;

    public TwoText(String classID, String state) {
        this.classID = classID;
        this.state = state;
    }
}

und so sieht meine TwoTextArrayAdapter-Klasse aus:

import java.util.ArrayList;
import android.app.Activity;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;

public class TwoTextArrayAdapter extends ArrayAdapter<TwoText> {

    private ArrayList<TwoText> classes;
    private Activity con;
    TextView seperator;

    public TwoTextArrayAdapter(Activity context, int textViewResourceId, ArrayList<TwoText> classes) {
        super(context, textViewResourceId, classes);
        this.con = context;
        this.classes = classes;

    }

    @Override

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

        View v = convertView;

        if (v == null) {

            LayoutInflater vi = (LayoutInflater) con.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

            v = vi.inflate(R.layout.my_list_item, null);

        }

        TwoText user = classes.get(position);

        if (user != null) {

            TextView content1 = (TextView) v.findViewById(R.id.list_content1);

            TextView content2 = (TextView) v.findViewById(R.id.list_content2);

            if (content1 != null) {

                content1.setText(user.classID);
            }   
            if(content2 != null) {

                content2.setText(user.state);
            }
        }
        return v;
    }
}

und das ist my_list_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <TextView
        style="?android:attr/listSeparatorTextViewStyle"
        android:id="@+id/separator"
        android:text="Header"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:background="#757678"
        android:textColor="#f5c227" />

    <LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal" >

        <TextView
            android:id="@+id/list_content1"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_margin="5dip"
            android:clickable="false"
            android:gravity="center"
            android:longClickable="false"
            android:paddingBottom="1dip"
            android:paddingTop="1dip"
            android:text="sample"
            android:textColor="#ff7f1d"
            android:textSize="17dip"
            android:textStyle="bold" />

        <TextView
            android:id="@+id/list_content2"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_margin="5dip"
            android:clickable="false"
            android:gravity="center"
            android:linksClickable="false"
            android:longClickable="false"
            android:paddingBottom="1dip"
            android:paddingTop="1dip"
            android:text="sample"
            android:textColor="#6d6d6d"
            android:textSize="17dip" />
    </LinearLayout>

</LinearLayout>

Was ich im Moment mache, ist, dass ich Header nur als reguläres Listenobjekt hinzufüge, aber ich möchte, dass es als Header ist und in meinem Fall ein Datum darauf hat.

Ich habe diesen Code in meiner XML für den Header:

<TextView
        style="?android:attr/listSeparatorTextViewStyle"
        android:id="@+id/separator"
        android:text="Header"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:background="#757678"
        android:textColor="#f5c227" />

und ich habe versucht, es zu verstecken, wenn es nicht notwendig ist, und es zu zeigen, wenn es notwendig ist, aber ich habe nur den Rest meines Codes durcheinander gebracht. Ich habe noch ein paar Tutorials ausprobiert, aber sie hatten auch den gleichen Effekt.

Könnte mich jemand anleiten, wie man das so einfach macht?

Rohit Malish
quelle

Antworten:

334

So mache ich das: Die Schlüssel sind getItemViewType und getViewTypeCount in der AdapterKlasse. getViewTypeCountGibt zurück, wie viele Arten von Elementen wir in der Liste haben. In diesem Fall haben wir ein Header-Element und ein Ereigniselement, also zwei. getItemViewTypesollte zurückgeben, welche Art von Viewwir am Eingang haben position.

Android kümmert sich dann darum, Ihnen die richtige Art von ViewIn zu übergebenconvertView automatisch.

So sieht das Ergebnis des folgenden Codes aus:

Zuerst haben wir eine Schnittstelle, die unsere beiden Listenelementtypen implementieren

public interface Item {
    public int getViewType();
    public View getView(LayoutInflater inflater, View convertView);
}

Dann haben wir einen Adapter, der eine Liste von Item

public class TwoTextArrayAdapter extends ArrayAdapter<Item> {
    private LayoutInflater mInflater;

    public enum RowType {
        LIST_ITEM, HEADER_ITEM
    }

    public TwoTextArrayAdapter(Context context, List<Item> items) {
        super(context, 0, items);
        mInflater = LayoutInflater.from(context);
    }

    @Override
    public int getViewTypeCount() {
        return RowType.values().length;

    }

    @Override
    public int getItemViewType(int position) {
        return getItem(position).getViewType();
    }
@Override
public View getView(int position, View convertView, ViewGroup parent) {
   return getItem(position).getView(mInflater, convertView);
}

EDIT Better For Performance .. kann beim Scrollen bemerkt werden

private static final int TYPE_ITEM = 0; 
private static final int TYPE_SEPARATOR = 1; 

public View getView(int position, View convertView, ViewGroup parent)  {
    ViewHolder holder = null;
    int rowType = getItemViewType(position);
    View View;
    if (convertView == null) {
        holder = new ViewHolder();
        switch (rowType) {
            case TYPE_ITEM:
                convertView = mInflater.inflate(R.layout.task_details_row, null);
                holder.View=getItem(position).getView(mInflater, convertView);
                break;
            case TYPE_SEPARATOR:
                convertView = mInflater.inflate(R.layout.task_detail_header, null);
                holder.View=getItem(position).getView(mInflater, convertView);
                break;
        }
        convertView.setTag(holder);
    }
    else
    {
        holder = (ViewHolder) convertView.getTag();
    }
    return convertView; 
} 

public static class ViewHolder {
    public  View View; } 
}

Dann haben wir Klassen das Gerät Itemund blasen die richtigen Layouts auf. In Ihrem Fall haben Sie so etwas wie eine HeaderKlasse und eine ListItemKlasse.

   public class Header implements Item {
    private final String         name;

    public Header(String name) {
        this.name = name;
    }

    @Override
    public int getViewType() {
        return RowType.HEADER_ITEM.ordinal();
    }

    @Override
    public View getView(LayoutInflater inflater, View convertView) {
        View view;
        if (convertView == null) {
            view = (View) inflater.inflate(R.layout.header, null);
            // Do some initialization
        } else {
            view = convertView;
        }

        TextView text = (TextView) view.findViewById(R.id.separator);
        text.setText(name);

        return view;
    }

}

Und dann die ListItemKlasse

    public class ListItem implements Item {
    private final String         str1;
    private final String         str2;

    public ListItem(String text1, String text2) {
        this.str1 = text1;
        this.str2 = text2;
    }

    @Override
    public int getViewType() {
        return RowType.LIST_ITEM.ordinal();
    }

    @Override
    public View getView(LayoutInflater inflater, View convertView) {
        View view;
        if (convertView == null) {
            view = (View) inflater.inflate(R.layout.my_list_item, null);
            // Do some initialization
        } else {
            view = convertView;
        }

        TextView text1 = (TextView) view.findViewById(R.id.list_content1);
        TextView text2 = (TextView) view.findViewById(R.id.list_content2);
        text1.setText(str1);
        text2.setText(str2);

        return view;
    }

}

Und einfach Activityanzuzeigen

public class MainActivity extends ListActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        List<Item> items = new ArrayList<Item>();
        items.add(new Header("Header 1"));
        items.add(new ListItem("Text 1", "Rabble rabble"));
        items.add(new ListItem("Text 2", "Rabble rabble"));
        items.add(new ListItem("Text 3", "Rabble rabble"));
        items.add(new ListItem("Text 4", "Rabble rabble"));
        items.add(new Header("Header 2"));
        items.add(new ListItem("Text 5", "Rabble rabble"));
        items.add(new ListItem("Text 6", "Rabble rabble"));
        items.add(new ListItem("Text 7", "Rabble rabble"));
        items.add(new ListItem("Text 8", "Rabble rabble"));

        TwoTextArrayAdapter adapter = new TwoTextArrayAdapter(this, items);
        setListAdapter(adapter);
    }

}

Layout für R.layout.header

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

    <TextView
        style="?android:attr/listSeparatorTextViewStyle"
        android:id="@+id/separator"
        android:text="Header"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:background="#757678"
        android:textColor="#f5c227" />

</LinearLayout>

Layout für R.layout.my_list_item

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

    <TextView
        android:id="@+id/list_content1"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_margin="5dip"
        android:clickable="false"
        android:gravity="center"
        android:longClickable="false"
        android:paddingBottom="1dip"
        android:paddingTop="1dip"
        android:text="sample"
        android:textColor="#ff7f1d"
        android:textSize="17dip"
        android:textStyle="bold" />

    <TextView
        android:id="@+id/list_content2"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_margin="5dip"
        android:clickable="false"
        android:gravity="center"
        android:linksClickable="false"
        android:longClickable="false"
        android:paddingBottom="1dip"
        android:paddingTop="1dip"
        android:text="sample"
        android:textColor="#6d6d6d"
        android:textSize="17dip" />

</LinearLayout>

Layout für R.layout.activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >

    <ListView
        android:id="@android:id/list"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" />

</RelativeLayout>

Sie können auch schicker werden und Dinge verwenden ViewHolders, asynchron laden oder was auch immer Sie möchten.

antew
quelle
5
Ihre Lösung hat Ihnen hier gefallen, aber da Sie einen ArrayAdapter erweitern, sollten Sie Ihre eigene Liste von Elementen nicht nachverfolgen. Verwenden Sie einfach die intern verfolgte in ArrayAdapter. Andernfalls verdoppeln Sie die Speicherkapazität, um Ihre Artikel zu speichern
Jay Soyer
3
Hervorragende Lösung. Ich würde dies nur dann zum Adapter hinzufügen, wenn die Header nicht anklickbar sind (was in einem Konstruktor festgelegt werden könnte). @Override public boolean isEnabled (int position) {return (getItem (position) .getViewType () == RowType.LIST_ITEM .Ordinal()); }
Dwbrito
2
Die Zeilen werden zufällig ausgewählt, wenn die Listenansicht gescrollt wird. könnte jemand bitte führen
i_raqz
1
Warum kann Google dies nicht einfach mit nur 3 Codezeilen erreichen?
Ojonugwa Jude Ochalifu
4
Diese Antwort ist eine der besten Antworten, die ich auf SO gefunden habe - klar, präzise und gut erklärt. Ich hatte jedoch ein Problem mit den Ergebnissen der ListView in einer halbzufälligen Reihenfolge (der erste Header und die Elemente waren in Ordnung, die folgenden waren durcheinander). Ich konnte herausfinden, wo das Problem liegt. Der Codeblock unter "EDIT Better For Performance .. kann beim Scrollen bemerkt werden" hat es für mich durcheinander gebracht - das Entfernen dieses Codes aus der Custom ArrayAdapter-Klasse hat das Problem für mich behoben. Ich rate jedem, der zufällige Ergebnisse erzielt, dies zu versuchen. Vielen Dank für eine hervorragende Antwort. Hat mir wirklich geholfen!
Blueprintchris
9

Sie suchen wahrscheinlich nach einer ExpandableListView mit Überschriften (Gruppen), um Elemente (untergeordnete Elemente) zu trennen.

Schönes Tutorial zum Thema: hier .

Saito Mea
quelle
Ich möchte nicht, dass sie erweiterbar sind
Rohit Malish
Wenn dies das einzige Problem ist, können Sie die onItemClick-Methode überschreiben, um zu verhindern, dass die Ansichten erweitert / reduziert werden.
Saito Mea
Aber ich brauche sie immer noch, um für andere Zwecke anklickbar zu sein
Rohit Malish
Ähm ... Ich wollte eigentlich sagen onGroupClick, dass nur das Klicken auf "Header" behandelt wird und Sie das Klicken oder ähnliches nicht deaktivieren müssen. Brechen Sie einfach jede Kollapsaktion ab und setzen Sie alle Gruppen von Anfang an auf "Erweitert".
Saito Mea
Ich bin damit einverstanden, dass dies ExpandableListViewin den meisten Fällen am besten ist. Ich habe jedoch eine Situation, in der ich manchmal eine flache Liste und zu anderen Zeiten in meiner Aktivität eine Liste mit Überschriften anzeigen möchte. Leider ExpandableListAdaptererweitert die ListAdapterSchnittstelle die Schnittstelle nicht, so dass ich für Polymorphismus gezwungen bin, die Lösung von @ antew zu verwenden.
Tytk
3

Alternativ gibt es eine schöne Bibliothek von Drittanbietern, die speziell für diesen Anwendungsfall entwickelt wurde. Dabei müssen Sie Header basierend auf den im Adapter gespeicherten Daten generieren. Sie werden Rolodex-Adapter genannt und mit verwendet ExpandableListViews. Sie können einfach so angepasst werden, dass sie sich wie eine normale Liste mit Überschriften verhalten.

Wenn Sie die EventObjekte des OP verwenden und wissen, dass die Header auf den Datezugehörigen basieren, würde der Code ungefähr so ​​aussehen:

Die Aktivität

    //There's no need to pre-compute what the headers are. Just pass in your List of objects. 
    EventDateAdapter adapter = new EventDateAdapter(this, mEvents);
    mExpandableListView.setAdapter(adapter);

Der Adapter

private class EventDateAdapter extends NFRolodexArrayAdapter<Date, Event> {

    public EventDateAdapter(Context activity, Collection<Event> items) {
        super(activity, items);
    }

    @Override
    public Date createGroupFor(Event childItem) {
        //This is how the adapter determines what the headers are and what child items belong to it
        return (Date) childItem.getDate().clone();
    }

    @Override
    public View getChildView(LayoutInflater inflater, int groupPosition, int childPosition,
                             boolean isLastChild, View convertView, ViewGroup parent) {
        //Inflate your view

        //Gets the Event data for this view
        Event event = getChild(groupPosition, childPosition);

        //Fill view with event data
    }

    @Override
    public View getGroupView(LayoutInflater inflater, int groupPosition, boolean isExpanded,
                             View convertView, ViewGroup parent) {
        //Inflate your header view

        //Gets the Date for this view
        Date date = getGroup(groupPosition);

        //Fill view with date data
    }

    @Override
    public boolean hasAutoExpandingGroups() {
        //This forces our group views (headers) to always render expanded.
        //Even attempting to programmatically collapse a group will not work.
        return true;
    }

    @Override
    public boolean isGroupSelectable(int groupPosition) {
        //This prevents a user from seeing any touch feedback when a group (header) is clicked.
        return false;
    }
}
Jay Soyer
quelle
1

Was ich getan habe, um das Datum (z. B. 01. Dezember 2016) als Header zu erstellen. Ich habe die StickyHeaderListView-Bibliothek verwendet

https://github.com/emilsjolander/StickyListHeaders

Konvertieren Sie das Datum in Millis in Long [ohne Uhrzeit] und legen Sie es als Header-ID fest.

@Override
public long getHeaderId(int position) {
    return <date in millis>;
}
Ban Daculan
quelle
1

Hier ist ein Beispielprojekt , das auf der detaillierten und hilfreichen Antwort von antew basiert und ein ListViewmit mehreren Überschriften implementiert , das Ansichtshalter enthält, um die Bildlaufleistung zu verbessern.

In diesem Projekt sind die in dargestellten Objekte ListViewInstanzen der Klasse HeaderItemoder der Klasse RowItem, die beide Unterklassen der abstrakten Klasse sind Item. Jede Unterklasse von Itementspricht einem anderen Ansichtstyp im benutzerdefinierten Adapter ItemAdapter. Die Methode getView()on ItemAdapterdelegiert die Erstellung der Ansicht für jedes Listenelement an eine individualisierte getView()Methode für entweder HeaderItemoder RowItem, abhängig von derItem Unterklasse, die an der Position verwendet wird, die an die getView()Methode auf dem Adapter übergeben wurde. JederItem Unterklasse bietet einen eigenen Ansichtshalter.

Die Ansichtsinhaber werden wie folgt implementiert. Die getView()Methoden in den ItemUnterklassen prüfen, ob das ViewObjekt, das an die getView()Methode übergeben wurde, ItemAdapternull ist. In diesem Fall wird das entsprechende Layout aufgeblasen und ein Ansichtshalterobjekt wird instanziiert und der aufgeblasenen Ansicht über zugeordnet View.setTag(). Wenn das ViewObjekt nicht null ist, wurde der Ansicht bereits ein Ansichtsinhaberobjekt zugeordnet, und der Ansichtsinhaber wird über abgerufen View.getTag(). Die Art und Weise, wie die Ansichtshalter verwendet werden, ist im folgenden Codeausschnitt von zu sehen HeaderItem:

@Override
View getView(LayoutInflater i, View v) {
    ViewHolder h;
    if (v == null) {
        v = i.inflate(R.layout.header, null);
        h = new ViewHolder(v);
        v.setTag(h);
    } else {
        h = (ViewHolder) v.getTag();
    }
    h.category.setText(text());
    return v;
}

private class ViewHolder {
    final TextView category;

    ViewHolder(View v) {
        category = v.findViewById(R.id.category);
    }
}

Die vollständige Implementierung der ListView folgt. Hier ist der Java-Code:

import android.app.ListActivity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class MainActivity extends ListActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setListAdapter(new ItemAdapter(getItems()));
    }

    class ItemAdapter extends ArrayAdapter<Item> {
        final private List<Class<?>> viewTypes;

        ItemAdapter(List<Item> items) {
            super(MainActivity.this, 0, items);
            if (items.contains(null))
                throw new IllegalArgumentException("null item");
            viewTypes = getViewTypes(items);
        }

        private List<Class<?>> getViewTypes(List<Item> items) {
            Set<Class<?>> set = new HashSet<>();
            for (Item i : items) 
                set.add(i.getClass());
            List<Class<?>> list = new ArrayList<>(set);
            return Collections.unmodifiableList(list);
        }

        @Override
        public int getViewTypeCount() {
            return viewTypes.size();
        }

        @Override
        public int getItemViewType(int position) {
            Item t = getItem(position);
            return viewTypes.indexOf(t.getClass());
        }

        @Override
        public View getView(int position, View v, ViewGroup unused) {
            return getItem(position).getView(getLayoutInflater(), v);
        }
    }

    abstract private class Item {
        final private String text;

        Item(String text) {
            this.text = text;
        }

        String text() { return text; }

        abstract View getView(LayoutInflater i, View v);
    }

    private class HeaderItem extends Item {
        HeaderItem(String text) {
            super(text);
        }

        @Override
        View getView(LayoutInflater i, View v) {
            ViewHolder h;
            if (v == null) {
                v = i.inflate(R.layout.header, null);
                h = new ViewHolder(v);
                v.setTag(h);
            } else {
                h = (ViewHolder) v.getTag();
            }
            h.category.setText(text());
            return v;
        }

        private class ViewHolder {
            final TextView category;

            ViewHolder(View v) {
                category = v.findViewById(R.id.category);
            }
        }
    }

    private class RowItem extends Item {
        RowItem(String text) {
            super(text);
        }

        @Override
        View getView(LayoutInflater i, View v) {
            ViewHolder h;
            if (v == null) {
                v = i.inflate(R.layout.row, null);
                h = new ViewHolder(v);
                v.setTag(h);
            } else {
                h = (ViewHolder) v.getTag();
            }
            h.option.setText(text());
            return v;
        }

        private class ViewHolder {
            final TextView option;

            ViewHolder(View v) {
                option = v.findViewById(R.id.option);
            }
        }
    }

    private List<Item> getItems() {
        List<Item> t = new ArrayList<>();
        t.add(new HeaderItem("Header 1"));
        t.add(new RowItem("Row 2"));
        t.add(new HeaderItem("Header 3"));
        t.add(new RowItem("Row 4"));

        t.add(new HeaderItem("Header 5"));
        t.add(new RowItem("Row 6"));
        t.add(new HeaderItem("Header 7"));
        t.add(new RowItem("Row 8"));

        t.add(new HeaderItem("Header 9"));
        t.add(new RowItem("Row 10"));
        t.add(new HeaderItem("Header 11"));
        t.add(new RowItem("Row 12"));

        t.add(new HeaderItem("Header 13"));
        t.add(new RowItem("Row 14"));
        t.add(new HeaderItem("Header 15"));
        t.add(new RowItem("Row 16"));

        t.add(new HeaderItem("Header 17"));
        t.add(new RowItem("Row 18"));
        t.add(new HeaderItem("Header 19"));
        t.add(new RowItem("Row 20"));

        t.add(new HeaderItem("Header 21"));
        t.add(new RowItem("Row 22"));
        t.add(new HeaderItem("Header 23"));
        t.add(new RowItem("Row 24"));

        t.add(new HeaderItem("Header 25"));
        t.add(new RowItem("Row 26"));
        t.add(new HeaderItem("Header 27"));
        t.add(new RowItem("Row 28"));
        t.add(new RowItem("Row 29"));
        t.add(new RowItem("Row 30"));

        t.add(new HeaderItem("Header 31"));
        t.add(new RowItem("Row 32"));
        t.add(new HeaderItem("Header 33"));
        t.add(new RowItem("Row 34"));
        t.add(new RowItem("Row 35"));
        t.add(new RowItem("Row 36"));

        return t;
    }

}

Es gibt auch zwei Listenelementlayouts, eines für jede Elementunterklasse. Hier ist das headervon HeaderItem verwendete Layout:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="#FFAAAAAA"
    >
    <TextView
        android:id="@+id/category"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="4dp"
        android:textColor="#FF000000"
        android:textSize="20sp"
        android:textStyle="bold"
        />
 </LinearLayout>

Und hier ist das rowvon RowItem verwendete Layout:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:minHeight="?android:attr/listPreferredItemHeight"
    >
    <TextView
        android:id="@+id/option"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="15sp"
        />
</LinearLayout>

Hier ist ein Bild eines Teils der resultierenden ListView:

ListView mit mehreren Headern

stevehs17
quelle