Android一个RecyclerView实现三级、多级列表(TreeRecyclerView)
2016-12-11 12:27 阅读(248)

前言

不得不吐槽一下产品. 
尼玛为啥要搞这样的功能…. 
搞个两级的不就好了嘛…自带控件,多好. 
三级,四级,听说还有六级的…. 
这样丧心病狂的设计,后台也不好给数据吧.

感谢

这里得感谢Hongyang大神的博客 
传送门

先看看效果:

两级的效果:

QQ图片20161210105724.png

三级的效果:

QQ图片20161210105802.png

全部展开的效果(我只写了五级)

QQ图片20161210105411.png

没设计布局,比较丑,哈哈. 
————————————分割线————————————–

说说为什么写这货吧:

公司产品提出三级这个需求后,我就在网上找啊找.

找的第一个,发现实现其实是ExpandListview嵌套.

找的第二个,ExpandRecyclview,然后就用呗,发现三级展开很卡,看源码, 
发现是RecyclerView套RecyclerView

就没有不嵌套的么…..

然后找到hongyang的那个博客,写个试试吧.

说说思路:

1.Treeadapter应该只需要关心List< TreeAdapterItem > datas 的内容

2.把每个item看成独立的个体,布局样式, 
每行所占比,bindViewHolder都由自己的来决定。

3.每一个item应该只关心自己的数据和自己的下一级的数据,不会去关心上上级,下下级

4.展开的实现,item把子数据集拿出来,然后添加到List< TreeAdapterItem > datas,变成与自己同级,因为每次展开只会展开一级数据。

5.折叠递归遍历所有子数据,递归拿到自己所有的子数据集(可以理解因为一个文件夹下所有的文件,包括子文件夹下的所有),然后从List< TreeAdapterItem > datas删除这些数据。

见代码:

/**
 * Created by Jlanglang on 2016/12/7.
*
 */
public abstract class TreeAdapterItem<D> {
    /**
     * 当前item的数据
     */
    protected D data;
    /**
     * 持有的子数据
     */
    protected List<TreeAdapterItem> childs;
    /**
     * 是否展开
     */
    protected boolean isExpand;
    /**
     * 布局资源id
     */
    protected int layoutId;
    /**
     * 在每行中所占的比例
     */
    protected int spanSize;

    ····
      get/set方法省略。。。。
    ····
    public TreeAdapterItem(D data) {
        this.data = data;
        childs = initChildsList(data);
        layoutId = initLayoutId();
        spanSize = initSpansize();
    }
    /**
     * 展开
     */
    public void onExpand() {
        isExpand = true;
    }

    /**
     * 折叠
     */
    public void onCollapse() {
        isExpand = false;
    }

    /**
     * 递归遍历所有的子数据,包括子数据的子数据
     *
     * @return List<TreeAdapterItem>
     */
    public List<TreeAdapterItem> getAllChilds() {

          ArrayList<TreeAdapterItem> treeAdapterItems = new ArrayList<>();

          for (int i = 0; i < childs.size(); i++) {

              TreeAdapterItem treeAdapterItem = childs.get(i);

              treeAdapterItems.add(treeAdapterItem);

              if (treeAdapterItem.isParent()) {

                  List list = treeAdapterItem.getAllChilds();

                  if (list != null && list.size() > 0) {

                        treeAdapterItems.addAll(list);
                  }
              }
          }
             return treeAdapterItems;
    }

    /**
     * 是否持有子数据
     *
     * @return
     */
    public boolean isParent() {
          return childs != null && childs.size() > 0;
    }
    /**
     * item在每行中的spansize
     * 默认为0,如果为0则占满一行
     * 不建议连续的两级,都设置该数值
     *
     * @return 所占值
     */
     public int initSpansize() {
          return spanSize;
     }
    /**
     * 初始化子数据
     *
     * @param data
     * @return
     */
    protected abstract List<TreeAdapterItem> initChildsList(D data);
    /**
     * 该条目的布局id
     *
     * @return 布局id
     */
    protected abstract int initLayoutId();

    /**
     * 抽象holder的绑定
     *
     * @param holder ViewHolder
     */
    public abstract void onBindViewHolder(ViewHolder holder);
}

再来看看Adapter

public class TreeRecyclerViewAdapter<T extends TreeAdapterItem> extends RecyclerView.Adapter<ViewHolder> {

    protected Context mContext;
    /**
     * 存储所有可见的Node
     */
    protected List<T> mDatas;//处理后的展示数据

    /**
     * 点击item的回调接口
     */
    private OnTreeItemClickListener onTreeItemClickListener;

    public void setOnTreeItemClickListener(OnTreeItemClickListener onTreeItemClickListener) {
        this.onTreeItemClickListener = onTreeItemClickListener;
    }

    /**
     *
     * @param context 上下文
     * @param datas 条目数据
     */
    public TreeRecyclerViewAdapter(Context context, List<T> datas) {
        mContext = context;
        mDatas = datas;
    }

