第一篇链接:

android RecyclerView一步步打造分组效果、类似QQ分组、折叠菜单、分组效果(一)

注!已更新代码!

上一篇写了分组效果的初步实现:

这一篇就继续增加分组折叠效果和基类的抽取与解决上一篇的bug(item布局宽度match_parent没有生效)

效果如下图:

三、点击头布局实现展开折叠效果

根据上一片文章最后的代码,继续修改代码让RecyclerView实现点击班级布局可以显示隐藏学生的效果,

首先,先画图分析可折叠的效果有几种情况:

与不可折叠的差别:

1.由上图可以看出,班级布局的position的可变的,也就是说,当折叠的时候无展开的时候,其他的班级布局的position会动态改变位置,所以,上一篇中用HashMap存放班级布局的index和position不可用于该效果,所以改为List集合来存放班级的position

2.根据当前选中情况来单独显示隐藏某个班级下的学生

怎么实现点击班级后显示学生,再次点击就隐藏学生呢?这个我们可以在返回班级对应学生人数哪里做文章,当我需要隐藏该班级学生,就返回0,显示的话就返回该班级的所有人数

代码改造如下:

    //存放班级对应的positionprivate List<Integer> mHeaderIndex = new ArrayList<>();//存放班级对应的学生private HashMap<Integer, List<String>> mContentMap = new HashMap<>();//存放当前班级的是否展开private SparseBooleanArray mBooleanMap;/*** 条目的总数量* @return*/@Overridepublic int getItemCount() {return getHeadersCount() + getContentCount();}/*** 头布局的数量* @return*/private int getHeadersCount(){return mContent.size();}/*** item的数量* @return*/private int getContentCount(){mHeaderIndex.clear();mContentMap.clear();int itemCount = 0;int studentSize = 0;for (int i = 0; i < mContent.size(); i++) {if(i != 0){itemCount++;}//存储第几班的index位置
//            mHeaderIndex.put(i,new Integer(itemCount));mHeaderIndex.add(new Integer(itemCount));itemCount += getStudentSizeOfClass(i);studentSize += getStudentSizeOfClass(i);if(getStudentSizeOfClass(i) > 0){mContentMap.put(i, mContent.get(i).classStudents);}}return studentSize;}/*** 根据班级获取对应的学生人数* @param classIndex* @return*/private int getStudentSizeOfClass(int classIndex){int count = mContent.get(classIndex).classStudents.size();if (!mBooleanMap.get(classIndex)) {count = 0;}return count;}@Overridepublic void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {//如果是head布局if(isHeaderView(position)){((HeaderHolder)holder).tvClassName.setOnClickListener(null);((HeaderHolder)holder).tvClassName.setText(mContent.get(getHeadRealCount(position)).className);((HeaderHolder)holder).tvClassName.setTag(getHeadRealCount(position));((HeaderHolder)holder).tvClassName.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {int position = (int) view.getTag();boolean isOpen = mBooleanMap.get(position);mBooleanMap.put(position, !isOpen);notifyDataSetChanged();}});return ;}else {//根据position获取position对应的学生所在班级int classId = getStudentOfClass(position);//获取该班级所有学生List<String> classStudent = mContentMap.get(classId);//获取改班级head所在的position位置int classOfPosition = mHeaderIndex.get(classId);//根据当前位置position和班级head布局的position,计算当前学生在班级中的位置int studentIndex = position - classOfPosition - 1;//根据位置获取具体学生String studentName = classStudent.get(studentIndex);//显示出来学生名字((ContentHolder) holder).tvInfo.setText(studentName);}}/*** 获取position是第几个班级* @param position* @return*/private int getHeadRealCount(int position){return mHeaderIndex.indexOf(new Integer(position));}/*** 根据position获取所属的班级的index* @return*/private int getStudentOfClass(int position){for (int i = 0; i < mHeaderIndex.size(); i++) {if(mHeaderIndex.get(i) > position){return i-1;}}return mHeaderIndex.size() - 1;}

从代码中可以看出,getHeadersCount()getItemCount()并没有改动,主要就是getContentCount()getHeadRealCount(int position)getStudentOfClass(int position)onBindViewHolder中改动较大,还多了个mBooleanMap,这个主要是存储当前班级是否展开。

