Android ListView 头部

我有一个 ListView,上面有一些事件。事件是按日排序的,我希望每天都有带日期的头文件,然后在下面听取事件。

下面是我如何填充这个列表:

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

这是我的 TwoText 类的样子:

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


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

这就是我的 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;
}
}

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

what I do at the moment is that I am adding header just as regular list object, but Id like it to be as header and in my case have a date on it.

我在 xml 中有这段代码,用于头部:

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

我试图在不必要的时候隐藏它,在必要的时候显示它,但是我把剩下的代码弄错了。我尝试了一些更多的教程,但他们也有同样的效果。

有人能指导我如何做到这一点容易的方法吗?

133651 次浏览

You probably are looking for an 可扩展列表视图 which has headers (groups) to separate items (childs).

Nice tutorial on the subject: 给你.

我是这样做的,键是 Adapter类中的 GetItemViewTypeGetViewTypeCountgetViewTypeCount返回列表中有多少种类型的项,在这种情况下,我们有一个头项和一个事件项,所以是两个。getItemViewType应该返回什么类型的 View,我们在输入 position

然后 Android 会自动向您传递 convertView中正确类型的 View

下面是下面代码的结果:

首先,我们有一个接口,我们的两个列表项类型将实现该接口

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

Then we have an adapter that takes a list of 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);
}

剪辑 更好的性能. . 可以注意到当滚动

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

然后,我们有类的实现 Item和膨胀正确的布局。在您的例子中,您将有类似于 Header类和 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;
}


}

然后是 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;
}


}

和一个简单的 Activity来显示它

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


}

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>

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>

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>

你也可以使用 ViewHolders,异步加载,或者任何你喜欢的东西。

As an alternative, there's a nice 第三方图书馆 designed just for this use case. Whereby you need to generate headers based on the data being stored in the adapter. They are called Rolodex adapters and are used with ExpandableListViews. They can easily be customized to behave like a normal list with headers.

Using the OP's Event objects and knowing the headers are based on the Date associated with it...the code would look something like this:

The Activity

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

适配器

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

我如何把日期(例如2016年12月1日)作为标题。 我使用了 StickyHeaderListView 库

Https://github.com/emilsjolander/stickylistheaders

将日期转换为长度(不包括时间) ,并将其作为标题 ID。

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

下面是一个示例项目 ,它基于 antew 的详细而有用的答案,实现了一个包含多个标头的 ListView,这些标头包含视图持有者,以提高滚动性能。

在这个项目中,ListView中表示的对象是类 HeaderItem或类 RowItem的实例,两者都是抽象类 Item的子类。Item的每个子类对应于定制适配器 ItemAdapter中的不同视图类型。ItemAdapter上的方法 getView()将每个列表项的视图的创建委托给 HeaderItemRowItem上的个性化 getView()方法,这取决于在传递给适配器上的 getView()方法的位置上使用的 Item子类。每个 Item子类都提供自己的视图持有者。

视图持有者的实现如下。Item子类上的 getView()方法检查传递给 ItemAdapter上的 getView()方法的 View对象是否为空。如果是这样,适当的布局就会膨胀,视图持有者对象就会被实例化,并通过 View.setTag()与膨胀的视图相关联。如果 View对象不为 null,那么视图持有者对象已经与视图关联,并且视图持有者是通过 View.getTag()检索的。视图持有者的使用方式可以在 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);
}
}

ListView 的完整实现如下: 以下是 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;
}


}

还有两个列表项布局,每个 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="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>

下面是 RowItem 使用的布局 row:

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

下面是结果 ListView 的一部分图像:

ListView with multiple headers