Android RecyclerView 二级列表实现

作者: Rusan | 来源:发表于2017-04-18 15:08 被阅读0次

    [TOC]

    Android RecyclerView 二级列表实现

    2017.5.16 添加demo

    git

    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 的过程如下图:

    flow.PNG

    ps简书的MD不支持流程图?

    除此之外,还需要创建一个 ViewHolder 用于寻找自定义 item的各个控件。

    实现思路

    根据上述,在实现二级列表的时候,我们在 onCreateViewHolder()onBindViewHolder() 中,判断是加载一级项目(GroupItem)还是二级子项目(SubItem)。

    实现过程

    二级列表数据格式

    一般来说,一个 GroupItem 下面有一个,或多个 SubItem,一对多。
    在这里,用一个 DataTree 来封装这种数据格式,代码如下:

    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状态

    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<Boolean> groupItemStatus 保存 groupItem 状态,开还是关

        //向外暴露设置显示数据的方法
        public void setDataTrees(List<DataTree<Album, Track>> 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

    position

    代码如下:

    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<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由“关闭”状态到“打开”状态
                            
                            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 RecyclerView 二级列表实现

        本文链接:https://www.haomeiwen.com/subject/kcofzttx.html