运行效果如下:

基本效果是实现了,如果想要listView样式的我们只需要修改布局管理器setLayoutManager就可以很轻松的切换成List样式了,那么,如果换成其他场景还要重复写这么多代码也不想我们的作风,程序员都是非常懒的,所以,我们需要抽出一个基类,在不同的地方使用,只需要继承该基类然后写一些少量的代码就可以实现该效果,这才是我们想要的,那么开始把。

四、抽出基类

首先,需要思考那些需要自己实现的,那些需要基类实现的,大概整理了一下不同需求需要改变的地方:

  1. 创建班级布局
  2. 创建学生布局
  3. 填充班级布局信息
  4. 填充学生布局信息
  5. 班级ViewHolder
  6. 学生ViewHolder
  7. 一共有多少个班级
  8. 每个班级有多少个学生

所以,我们在基类中,把这些方法定义为抽象方法,让子类必须去实现,注意:ViewHolder需要使用泛型!:

    /*** 头布局的总数(一共有多少个班级)* @return*/public abstract int getHeadersCount();/*** 头布局对应内容的总数(也就是改头布局里面有多少条item)(根据班级获取该班级有多少个学生)* @param headerPosition 第几个头布局* @return*/public abstract int getContentCountForHeader(int headerPosition);/*** 创建头布局(创建班级布局)* @param parent* @param viewType* @return*/public abstract C onCreateHeaderViewHolder(ViewGroup parent, int viewType);/*** 创建内容布局(创建学生布局)* @param parent* @param viewType* @return*/public abstract S onCreateContentViewHolder(ViewGroup parent, int viewType);/*** 填充头布局的数据(填充班级布局信息)* @param holder* @param position*/public abstract void onBindHeaderViewHolder(C holder, int position);/*** 填充(填充学生布局信息)* @param holder* @param HeaderPosition* @param ContentPositionForHeader*/public abstract void onBindContentViewHolder(S holder, int HeaderPosition, int ContentPositionForHeader);

