Android(安卓)RecyclerView 二级列表实现
- Android RecyclerView 二级列表实现
- 简述
- 实现基础
- 实现思路
- 实现过程
- 二级列表数据格式
- RecyclerView Item状态
- ViewHolder
- 其它属性和方法
- getItemStatusByPosition 方法实现
- getItemCount方法实现
- 其它方法实现
- 总结
- 附
Android RecyclerView 二级列表实现
2017.5.16 添加demo
Demo
简述
在开发 Android APP 的时候,难免会需要实现二级列表的情况,而在自己的项目中使用的列表是 android.support.v7.widget
包里面的 RecyclerView
,好处是可以根据情况实现不同样式的列表,可扩展程度高。而坏处是什么都要自己实现。所以在想要用 RecyclerView
实现的二级列表的时候,却发现没有类似 ListView
的 ExpandableListView
,只能自己去实现。
实现基础
在使用 RecyclerView
的时候,与 ListView
类似,需要创建一个 Adapter
去告诉 RecyclerView
如何工作1,而在创建 RecyclerView
的 Adapter
的时候,一般需要重载以下几个方法:
onCreateViewHolder()
为每个项目创建 ViewHolder
onBindViewHolder()
处理每个 item
getItemViewType()
在 onCreateViewHolder
前调用,返回 item
类型
getItemCount()
获取 item
总数
加载 RecyclerView
的过程如下图:
除此之外,还需要创建一个 ViewHolder
用于寻找自定义 item
的各个控件。
实现思路
根据上述,在实现二级列表的时候,我们在 onCreateViewHolder()
和 onBindViewHolder()
中,判断是加载一级项目(GroupItem)还是二级子项目(SubItem)。
实现过程
二级列表数据格式
一般来说,一个 GroupItem 下面有一个,或多个 SubItem,一对多。
在这里,用一个 DataTree
来封装这种数据格式,代码如下:
public class DataTree<K, V> { private K groupItem; private List subItems; public DataTree(K groupItem, List subItems) { this.groupItem = groupItem; this.subItems = subItems; } public K getGroupItem() { return groupItem; } public List getSubItems() { return subItems; }}
RecyclerView Item状态
ItemStatus
用封装列表每一项的状态,包括:
viewType
item的类型,group item 还是 subitem
groupItemIndex
一级索引位置
subItemIndex
如果该 item 是一个二级子项目,则保存子项目索引
private static class ItemStatus { public static final int VIEW_TYPE_GROUPITEM = 0; public static final int VIEW_TYPE_SUBITEM = 1; private int viewType; private int groupItemIndex = 0; private int subItemIndex = -1; public ItemStatus() { } public int getViewType() { return viewType; } public void setViewType(int viewType) { this.viewType = viewType; } public int getGroupItemIndex() { return groupItemIndex; } public void setGroupItemIndex(int groupItemIndex) { this.groupItemIndex = groupItemIndex; } public int getSubItemIndex() { return subItemIndex; } public void setSubItemIndex(int subItemIndex) { this.subItemIndex = subItemIndex; } }
ViewHolder
这里的 groupItem
和 subItem
分别用不同的布局文件,所以我把 ViewHolder
分开写,如下:
public static class GroupItemViewHolder extends RecyclerView.ViewHolder { ... public GroupItemViewHolder(View itemView) { super(itemView); ... } } public static class SubItemViewHolder extends RecyclerView.ViewHolder { ... public SubItemViewHolder(View itemView) { super(itemView); ... } }
其它属性和方法
context
list dataTrees
用于显示的数据
list
保存 groupItem
状态,开还是关
//向外暴露设置显示数据的方法 public void setDataTrees(List dt) { this.dataTrees = dt; initGroupItemStatus(groupItemStatus); notifyDataSetChanged(); } //设置初始值,所有 groupItem 默认为关闭状态 private void initGroupItemStatus(List l) { for (int i = 0; i < dataTrees.size(); i++) { l.add(false); } }
getItemStatusByPosition() 方法实现
顾名思义,用于根据 position
来计算判断该 item
的状态,返回一个 ItemStatus
。
代码如下:
private ItemStatus getItemStatusByPosition(int position) { ItemStatus itemStatus = new ItemStatus(); int count = 0; //计算groupItemIndex = i 时,position最大值 int i = 0; //轮询 groupItem 的开关状态 for (i = 0; i < groupItemStatus.size(); i++ ) { //pos刚好等于计数时,item为groupItem if (count == position) { itemStatus.setViewType(ItemStatus.VIEW_TYPE_GROUPITEM); itemStatus.setGroupItemIndex(i); break; //pos大于计数时,item为groupItem(i - 1)中的某个subItem } else if (count > position) { itemStatus.setViewType(ItemStatus.VIEW_TYPE_SUBITEM); itemStatus.setGroupItemIndex(i - 1); itemStatus.setSubItemIndex(position - ( count - dataTrees.get(i - 1).getSubItems().size() ) ); break; } //无论groupItem状态是开或者关,它在列表中都会存在,所有count++ count++; //当轮询到的groupItem的状态为“开”的话,count需要加上该groupItem下面的子项目数目 if (groupItemStatus.get(i)) { count += dataTrees.get(i).getSubItems().size(); } } //简单地处理当轮询到最后一项groupItem的时候 if (i >= groupItemStatus.size()) { itemStatus.setGroupItemIndex(i - 1); itemStatus.setViewType(ItemStatus.VIEW_TYPE_SUBITEM); itemStatus.setSubItemIndex(position - ( count - dataTrees.get(i - 1).getSubItems().size() ) ); } return itemStatus; }
getItemCount()方法实现
该方法在显示列表的时候会执行多次,如果返回的项目计数不正确的话程序会出现错误奔溃,代码如下:
@Override public int getItemCount() { Logger.i("1"); int itemCount = 0; if (groupItemStatus.size() == 0) { return 0; } for (int i = 0; i < dataTrees.size(); i++) { if (groupItemStatus.get(i)) { itemCount += dataTrees.get(i).getSubItems().size() + 1; } else { itemCount++; } } return itemCount; }
其它方法实现
- getItemViewType()
该方法会在 onCreateViewHolder()
前执行,并返回 int viewType
,代码如下:
@Override public int getItemViewType(int position) { return getItemStatusByPosition(position).getViewType(); }
- onCreateViewHolder()
根据不同的由getItemViewType()
返回的viewType
选择不同的项目布局,代码如下:
@Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View v; RecyclerView.ViewHolder viewHolder = null; if (viewType == ItemStatus.VIEW_TYPE_GROUPITEM) { v = LayoutInflater.from(parent.getContext()).inflate(R.layout .item_artist_detail_album, parent, false); viewHolder = new GroupItemViewHolder(v); } else if (viewType == ItemStatus.VIEW_TYPE_SUBITEM) { v = LayoutInflater.from(parent.getContext()).inflate(R.layout .item_artist_detail_track, parent, false); viewHolder = new SubItemViewHolder(v); } return viewHolder; }
- onBindViewHolder()
根据不同的viewType
绑定不同的ViewHolder
,代码如下:
@Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { final ItemStatus itemStatus = getItemStatusByPosition(position); final DataTree dt = dataTrees.get(itemStatus.getGroupItemIndex()); if ( itemStatus.getViewType() == ItemStatus.VIEW_TYPE_GROUPITEM ) { final GroupItemViewHolder groupItemVh = (GroupItemViewHolder) holder; . . . //加载groupItem,处理groupItem控件 groupItemVh.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { int groupItemIndex = itemStatus.getGroupItemIndex(); if ( !groupItemStatus.get(groupItemIndex) ) { . . . //groupItem由“关闭”状态到“打开”状态 groupItemStatus.set(groupItemIndex, true); notifyItemRangeInserted(groupItemVh.getAdapterPosition() + 1, dt.getSubItems().size()); } else { . . . //groupItem由“打开”状态到“关闭”状态 groupItemStatus.set(groupItemIndex, false); notifyItemRangeRemoved(groupItemVh.getAdapterPosition() + 1, dt.getSubItems().size()); } } }); } else if (itemStatus.getViewType() == ItemStatus.VIEW_TYPE_SUBITEM) { SubItemViewHolder subItemVh = (SubItemViewHolder) holder; . . . //加载subItem,处理subItem控件 subItemVh.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { . . . //点击subItem处理 } }); } }
总结
二级列表对 RecyclerView
小小地扩展,在实现的过程中,有点麻烦的地方是对特定的 item
进行识别,即 getItemViewType()
的实现,在这里,我简单地用遍历轮询的方法去判断,应该会有更简单更节省资源的方法。实现二级列表之后,理论上可以实现多级列表,可以试试。
附
在用这个方法实现之前,有尝试过有 View
提供的方法—— View.setTag()
,但是由于 RecyclerView
的加载机制,当列表被划出界面时,会被销毁,而重新划进来显示时,RecyclerView
会重新创建新的 item
,而不是之前的那个,所以,之前的 Tag
会不见,最后没能实现出来,感兴趣的同学可以试试。
- [Android RecyclerView 使用完全解析 体验艺术般的控件 - hongyang - CSDN博客]http://blog.csdn.net/lmj623565791/article/details/45059587 ↩
更多相关文章
- 【Android】 Activity Lifecycle
- Android-NDK开发之基础--Android(安卓)JNI实例代码(三)-- 在JNI
- android绑定点击事件的四种方法
- Android中关于数据库SQLite的insert插入操作的理解
- android 自动化测试方法
- ListView的两种使用方法1.继承ListActivity2.自己定义ListView
- Android(安卓)Studio打包APK文件的具体方法介绍
- AndroidStudio快捷键中文版
- Android内容提供者(ContentProvider)浅析(二)