• Android RecyclerView 二级列表实现
    • 简述
    • 实现基础
    • 实现思路
    • 实现过程
      • 二级列表数据格式
      • RecyclerView Item状态
      • ViewHolder
      • 其它属性和方法
      • getItemStatusByPosition 方法实现
      • getItemCount方法实现
      • 其它方法实现
    • 总结

Android RecyclerView 二级列表实现

2017.5.16 添加demo

Demo

简述

在开发 Android APP 的时候,难免会需要实现二级列表的情况,而在自己的项目中使用的列表是 android.support.v7.widget 包里面的 RecyclerView,好处是可以根据情况实现不同样式的列表,可扩展程度高。而坏处是什么都要自己实现。所以在想要用 RecyclerView 实现的二级列表的时候,却发现没有类似 ListViewExpandableListView,只能自己去实现。

实现基础

在使用 RecyclerView 的时候,与 ListView 类似,需要创建一个 Adapter 去告诉 RecyclerView 如何工作1,而在创建 RecyclerViewAdapter 的时候,一般需要重载以下几个方法:
onCreateViewHolder() 为每个项目创建 ViewHolder
onBindViewHolder() 处理每个 item
getItemViewType()onCreateViewHolder 前调用,返回 item 类型
getItemCount() 获取 item 总数
加载 RecyclerView 的过程如下图:

Created with Raphaël 2.1.0 Start getItemCount() getItemViewType() onCreateViewHolder() onBindViewHolder() End

除此之外,还需要创建一个 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

这里的 groupItemsubItem 分别用不同的布局文件,所以我把 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 groupItemStatus 保存 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 会不见,最后没能实现出来,感兴趣的同学可以试试。


  1. [Android RecyclerView 使用完全解析 体验艺术般的控件 - hongyang - CSDN博客]http://blog.csdn.net/lmj623565791/article/details/45059587 ↩

更多相关文章

  1. 【Android】 Activity Lifecycle
  2. Android-NDK开发之基础--Android(安卓)JNI实例代码(三)-- 在JNI
  3. android绑定点击事件的四种方法
  4. Android中关于数据库SQLite的insert插入操作的理解
  5. android 自动化测试方法
  6. ListView的两种使用方法1.继承ListActivity2.自己定义ListView
  7. Android(安卓)Studio打包APK文件的具体方法介绍
  8. AndroidStudio快捷键中文版
  9. Android内容提供者(ContentProvider)浅析(二)

随机推荐

  1. 【第769期】2016 JavaScript 发展现状大
  2. 【早读汇】上海@瑞心扉雪《CSS揭秘》笔记
  3. 【第814期】你不懂JS:ES6与未来 语法(下)
  4. 2016,我做了什么?
  5. Android(安卓)本地代码如何输出日志
  6. RocketMQ 源码分析 —— Message 发送与
  7. 【早读汇】上海@jean-lee《ES6标准入门》
  8. 【第773期】你不懂JS:行为委托
  9. 浅谈Android开机启动速度优化
  10. 【第824期】小公司的一年,一起看看小公司