下面把基类的adapter中对应的代码改一下:

    /*** 条目的总数量* @return*/@Overridepublic int getItemCount() {mHeaderIndex.clear();int count = 0;int headSize = getHeadersCount();for (int i = 0; i < headSize; i++) {if(i != 0){count++;}mHeaderIndex.add(new Integer(count));count += getContentCountForHeader(i);}Log.e("fan", "--getItemCount:" + count + "--headSize" + headSize);return count + 1;}@Overridepublic RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {if(viewType == TYPE_HEADER){//班级header布局return onCreateHeaderViewHolder(parent, viewType);}else {//学生布局return onCreateContentViewHolder(parent, viewType);}}@Overridepublic void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {//如果是head布局if(isHeaderView(position)){     //班级header布局填充onBindHeaderViewHolder((C)holder, getHeadRealCount(position));return ;}else {      //学生信息填充//根据position获取position对应的学生所在班级int classId = getStudentOfClass(position);//获取改班级head所在的position位置int classOfPosition = mHeaderIndex.get(classId);//根据当前位置position和班级head布局的position,计算当前学生在班级中的位置int studentIndex = position - classOfPosition - 1;onBindContentViewHolder((S)holder, classId, studentIndex);}}

基类adapter完整的代码如下(如果不懂泛型的可以注意下类名后面跟着的代码):

这里写代码片

public abstract class ClassAdapter<C extends RecyclerView.ViewHolder, S extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<RecyclerView.ViewHolder> {private static final int TYPE_HEADER = 1;private static final int TYPE_CONTENT = 0;private List<Integer> mHeaderIndex = new ArrayList<>();/*** 是否为头布局* @param position* @return*/private boolean isHeaderView(int position){return mHeaderIndex.contains(new Integer(position));}@Overridepublic RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {if(viewType == TYPE_HEADER){//班级header布局return onCreateHeaderViewHolder(parent, viewType);}else {//学生布局return onCreateContentViewHolder(parent, viewType);}}@Overridepublic void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {//如果是head布局if(isHeaderView(position)){     //班级header布局填充onBindHeaderViewHolder((C)holder, getHeadRealCount(position));return ;}else {      //学生信息填充//根据position获取position对应的学生所在班级int classId = getStudentOfClass(position);//获取改班级head所在的position位置int classOfPosition = mHeaderIndex.get(classId);//根据当前位置position和班级head布局的position,计算当前学生在班级中的位置int studentIndex = position - classOfPosition - 1;onBindContentViewHolder((S)holder, classId, studentIndex);}}@Overridepublic int getItemViewType(int position) {if(isHeaderView(position)){return TYPE_HEADER;}else{return TYPE_CONTENT;}}/*** 条目的总数量* @return*/@Overridepublic int getItemCount() {mHeaderIndex.clear();int count = 0;int headSize = getHeadersCount();for (int i = 0; i < headSize; i++) {if(i != 0){count++;}mHeaderIndex.add(new Integer(count));count += getContentCountForHeader(i);}return count + 1;}/*** 获取position是第几个头布局* @param position* @return*/private int getHeadRealCount(int position){return mHeaderIndex.indexOf(new Integer(position));}/*** 根据value获取所属的key* @return*/private int getStudentOfClass(int position){for (int i = 0; i < mHeaderIndex.size(); i++) {if(mHeaderIndex.get(i) > position){return i-1;}}return mHeaderIndex.size() - 1;}@Overridepublic void onAttachedToRecyclerView(RecyclerView recyclerView) {final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();if(layoutManager instanceof GridLayoutManager){GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;GridLayoutManager.SpanSizeLookup spanSizeLookup = gridLayoutManager.getSpanSizeLookup();gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup(){@Overridepublic int getSpanSize(int position) {int viewType = getItemViewType(position);if(viewType == TYPE_HEADER){return ((GridLayoutManager) layoutManager).getSpanCount();}return 1;}});}}@Overridepublic void onViewAttachedToWindow(RecyclerView.ViewHolder holder){int position = holder.getLayoutPosition();if (isHeaderView(position)){ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();if (lp != null && lp instanceof StaggeredGridLayoutManager.LayoutParams){StaggeredGridLayoutManager.LayoutParams p =(StaggeredGridLayoutManager.LayoutParams) lp;p.setFullSpan(true);}}}/*** 头布局的总数* @return*/public abstract int getHeadersCount();/*** 头布局对应内容的总数(也就是改头布局里面有多少条item)* @param headerPosition 第几个头布局* @return*/public abstract int getContentCountForHeader(int headerPosition);/*** 创建头布局* @param parent* @param viewType* @return*/public abstract C onCreateHeaderViewHolder(ViewGroup parent, int viewType);/*** 创建内容布局* @param parent* @param viewType* @return*/public abstract S onCreateContentViewHolder(ViewGroup parent, int viewType);/*** 填充头布局的数据* @param holder* @param position*/public abstract void onBindHeaderViewHolder(C holder, int position);/*** 填充* @param holder* @param HeaderPosition* @param ContentPositionForHeader*/public abstract void onBindContentViewHolder(S holder, int HeaderPosition, int ContentPositionForHeader);}

而我们平常使用的话就写个子类去继承该adapter,例如:

MyAdapter.class

public class MyAdapter extends ClassAdapter<MyAdapter.ClassHolder, MyAdapter.StudentHolder> {private Context context;private List<ClassBean> mContent;//用于记录当前班级是隐藏还是显示private SparseBooleanArray mBooleanMap;public MyAdapter(Context context, List mContent) {this.context = context;this.mContent = mContent;mBooleanMap = new SparseBooleanArray();}@Overridepublic int getHeadersCount() {return mContent.size();}@Overridepublic int getContentCountForHeader(int headerPosition) {int count = mContent.get(headerPosition).classStudents.size();if (!mBooleanMap.get(headerPosition)) {count = 0;}return count;}/*** 创建头布局header的viewholder* @param parent* @param viewType* @return*/@Overridepublic MyAdapter.ClassHolder onCreateHeaderViewHolder(ViewGroup parent, int viewType) {return new ClassHolder(View.inflate(context, R.layout.item, null));}/*** 创建内容布局item的viewholder* @param parent* @param viewType* @return*/@Overridepublic MyAdapter.StudentHolder onCreateContentViewHolder(ViewGroup parent, int viewType) {return new StudentHolder(View.inflate(context, R.layout.item, null));}@Overridepublic void onBindHeaderViewHolder(MyAdapter.ClassHolder holder, int position) {holder.tvClassName.setOnClickListener(null);holder.tvClassName.setText(mContent.get(position).className);holder.tvClassName.setTag(position);holder.tvClassName.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {int position = (int) view.getTag();boolean isOpen = mBooleanMap.get(position);mBooleanMap.put(position, !isOpen);notifyDataSetChanged();}});}@Overridepublic void onBindContentViewHolder(StudentHolder holder, int HeaderPosition, int ContentPositionForHeader) {holder.tvInfo.setText(mContent.get(HeaderPosition).classStudents.get(ContentPositionForHeader));}class ClassHolder extends RecyclerView.ViewHolder{public TextView tvClassName;public ClassHolder(View itemView) {super(itemView);tvClassName = itemView.findViewById(R.id.tvInfo);}}class StudentHolder extends RecyclerView.ViewHolder{public TextView tvInfo;public StudentHolder(View itemView) {super(itemView);tvInfo = itemView.findViewById(R.id.tvInfo);}}}

这里需要注意一点的是,我们把显示隐藏的放到子类的getContentCountForHeader(int headerPosition)里面了,所以,如果我们不想要隐藏,只要一直显示内容的话就把里面的map给注释了,如下:

    @Overridepublic int getContentCountForHeader(int headerPosition) {int count = mContent.get(headerPosition).classStudents.size();//这里是控制显示隐藏内容的部分//if (!mBooleanMap.get(headerPosition)) {//    count = 0;//}return count;}

对了,还有Activity中使用的话就直接创建我们写的子类就可以了,比如:

        for (int i = 1; i < 4; i++) {List<String> studentName = new ArrayList<>();for (int j = 1; j < 56; j++) {studentName.add(i + "班 学生" + j);}ClassBean bean = new ClassBean();bean.className = "二年级" + i + "班";bean.classStudents = studentName;mListClass.add(bean);}//可以修改布局管理器来显示网格布局还是线性布局rvShow.setLayoutManager(new GridLayoutManager(this, 4));//这个就用我们的子类adapterMyAdapter mWrapper = new MyAdapter(this, mListClass);rvShow.setAdapter(mWrapper);

ok,搞定,对了,还有最后一步,就是修改之前的match_parent不生效问题

五、修复布局match_parent不生效问题

这个百度一下估计多的是,这里就简要说一下解决方法:

在创建ViewHolder的时候,我们之前使用的是用的以前Listview的adapter的写法:

View.inflate(context, R.layout.item, null)

需要修改为:

LayoutInflater.from(context).inflate(R.layout.item, parent, false)

就可以了,最终效果图就是:

完整版代码:https://github.com/fan0424/RecyclerViewGroupDemo

教程结束!

番外一:根据点击child获取真实的position

    /*** 获取真实位置* @param section* @param position 如果是标题栏,为-1* @return*/private int getRealPosition(int headerPosition, int ContentPositionForHeader){int realPosition = -1;if(headerPosition!= -1){realPosition = 0;for (int i = 0; i < headerPosition; i++) {realPosition++;realPosition += getContentCountForHeader(i);}realPosition++;realPosition += ContentPositionForHeader;}return realPosition;}

android RecyclerView一步步打造分组效果、类似QQ分组、折叠菜单、分组效果(二)相关推荐

  1. android RecyclerView一步步打造分组效果、类似QQ分组、折叠菜单、分组效果(一)

    第二篇文章链接: android RecyclerView一步步打造分组效果.类似QQ分组.折叠菜单.分组效果(二) 效果图: 前言 之前看到这种效果如果用recyclerview来实现没有一点思路, ...

  2. Android 贝塞尔曲线——类似QQ红点拖拽效果

    在Android绘制中,提供了更为丰富绘制api--Path类,包括直线,二阶贝塞尔曲线,三阶贝塞尔曲线,弧形,圆,椭圆,圆角矩形等等,path的绘制最终是调用了C中的绘制方法. 下面来看一下常用的几 ...

  3. android 微信浮窗实现_Android实现类似qq微信消息悬浮窗通知功能

    实现方法:(需要开启悬浮窗通知权限.允许应用在其他应用上显示) 一.利用headsup 悬挂式Notification,他是5.0中新增的,也就是API中的Headsup的Notification,可 ...

  4. Qt+html+JavaScript实现类似QQ聊天界面的气泡效果

    这是一个简单的类似QQ聊天界面的Demo,用Qt实现,在QWebView里嵌入网页的方式实现.先看效果图: 无论怎样,我觉得自己动手写出来的东西才是自己的,所以源码不全,重点的代码我会放上来.只是模拟 ...

  5. Android RatingBar结合属性动画,快速实现 QQ群男女比例分布图效果

    RatingBar介绍 RatingBar作为评分组件,它在实现打分功能的时候确实很方便,并结合了手势触摸事件:RatingBar 的实质是 ProgressBar ,可以看看他的继承关系 java. ...

  6. 【cocos2dx 3.3 lua】04 纸牌翻转效果--类似QQ斗地主癞子效果

    周末在家玩QQ斗地主,发现里面的癞子翻牌效果挺不错,于是想着自己做一个玩玩,于是有了如下代码: -- 卡牌旋转动画-- 需要2张图,一张正面一张背面,否则会看到一张正面旋转了360度local car ...

  7. html怎么做到滚动鼠标转换,js实现的鼠标滚轮滚动切换页面效果(类似360默认页面滚动切换效果)...

    本文实例讲述了js实现的鼠标滚轮滚动切换页面效果的方法.分享给大家供大家参考,具体如下: 运行效果截图如下: 具体代码如下: wheel var currentShowPageIndex = 0; v ...

  8. 初学Android之viewPager+imageLoader+图片圆角(类似qq头像)+xListView+DrawLayout

    有adapter.application.bean.view这几个包 接下来就按照顺序去写一下 首先是adapter public class MyPagerAdapter extends Pager ...

  9. 在asp.net中使用jQuery实现类似QQ网站的图片切割效果

    今天要给大家介绍一个asp.net结合jQuery来切割图片的小程序,原理很简单,大致流程是: 加载原图 --> 用矩形框在原图上选取区域并将选取的顶点坐标和矩形尺寸发送至服务器 --> ...

最新文章

  1. 04Strategy(策略)模式
  2. python json数据的转换
  3. python人工智能学多久_Python人工智能学习需要多久?什么学历可以学习?
  4. sqlserver查找存储过程关键字方法之三
  5. PHP生成Mysql数据字典
  6. sh执行文件 参数传递_Shell脚本传参数方法总结
  7. php 如何让html表单当中的数据在修改mysql的时候自动变更_怎么用php把html表单内容写入数据库?...
  8. 边开车边唱K?特斯拉汽车卡拉OK功能即将推出
  9. python中pow_如何在python中找到pow(a,b,c)的反向?
  10. 大厂开发“大牛”,你距离他们有多远?
  11. Helm 3 完整教程(七):Helm 函数讲解(1)逻辑和流控制函数
  12. python random random_【python】random与numpy.random
  13. 轻松复制百度文库内容
  14. flutter配置高德地图定位
  15. 计算机应用维护师实习周记,计算机系统维护专业毕业实习周记
  16. Python 实现电信天翼网关光猫自动重启
  17. 台式计算机无线网卡怎么找,win7台式机找不到无线网卡怎么办
  18. Windows IE8降为IE6的方法
  19. mysql 索引 insert_Mysql 大批量数据insert or update与UQ索引
  20. SpringBoot启动完之后自动打开浏览器网址

热门文章

  1. 考研路上的那些一战二战三战成功与失败的故事系列之十一
  2. Java工具类系列--Arrays的用法
  3. VSCode报警处理:VisualStudioCode无法监视这个大型工作区的文件变化
  4. Windows CE大排档 资源汇总
  5. 软件测试行业趋势分析
  6. java设计模式(2):UML图
  7. Caffeine使用分享
  8. SQL——结构化查询语言
  9. 好客租房172-地图找房createOverlays
  10. python之汉诺塔问题详解