java android listview header android-arrayadapter

java - Encabezados ListView de Android



header listview android (5)

Tengo ListView que tiene algún tipo de evento. Los eventos se ordenan por día, y me gustaría tener un encabezado con fecha para cada día, y luego los eventos se escuchan a continuación.

Así es como llené esa lista:

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

y así es como se ve mi clase TwoText:

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

y así es como se ve mi clase 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; } }

y este es 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>

lo que hago en este momento es que estoy agregando encabezado como objeto de lista normal, pero me gustaría que fuera como encabezado y en mi caso tenga una fecha.

Tengo este código en mi xml para encabezado:

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

y traté de esconderlo cuando es innecesario y de mostrarlo cuando sea necesario, pero simplemente eché a perder el resto de mi código. Probé algunos tutoriales más, pero también tuvieron el mismo efecto.

¿Alguien podría guiarme sobre cómo hacerlo de esa manera fácil?


Así es como lo hago, las claves son getItemViewType y getViewTypeCount en la clase Adapter . getViewTypeCount devuelve cuántos tipos de elementos tenemos en la lista, en este caso tenemos un elemento de encabezado y un elemento de evento, por lo que dos. getItemViewType debe devolver qué tipo de View tenemos en la position entrada.

Android se encargará de pasarle el tipo correcto de View en convertView automáticamente.

Aquí cuál es el resultado del siguiente código:

Primero tenemos una interfaz que nuestros dos tipos de elementos de lista implementarán

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

Entonces tenemos un adaptador que toma una lista 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 Better For Performance ... se puede notar cuando se desplaza

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

Luego tenemos clases para implementar el Item e inflar los diseños correctos. En su caso, tendrá algo así como una clase de Header y una clase 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; } }

Y luego la clase 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; } }

Y una Activity simple para mostrarlo

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

Diseño para 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>

Diseño para 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>

Diseño para 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>

También puedes ser más elegante y usar ViewHolders , cargar cosas de forma asíncrona o lo que quieras.


Como alternativa, hay una buena biblioteca de terceros diseñada solo para este caso de uso. Por lo que debe generar encabezados en función de los datos almacenados en el adaptador. Se llaman adaptadores Rolodex y se usan con ExpandableListViews . Se pueden personalizar fácilmente para que se comporten como una lista normal con encabezados.

Usando los objetos de Event de OP y sabiendo que los encabezados se basan en la Date asociada ... el código sería algo como esto:

La actividad

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

El adaptador

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


Lo que hice para hacer que la Fecha (por ejemplo, 01 de diciembre de 2016) como encabezado. Utilicé la biblioteca StickyHeaderListView

https://github.com/emilsjolander/StickyListHeaders

Convierta la fecha a long en milis [no incluya la hora] y hágalo como Id del encabezado.

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


Probablemente esté buscando una ExpandableListView que tenga encabezados (grupos) para separar elementos (childs).

Buen tutorial sobre el tema: here .


Aquí hay un proyecto de muestra , basado en la respuesta detallada y útil de antew, que implementa un ListView con múltiples encabezados que incorpora los titulares de vista para mejorar el rendimiento del desplazamiento.

En este proyecto, los objetos representados en ListView son instancias de la clase HeaderItem o de la clase RowItem , y ambas son subclases de la clase abstracta Item . Cada subclase de Item corresponde a un tipo de vista diferente en el adaptador personalizado, ItemAdapter . El método getView() en ItemAdapter delega la creación de la vista para cada elemento de la lista en un método getView() individualizado en HeaderItem o RowItem , dependiendo de la subclase Item usada en la posición pasada al método getView() en el adaptador. Cada subclase de Item proporciona su propio titular de vista.

Los titulares de vista se implementan de la siguiente manera. Los métodos getView() en las subclases de Item comprueban si el objeto View que se pasó al método getView() en ItemAdapter es nulo. Si es así, el diseño apropiado está inflado, y un objeto del titular de la vista se View.setTag() instancia y se asocia con la vista inflada a través de View.setTag() . Si el objeto View no es nulo, entonces un objeto del titular de la vista ya estaba asociado con la vista, y el titular de la vista se recupera a través de View.getTag() . La forma en que se utilizan los titulares de vista se puede ver en el siguiente fragmento de código 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); } }

La implementación completa de ListView sigue. Aquí está el código de 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; } }

También hay dos diseños de elementos de lista, uno para cada subclase de artículo. Aquí está el header diseño, utilizado por 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>

Y aquí está la row diseño, utilizada por 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>

Aquí hay una imagen de una porción del ListView resultante: