Android RecyclerView 二级列表实现

  • 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 ↩

你可能感兴趣的:(Android)