Android ListView 标头
Posted
技术标签:
【中文标题】Android ListView 标头【英文标题】:Android ListView headers 【发布时间】:2012-11-15 10:50:06 【问题描述】:我的 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_
android:layout_
android:orientation="vertical" >
<TextView
style="?android:attr/listSeparatorTextViewStyle"
android:id="@+id/separator"
android:text="Header"
android:layout_
android:layout_
android:background="#757678"
android:textColor="#f5c227" />
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_
android:layout_
android:orientation="horizontal" >
<TextView
android:id="@+id/list_content1"
android:layout_
android:layout_
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_
android:layout_
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>
我现在所做的是将标题添加为常规列表对象,但我希望它作为标题并且在我的情况下有一个日期。
我的 xml 中有此代码作为标题:
<TextView
style="?android:attr/listSeparatorTextViewStyle"
android:id="@+id/separator"
android:text="Header"
android:layout_
android:layout_
android:background="#757678"
android:textColor="#f5c227" />
我尝试在不必要时隐藏它并在必要时显示它,但我只是弄乱了我的其余代码。我尝试了更多教程,但它们也有相同的效果。
谁能指导我如何做到这一点?
【问题讨论】:
【参考方案1】:我是这样做的,键是Adapter
类中的getItemViewType 和getViewTypeCount。 getViewTypeCount
返回列表中有多少类型的项目,在这种情况下,我们有一个标题项目和一个事件项目,所以两个。 getItemViewType
应该返回我们在输入 position
处拥有的 View
的类型。
Android 会自动在convertView
中为您传递正确类型的View
。
以下代码的结果如下所示:
首先我们有一个接口,我们的两个列表项类型将实现
public interface Item
public int getViewType();
public View getView(LayoutInflater inflater, View convertView);
然后我们有一个适配器,它接受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);
编辑 Better For Performance.. 可以在滚动时注意到
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_
android:layout_
android:orientation="horizontal" >
<TextView
style="?android:attr/listSeparatorTextViewStyle"
android:id="@+id/separator"
android:text="Header"
android:layout_
android:layout_
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_
android:layout_
android:orientation="horizontal" >
<TextView
android:id="@+id/list_content1"
android:layout_
android:layout_
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_
android:layout_
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_
android:layout_
tools:context=".MainActivity" >
<ListView
android:id="@android:id/list"
android:layout_
android:layout_ />
</RelativeLayout>
您还可以更高级地使用ViewHolders
、异步加载内容或任何您喜欢的方式。
【讨论】:
喜欢您的解决方案,但由于您正在扩展 ArrayAdapter,因此您不应跟踪自己的项目列表。只需使用 ArrayAdapter 中的内部跟踪。否则,存储物品的内存量会增加一倍 优秀的解决方案。如果标题不可点击(可以在构造函数中设置),我只会将此添加到适配器中 @Override public boolean isEnabled(int position) return (getItem(position).getViewType() == RowType.LIST_ITEM .ordinal()); 滚动列表视图时行会随机化吗?谁能指导一下 为什么 Google 不能只用 3 行代码实现这一目标? 这个答案是我在 SO 上找到的最佳答案之一 - 清晰、简洁且解释清楚。但是,我确实遇到了 ListView 的结果是半随机顺序的问题(第一个标题和项目还可以,后面的项目搞砸了),我设法找出了问题所在。 'EDIT Better For Performance.. 可以在滚动时注意到'下面的代码块让我搞砸了 - 从自定义 ArrayAdapter 类中删除它为我解决了这个问题。我建议每个得到随机结果的人都试试这个。不过,感谢您的出色回答。真的帮了我大忙!【参考方案2】:您可能正在寻找一个ExpandableListView,它具有用于分隔项目(子项)的标题(组)。
关于这个主题的好教程:here。
【讨论】:
我不希望它们可扩展 如果这是唯一的问题,您可以覆盖 onItemClick 方法以防止展开/折叠视图。 但我仍然需要它们可以点击以用于其他目的 Err...我实际上是想说onGroupClick
只处理点击“标题”,你不需要禁用点击或类似的东西,只需取消任何折叠操作并设置所有要从头开始扩展的组。
我同意ExpandableListView
在大多数情况下是最好的。但是,我有时想在我的活动中显示一个平面列表,并在其他时间显示一个带有标题的列表。可悲的是,ExpandableListAdapter
接口没有扩展ListAdapter
接口,因此对于多态性,我不得不使用@antew 的解决方案。【参考方案3】:
作为替代方案,有一个很好的3rd party library 专门为此用例设计。因此,您需要根据存储在适配器中的数据生成标头。它们被称为 Rolodex 适配器,与ExpandableListViews
一起使用。它们可以很容易地被定制成一个带有标题的普通列表。
使用 OP 的 Event
对象并知道标头基于与之关联的 Date
...代码如下所示:
活动
//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;
【讨论】:
【参考方案4】:我做了什么来将日期(例如 2016 年 12 月 1 日)作为标题。 我使用了 StickyHeaderListView 库
https://github.com/emilsjolander/StickyListHeaders
将日期转换为以毫秒为单位的长[不包括时间]并将其作为标题ID。
@Override
public long getHeaderId(int position)
return <date in millis>;
【讨论】:
【参考方案5】:Here is a sample project,基于 antew 的详细而有用的答案,实现了一个带有多个标题的 ListView
,其中包含视图持有者以提高滚动性能。
在这个项目中,ListView
中表示的对象是类HeaderItem
或类RowItem
的实例,它们都是抽象类Item
的子类。 Item
的每个子类对应于自定义适配器中的不同视图类型ItemAdapter
。 ItemAdapter
上的 getView()
方法将每个列表项的视图创建委托给 HeaderItem
或 RowItem
上的个性化 getView()
方法,具体取决于传递给getView()
适配器上的方法。每个Item
子类都提供了自己的视图持有者。
视图持有者的实现如下。 Item
子类上的 getView()
方法检查传递给 ItemAdapter
上的 getView()
方法的 View
对象是否为空。如果是这样,适当的布局被膨胀,并且视图持有者对象被实例化并通过View.setTag()
与膨胀的视图相关联。如果View
对象不为空,则视图持有者对象已经与视图相关联,并且通过View.getTag()
检索视图持有者。视图持有者的使用方式可以在HeaderItem
的以下代码sn-p中看到:
@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 子类一个。这是 HeaderItem 使用的布局header
:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_
android:layout_
android:background="#FFAAAAAA"
>
<TextView
android:id="@+id/category"
android:layout_
android:layout_
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_
android:layout_
android:minHeight="?android:attr/listPreferredItemHeight"
>
<TextView
android:id="@+id/option"
android:layout_
android:layout_
android:textSize="15sp"
/>
</LinearLayout>
这是生成的 ListView 的一部分的图像:
【讨论】:
以上是关于Android ListView 标头的主要内容,如果未能解决你的问题,请参考以下文章
android问题及其解决-优化listView卡顿和怎样禁用ListView的fling