    /**
     *  相应RecyclerView的点击事件 展开或关闭
     *  重要
     * @param position 触发的条目
     */
    public void expandOrCollapse(int position) {
        //获取当前点击的条目
        TreeAdapterItem treeAdapterItem = mDatas.get(position);
        //判断点击的条目有没有下一级
        if (!treeAdapterItem.isParent()) {
            return;
        }
        //判断是否展开
        boolean expand = treeAdapterItem.isExpand();
        if (expand) {
            //获取所有的子数据.
            List allChilds = treeAdapterItem.getAllChilds();
            mDatas.removeAll(allChilds);
            //告诉item,折叠
            treeAdapterItem.onCollapse();
        } else {
            //获取下一级的数据
            mDatas.addAll(position + 1, treeAdapterItem.getChilds());
            //告诉item,展开
            treeAdapterItem.onExpand();
        }
        notifyDataSetChanged();
    }
    //adapter绑定Recycleview后.
    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);
        //拿到布局管理器
        RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
        //判断是否是GridLayoutManager,因为GridLayoutManager才能设置每个条目的行占比.
        if (layoutManager instanceof GridLayoutManager) {
            final GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
            gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                @Override
                public int getSpanSize(int position) {

                    TreeAdapterItem treeAdapterItem = mDatas.get(position);
                    if (treeAdapterItem.getSpanSize() == 0) {
                        //如果是默认的大小,则占一行
                        return gridLayoutManager.getSpanCount();
                    }
                    //根据item的SpanSize来决定所占大小
                    return treeAdapterItem.getSpanSize();
                }
            });
        }
    }


    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        //这里,直接通过item设置的id来创建Viewholder
        return ViewHolder.createViewHolder(mContext, parent, viewType);
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, final int position) {
        final TreeAdapterItem treeAdapterItem = mDatas.get(position);
        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //折叠或展开
                expandOrCollapse(position);
                if (onTreeItemClickListener != null) {
                    //点击监听的回调.一般不是最后一级,不需要处理吧.
                    onTreeItemClickListener.onClick(treeAdapterItem, position);
                }
            }
        });
        treeAdapterItem.onBindViewHolder(holder);
    }

    @Override
    public int getItemViewType(int position) {
        //返回item的layoutId
        return mDatas.get(position).getLayoutId();
    }

    @Override
    public int getItemCount() {
        return mDatas == null ? 0 : mDatas.size();
    }

    public interface OnTreeItemClickListener {
        void onClick(TreeAdapterItem node, int position);
    }
}

具体使用:

/**
 * Created by baozi on 2016/12/8.
 */
public class OneItem extends TreeAdapterItem<CityBean> {

    public OneItem(CityBean data) {
        super(data);
    }
    //这里数据用的是,一个三级城市列表数据。
    @Override
    protected List<TreeAdapterItem> initChildsList(CityBean data) {//这个CityBean 是一级数据
        ArrayList<TreeAdapterItem> oneChilds= new ArrayList<>();
        List<CityBean.CitysBean> citys = data.getCitys();
        if (citys == null) {//如果没有二级数据就直接返回.
            return null;
        }
        for (int i = 0; i < citys.size(); i++) {//遍历二级数据.
            TwoItem twoItem = new TwoItem(citys.get(i));//创建二级条目。
            oneChilds.add(twoItem);
        }
        return oneChilds;
    }

    @Override
    protected int initLayoutId() {//当前级数的布局
        return R.layout.itme_one;
    }

    @Override
    public void onExpand() {
        super.onExpand();

    }


    @Override
    public void onBindViewHolder(ViewHolder holder) {
      //设置当前级数的viewhodler.
      //如果需要某个view展开关闭时的动画,可以在这里保存view到成员变量。
      //然后在onExpand()方法里面操作。
      holder.setText(R.id.tv_content, data.getProvinceName());
    }
}

如果是同一级想要设置不同的布局,接着看

/**
 * Created by baozi on 2016/12/8.
 */
public class FourItem extends TreeAdapterItem<String> {
....

    @Override
    protected List<TreeAdapterItem> initChildsList(String data) {
        ArrayList<TreeAdapterItem> treeAdapterItems = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            FiveItem threeItem = new FiveItem("我是五级");
            //在遍历的时候,通过条件,重设孩子的布局id.和所占比
            if (i % 4 == 0) {//偷个懒,不多写布局了.
                threeItem.setLayoutId(R.layout.itme_one);
                threeItem.setSpanSize(0);
            } else if (i % 3 == 0) {
                threeItem.setLayoutId(R.layout.item_two);
                threeItem.setSpanSize(2);
            }
            treeAdapterItems.add(threeItem);
        }
        return treeAdapterItems;
    }
....
}
/**
 * Created by baozi on 2016/12/8.
 */
public class FiveItem extends TreeAdapterItem<String> {
   .......
//设置默认的布局
    @Override
    protected int initLayoutId() {
        return R.layout.item_five;
    }
//设置默认的占比
    @Override
    public int initSpansize() {
        return 2;
    }
  //根据layoutId来判断viewhodler并设置
    @Override
    public void onBindViewHolder(ViewHolder holder) {
        if (layoutId == R.layout.itme_one) {
            holder.setText(R.id.tv_content, "我是第一种五级");
        } else if (layoutId == R.layout.item_five) {
            holder.setText(R.id.tv_content, "我是第二种五级");
        }else if (layoutId == R.layout.item_two) {
            holder.setText(R.id.tv_content, "我是第三种五级");
        }
    }
}

下面附上github地址,里面有Demo,

具体还有待优化- -,大家给出宝贵意见啊. 
这个item,感觉职责过于强大了,但如果拆分开来,写一个item得创建好几个类. 
而且这种需求一个app也就1,2个页面吧.能简化就简化咯.

传送门:TreeRecyclerView 
欢迎点赞,哈哈 
一直用的svn,搞这个才学的
Git,还不熟,哈哈…


作者:jlanglang