[TOC]
Android RecyclerView 二級列表實(shí)現(xiàn)
2017.5.16 添加demo
簡述
在開發(fā) Android APP 的時(shí)候感耙,難免會需要實(shí)現(xiàn)二級列表的情況塑娇,而在自己的項(xiàng)目中使用的列表是 android.support.v7.widget
包里面的 RecyclerView
磕谅,好處是可以根據(jù)情況實(shí)現(xiàn)不同樣式的列表业稼,可擴(kuò)展程度高突倍。而壞處是什么都要自己實(shí)現(xiàn)芭商。所以在想要用 RecyclerView
實(shí)現(xiàn)的二級列表的時(shí)候,卻發(fā)現(xiàn)沒有類似 ListView
的 ExpandableListView
须蜗,只能自己去實(shí)現(xiàn)。
實(shí)現(xiàn)基礎(chǔ)
在使用 RecyclerView
的時(shí)候目溉,與 ListView
類似明肮,需要創(chuàng)建一個(gè) Adapter
去告訴 RecyclerView
如何工作[1],而在創(chuàng)建 RecyclerView
的 Adapter
的時(shí)候缭付,一般需要重載以下幾個(gè)方法:
onCreateViewHolder()
為每個(gè)項(xiàng)目創(chuàng)建 ViewHolder
onBindViewHolder()
處理每個(gè) item
getItemViewType()
在 onCreateViewHolder
前調(diào)用柿估,返回 item
類型
getItemCount()
獲取 item
總數(shù)
加載 RecyclerView
的過程如下圖:
ps簡書的MD不支持流程圖?
除此之外陷猫,還需要創(chuàng)建一個(gè) ViewHolder
用于尋找自定義 item
的各個(gè)控件秫舌。
實(shí)現(xiàn)思路
根據(jù)上述,在實(shí)現(xiàn)二級列表的時(shí)候绣檬,我們在 onCreateViewHolder()
和 onBindViewHolder()
中足陨,判斷是加載一級項(xiàng)目(GroupItem)還是二級子項(xiàng)目(SubItem)。
實(shí)現(xiàn)過程
二級列表數(shù)據(jù)格式
一般來說娇未,一個(gè) GroupItem 下面有一個(gè)墨缘,或多個(gè) SubItem,一對多零抬。
在這里镊讼,用一個(gè) DataTree
來封裝這種數(shù)據(jù)格式,代碼如下:
public class DataTree<K, V> {
private K groupItem;
private List<V> subItems;
public DataTree(K groupItem, List<V> subItems) {
this.groupItem = groupItem;
this.subItems = subItems;
}
public K getGroupItem() {
return groupItem;
}
public List<V> getSubItems() {
return subItems;
}
}
RecyclerView Item狀態(tài)
ItemStatus
用封裝列表每一項(xiàng)的狀態(tài)平夜,包括:
viewType
item的類型蝶棋,group item 還是 subitem
groupItemIndex
一級索引位置
subItemIndex
如果該 item 是一個(gè)二級子項(xiàng)目,則保存子項(xiàng)目索引
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
用于顯示的數(shù)據(jù)
list<Boolean> groupItemStatus
保存 groupItem
狀態(tài)兼贸,開還是關(guān)
//向外暴露設(shè)置顯示數(shù)據(jù)的方法
public void setDataTrees(List<DataTree<Album, Track>> dt) {
this.dataTrees = dt;
initGroupItemStatus(groupItemStatus);
notifyDataSetChanged();
}
//設(shè)置初始值,所有 groupItem 默認(rèn)為關(guān)閉狀態(tài)
private void initGroupItemStatus(List l) {
for (int i = 0; i < dataTrees.size(); i++) {
l.add(false);
}
}
getItemStatusByPosition() 方法實(shí)現(xiàn)
顧名思義献酗,用于根據(jù) position
來計(jì)算判斷該 item
的狀態(tài)寝受,返回一個(gè) ItemStatus
。
代碼如下:
private ItemStatus getItemStatusByPosition(int position) {
ItemStatus itemStatus = new ItemStatus();
int count = 0; //計(jì)算groupItemIndex = i 時(shí)罕偎,position最大值
int i = 0;
//輪詢 groupItem 的開關(guān)狀態(tài)
for (i = 0; i < groupItemStatus.size(); i++ ) {
//pos剛好等于計(jì)數(shù)時(shí)很澄,item為groupItem
if (count == position) {
itemStatus.setViewType(ItemStatus.VIEW_TYPE_GROUPITEM);
itemStatus.setGroupItemIndex(i);
break;
//pos大于計(jì)數(shù)時(shí),item為groupItem(i - 1)中的某個(gè)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狀態(tài)是開或者關(guān)颜及,它在列表中都會存在甩苛,所有count++
count++;
//當(dāng)輪詢到的groupItem的狀態(tài)為“開”的話,count需要加上該groupItem下面的子項(xiàng)目數(shù)目
if (groupItemStatus.get(i)) {
count += dataTrees.get(i).getSubItems().size();
}
}
//簡單地處理當(dāng)輪詢到最后一項(xiàng)groupItem的時(shí)候
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()方法實(shí)現(xiàn)
該方法在顯示列表的時(shí)候會執(zhí)行多次俏站,如果返回的項(xiàng)目計(jì)數(shù)不正確的話程序會出現(xiàn)錯(cuò)誤奔潰讯蒲,代碼如下:
@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;
}
其它方法實(shí)現(xiàn)
- getItemViewType()
該方法會在 onCreateViewHolder()
前執(zhí)行,并返回 int viewType
肄扎,代碼如下:
@Override
public int getItemViewType(int position) {
return getItemStatusByPosition(position).getViewType();
}
- onCreateViewHolder()
根據(jù)不同的由getItemViewType()
返回的viewType
選擇不同的項(xiàng)目布局墨林,代碼如下:
@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()
根據(jù)不同的viewType
綁定不同的ViewHolder
,代碼如下:
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
final ItemStatus itemStatus = getItemStatusByPosition(position);
final DataTree<Album, Track> 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由“關(guān)閉”狀態(tài)到“打開”狀態(tài)
groupItemStatus.set(groupItemIndex, true);
notifyItemRangeInserted(groupItemVh.getAdapterPosition() + 1, dt.getSubItems().size());
} else {
. . . //groupItem由“打開”狀態(tài)到“關(guān)閉”狀態(tài)
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) {
. . . //點(diǎn)擊subItem處理
}
});
}
}
總結(jié)
二級列表對 RecyclerView
小小地?cái)U(kuò)展,在實(shí)現(xiàn)的過程中衡载,有點(diǎn)麻煩的地方是對特定的 item
進(jìn)行識別搔耕,即 getItemViewType()
的實(shí)現(xiàn),在這里痰娱,我簡單地用遍歷輪詢的方法去判斷弃榨,應(yīng)該會有更簡單更節(jié)省資源的方法。實(shí)現(xiàn)二級列表之后梨睁,理論上可以實(shí)現(xiàn)多級列表鲸睛,可以試試。
附
在用這個(gè)方法實(shí)現(xiàn)之前坡贺,有嘗試過有 View
提供的方法—— View.setTag()
腊凶,但是由于 RecyclerView
的加載機(jī)制,當(dāng)列表被劃出界面時(shí)拴念,會被銷毀钧萍,而重新劃進(jìn)來顯示時(shí),RecyclerView
會重新創(chuàng)建新的 item
政鼠,而不是之前的那個(gè)风瘦,所以,之前的 Tag
會不見公般,最后沒能實(shí)現(xiàn)出來万搔,感興趣的同學(xué)可以試試胡桨。
-
[Android RecyclerView 使用完全解析 體驗(yàn)藝術(shù)般的控件 - hongyang - CSDN博客]http://blog.csdn.net/lmj623565791/article/details/45059587 ?