web-dev-qa-db-fra.com

Android En-têtes de liste

J'ai ListView qui a une sorte d'événements sur elle. Les événements sont triés par jour, et j'aimerais avoir l'en-tête avec la date pour chaque jour, puis les événements sont écoutés ci-dessous.

Voici comment je remplis cette 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);

et voici à quoi ressemble ma classe TwoText:

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

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

et voici à quoi ressemble ma classe TwoTextArrayAdapter:

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;
    }
}

et voici 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>

ce que je fais en ce moment, c’est que j’ajoute l’en-tête comme objet de liste ordinaire, mais j’aime bien que ce soit comme en-tête et, dans mon cas, une date.

J'ai ce code dans mon xml pour en-tête:

<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" />

et j'ai essayé de le cacher quand ce n'est pas nécessaire et de le montrer lorsque c'est nécessaire, mais je me suis juste trompé avec le reste de mon code. J'ai essayé quelques autres tutoriels mais ils ont également eu le même effet.

Quelqu'un pourrait-il me guider sur la façon de procéder de cette manière simple?

122
Rohit Malish

Voici comment je le fais, les clés sont getItemViewType et getViewTypeCount dans la classe Adapter. getViewTypeCount renvoie combien de types d'éléments nous avons dans la liste, dans ce cas nous avons un élément d'en-tête et un élément d'événement, donc deux. getItemViewType devrait renvoyer le type de View que nous avons à l'entrée position.

Android se chargera ensuite de vous transmettre automatiquement le bon type de View dans convertView.

Voici à quoi ressemble le résultat du code ci-dessous:

Nous avons d’abord une interface que nos deux types d’éléments de liste implémenteront

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

Ensuite, nous avons un adaptateur qui prend une liste de 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 Meilleur pour les performances .. peut être remarqué lors du défilement

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; } 
}

Ensuite, nous avons les classes implémentation Item et gonflons les présentations correctes. Dans votre cas, vous aurez quelque chose comme une classe Header et une classe ListItem.

   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;
    }

}

Et puis la classe ListItem

    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;
    }

}

Et un simple Activity pour l'afficher

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);
    }

}

Mise en page pour 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>

Mise en page pour 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>

Mise en page pour 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>

Vous pouvez également devenir plus sophistiqué et utiliser ViewHolders, charger des choses de manière asynchrone, ou faire ce que vous voulez.

332
antew

Vous recherchez probablement un ExpandableListView qui possède des en-têtes (groupes) pour séparer les éléments (enfants).

Beau tutoriel sur le sujet: ici .

9
Saito Mea

En guise d'alternative, il existe une bibliothèque Nice tierce partie conçue uniquement pour ce cas d'utilisation. Vous devez alors générer des en-têtes en fonction des données stockées dans l'adaptateur. Ils s'appellent des adaptateurs Rolodex et sont utilisés avec ExpandableListViews. Ils peuvent facilement être personnalisés pour se comporter comme une liste normale avec des en-têtes.

En utilisant les objets Event de l'OP et en sachant que les en-têtes sont basés sur le Date qui lui est associé ... le code ressemblerait à ceci:

L'activité

    //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);

L'adaptateur

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;
    }
}
2
Jay Soyer

Ce que j'ai fait pour que la date (par exemple le 1er décembre 2016) soit en-tête. J'ai utilisé la bibliothèque StickyHeaderListView

https://github.com/emilsjolander/StickyListHeaders

Convertissez la date en long en millis [n'incluez pas l'heure] et transformez-la en id d'en-tête.

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

Voici un exemple de projet , basé sur la réponse détaillée et utile d’antew, qui implémente un ListView avec plusieurs en-têtes, qui intègre des détenteurs de vues pour améliorer les performances de défilement.

Dans ce projet, les objets représentés dans la ListView sont des instances de la classe HeaderItem ou de la classe RowItem, qui sont toutes deux des sous-classes de la classe abstraite Item. Chaque sous-classe de Item correspond à un type de vue différent dans l'adaptateur personnalisé, ItemAdapter. La méthode getView() sur ItemAdapter délègue la création de la vue de chaque élément de la liste à une méthode personnalisée getView() sur HeaderItem ou RowItem, en fonction de Item sous-classe utilisée à la position transmise à la méthode getView() de l'adaptateur. Chaque sous-classe Item fournit son propre détenteur de vue.

Les détenteurs de vue sont implémentés comme suit. Les méthodes getView() sur les sous-classes Item vérifient si l'objet View transmis à la méthode getView() sur ItemAdapter est nul. Si tel est le cas, la présentation appropriée est gonflée et un objet détenteur de vue est instancié et associé à la vue gonflée via View.setTag(). Si l'objet View n'est pas null, un objet détenteur de vue était déjà associé à la vue et le détenteur de la vue est récupéré via View.getTag(). La manière dont les vues sont utilisées peut être vue dans l'extrait de code suivant tiré de 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);
    }
}

L’implémentation complète de ListView est la suivante. Voici le code Java:

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;
    }

}

Il existe également deux dispositions d’éléments de liste, une pour chaque sous-classe d’éléments. Voici la disposition header, utilisée par HeaderItem:

<?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>

Et voici la disposition row, utilisée par RowItem:

<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>

Voici une image d'une partie du ListView résultant:

ListView with multiple headers

1
stevehs17