摘自《Android 5.0开发范例代码大全(第四版)》
问题:
需要创建一个有若干内容的列表,其中每一节的顶部都有各自的头部。
解决方案:
我们可以通过构建自定义列表适配器来实现此效果,此适配器利用了对多种视图类型的支持。适配器依赖getViewTypeCount()和getItemViewType()来确定将多种视图用作列表中的行。在大多数情况下,如果所有行类型相同,就忽略上述方法。然而,在此可以使用这些回调为头部行和内容行定义独特的类型。
实现机制:
效果:
首先定义SectionItem数据结构,用于表示列表中的每一节。此项将保存节标题以及此标题下列出的数据数组子集。
public class SectionItem<T> { private String mTitle; private T[] mItems; public SectionItem(String title, T[] items) { if (title == null) title = ""; mTitle = title; mItems = items; } public String getTitle() { return mTitle; } public T getItem(int position) { return mItems[position]; } public int getCount() { //为节标题包含额外的项 return (mItems == null ? 1 : 1 + mItems.length); } @Override public boolean equals(Object object) { //如果两个节有相同的标题,则它们相等 if (object != null && object instanceof SectionItem) { return ((SectionItem) object).getTitle().equals(mTitle); } return false; } }
此结构将使列表适配器中的逻辑更易于管理。在代码清单1-43中,我们可以看到提供分节列表视图的适配器。此适配器的任务是将每个节项(包括他们的头部)的位置映射到适配器视图所了解的可见列表的全局位置。
用于显示多个节的ListAdapter
public abstract class SimpleSectionsAdapter<T> extends BaseAdapter implements AdapterView.OnItemClickListener { //为每个视图类型定义常量 private static final int TYPE_HEADER = 0; private static final int TYPE_ITEM = 1; private LayoutInflater mLayoutInflater; private int mHeaderResource; private int mItemResource; /*所有节的唯一集合*/ private List<SectionItem<T>> mSections; /* 节的分组,按其初始位置设置键 */ private SparseArray<SectionItem<T>> mKeyedSections; public SimpleSectionsAdapter(ListView parent, int headerResId, int itemResId) { mLayoutInflater = LayoutInflater.from(parent.getContext()); mHeaderResource = headerResId; mItemResource = itemResId; //创建包含自动排序键的集合 mSections = new ArrayList<SectionItem<T>>(); mKeyedSections = new SparseArray<SectionItem<T>>(); //将自身附加为列表的单击处理程序 parent.setOnItemClickListener(this); } /* * 向列表添加新的带标题的节, * 或者是更新现有的节 */ public void addSection(String title, T[] items) { SectionItem<T> sectionItem = new SectionItem<T>(title, items); //添加节,替换具有相同标题的现有节 int currentIndex = mSections.indexOf(sectionItem); if (currentIndex >= 0) { mSections.remove(sectionItem); mSections.add(currentIndex, sectionItem); } else { mSections.add(sectionItem); } //对最新的集合排序 reorderSections(); //表明视图数据已改变 notifyDataSetChanged(); } /* * 将带有初始全局位置的节标记为可引用的键 */ private void reorderSections() { mKeyedSections.clear(); int startPosition = 0; for (SectionItem<T> item : mSections) { mKeyedSections.put(startPosition, item); //此计数包括头部视图 startPosition += item.getCount(); } } @Override public int getCount() { int count = 0; for (SectionItem<T> item : mSections) { //添加项的计数 count += item.getCount(); } return count; } @Override public int getViewTypeCount() { //两种视图类型:头部和项 return 2; } @Override public int getItemViewType(int position) { if (isHeaderAtPosition(position)) { return TYPE_HEADER; } else { return TYPE_ITEM; } } @Override public T getItem(int position) { return findSectionItemAtPosition(position); } @Override public long getItemId(int position) { return position; } /* * 重写并返回false,告诉ListView 有一些项(头部)不可点击 */ @Override public boolean areAllItemsEnabled() { return false; } /* *重写以告诉 ListView 哪些项(头部)是不可以点击的
*/ @Override public boolean isEnabled(int position) { return !isHeaderAtPosition(position); } @Override public View getView(int position, View convertView, ViewGroup parent) { switch (getItemViewType(position)) { case TYPE_HEADER: return getHeaderView(position, convertView, parent); case TYPE_ITEM: return getItemView(position, convertView, parent); default: return convertView; } } private View getHeaderView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = mLayoutInflater.inflate(mHeaderResource, parent, false); } SectionItem<T> item = mKeyedSections.get(position); TextView textView = (TextView) convertView.findViewById(android.R.id.text1); textView.setText(item.getTitle()); return convertView; } private View getItemView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = mLayoutInflater.inflate(mItemResource, parent, false); } T item = findSectionItemAtPosition(position); TextView textView = (TextView) convertView.findViewById(android.R.id.text1); textView.setText(item.toString()); return convertView; } /** OnItemClickListener 方法 */ @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { T item = findSectionItemAtPosition(position); if (item != null) { onSectionItemClick(item); } } /** *重写方法以处理特定元素的单击事件,即用户单击@param项列表项 */ public abstract void onSectionItemClick(T item); /* 用于将项映射到节的辅助方法 */ /* * 检查是否代表节标题的全局位置值 */ private boolean isHeaderAtPosition(int position) { for (int i=0; i < mKeyedSections.size(); i++) { //如果此位置是键值,则它就是头部位置 if (position == mKeyedSections.keyAt(i)) { return true; } } return false; } /* * 返回给定全局位置的显式列表项 */ private T findSectionItemAtPosition(int position) { int firstIndex, lastIndex; for (int i=0; i < mKeyedSections.size(); i++) { firstIndex = mKeyedSections.keyAt(i); lastIndex = firstIndex + mKeyedSections.valueAt(i).getCount(); if (position >= firstIndex && position < lastIndex) { int sectionPosition = position - firstIndex - 1; return mKeyedSections.valueAt(i).getItem(sectionPosition); } } return null; } }
Activity代码:
public class MainActivity extends ActionBarActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ListView list = new ListView(this); SimpleSectionsAdapter<String> adapter = new SimpleSectionsAdapter<String>( list, R.layout.list_header, android.R.layout.simple_list_item_1 ) { @Override public void onSectionItemClick(String item) { Toast.makeText(MainActivity.this, item, Toast.LENGTH_SHORT).show(); } }; adapter.addSection("Fruits", new String[]{"Apples", "Oranges", "Bananas", "Mangos"}); adapter.addSection("Vegetables", new String[]{"Carrots", "Peas", "Broccoli"}); adapter.addSection("Meats", new String[]{"Pork", "Chicken", "Beef", "Lamb"}); list.setAdapter(adapter); setContentView(list); } }