序:前边我因为项目需要撸了一下RecyclerView GridLayoutManager item设置万能分隔线,感觉还是实用的吧,阅读量也水涨船高,令人欣喜,也满足了内心的小九九~~~ 😝
至于这篇文章呢,是在上一篇的基础上做加法,增加了HeadView的显示。所以至于Item间距啥的算法,原理之类的这里就不再讲解了。没看过上一篇文章的亲们,直接先去撸一把~
下面先贴个图:
看到这个图大家第一想到的做法是什么?
让我们猜猜看:是不是一个RecyclerView(LinearLayoutManager)嵌套另一个RecyclerView(GridLayoutManager)?
嗯!我们一般都是这么做的,也没什么不妥。
但是这里呢,我们换个角度,换个思维,尝试用给一个RecyclerView给它解决了~,年轻就是要燥😝
照旧~ 无图无真相👀
一、首先,我们要对我们需要显示的Item进行ViewType区分
@Override
public int getItemViewType(int position) {
if(listData != null)
return listData.get(position).getViewType();
return super.getItemViewType(position);
}
大家都看得懂哈~ 略...
二、我们要对GridLayoutManager,做定制以用一行显示HeaderView
final GridLayoutManager gridLayoutManager = new GridLayoutManager(this,3);
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
int itemViewType = subAdapter.getItemViewType(position);
if(itemViewType == Constants.PENDING_UPLOAD_SUB_VIEW_TYPE_HEADER)
return gridLayoutManager.getSpanCount();
else
return 1;
}
});
这里实际上就是获取Item的ViewType,如果是HeadView的Type就将GridLayoutManger的spanCount,变成一行。
spanSize 是说用多少个Item空间来显示这个View(比如说可以用2个Item位置显示该View,也可以3个等最大不超过设置的SpanCount),我们这里是获取spanCount,也就是3个,相当于一行。
三、定制适合多个ViewType的ItemDecoration
其实,使用一个RecyclerView来做图1.1的效果,最重要的是要定制适合的ItemDecoration,前面文章详解了GridLayout Item之间平均间距的原理和实现方法。这里就不多说了。
为了实现HeadView和子Item同在一个RecyclerView,并且能正确的设置他们之间的间距,看起来还是挺繁琐的,我们来试试看
3.1 首先我们要在自定义的ItemDecoration中,区分HeadView和subItem
int spanCount = getSpanCount(parent);
int spanSize = getSpanSize(itemPosition,parent);
if(spanSize == spanCount){//这是有HeaderView 的情况
...
}else {//类似HeaderView 下的子item
...
}
这里我们可以通过获取GridLayoutManager的SpanCount和item的SpanSize,如果这两者相等就说明是HeadView Item,因为我们之前在设置GridLayoutManager spansize的时候设置为gridLayoutManager.getSpanCount()
。
3.2 通过上一步我们能够区分HeadView和subItem,但是仍然有个问题是:我们如何能知道每个Item的相对Position?
什么意思呢?
1、比如说我们Item的绝对position,是按照顺序排列的,0、1、2、3、4....等等
(实际上就是上一篇《RecyclerView GridLayoutManager item设置万能分隔线》中使用到的position)
2、而我所说的Item的相对position,有点难解释
先贴个上个文章的公式
第一个Item:L0=sW R0=eW-sW
第二个Item:L1=dW-R0=dW-eW+sW R1=eW-L1=2eW-dW-sW
第三个Item:L2=dW-R1=2(dW-eW)+sW R2=eW-L2=3eW-2dW-sW
所以根据以上可以得出
Ln = (position % spanCount) * (dW-eW) + sW
Rn = eW-Ln
这里可看出我们要获取Ln = (position % spanCount) * (dW-eW) + sW
中的position,而这里的这个position就不是上篇文章那个绝对Position了。
对于HeadView的left、top、right、bottom都是要设置的,而对每个HeadView下面所属的subItem设置left、top、right、bottom就比较难了,因为如果按照《RecyclerView GridLayoutManager item设置万能分隔线》中直接用绝对position进行计算的话,界面就乱了。
下面上一个图:
在上图中
区域一/区域二:代表了一个区块HeadView+subItems,而我在subItem上都标了0、1、2、3...等。这些标记的数字就代表相对position,而他们本身绝对position这是他们本身实际的position:可能是1、2、3、5、6、7、8这样。
而区域二中图标0的Item实际position是多少呢? 是5! 那我们如何得到其相对position为0?那我们就需要用绝对position 减去 包含其headView之上的Item数量。相当于5-5=0。 然后其后面Item的相对position 也是6-5=1、7-5=2、8-5=3(图上标4的给标错了😅)
那这样看就只有相对position才能套用Ln = (position % spanCount) * (dW-eW) + sW
这个公式了。
划重点:subItem的相对position就是相对于包括他的HeadView Item以及上面 所有的Item。
3.3 我们怎么获取subItem的相对position
通过3.1,我们能区分HeadView和subItem了。这点很重要
3.3.1 首先我们先定义两个HashMap用于存储position信息
/**
* 意思是存储每一个个HeadView 的之前所有Item包括自己的数量
*/
LinkedHashMap<Integer,Integer> headPositionTotalCountMap = new LinkedHashMap<>();
/**
* 每一个子Item(非HeadView),存储自己对应的headView的Item数量,
* 主要用于取余计算时,位置换算
*/
LinkedHashMap<Integer,Integer> subItemPositionCountMap = new LinkedHashMap<>();
1、headPositionTotalCountMap:用于存储在它之前的并包括自己的item数量
2、subItemPositionCountMap: 对应于每个subItem存储用于计算自己相对position的Items数量(每个HeadView下的subItem,相对Items数量是相等的)
3.3.2 存储用于计算相对position的items total count
if(spanSize == spanCount){//这是有HeaderView 的情况
...
//如果HeadView Item没有保存count信息,则将它之前包括自身的count,记录
//到以其绝对position为Key的Map中
if(!headPositionTotalCountMap.containsKey(itemPosition)) {
headPositionTotalCountMap.put(itemPosition,itemPosition+1);
}
}else {//类似HeaderView 下的子item
//如果subItem没有保存count信息,则将它HeadView记录的Count取出,记录
//到以其绝对position为Key的Map中
if(!subItemPositionCountMap.containsKey(itemPosition)) {
//找到headPostionTotalCountMap中最近的entry,获取其value
int headViewTotalCount = headPositionTotalCountMap.size() == 0 ? 0 : getMapTail(headPositionTotalCountMap).getValue();
subItemPositionCountMap.put(itemPosition, headViewTotalCount);
}
...
//通过item自身记录的相对items count,计算出相对position“(itemPosition-subItemPositionCountMap.get(itemPosition))”,套入公式
left = (itemPosition-subItemPositionCountMap.get(itemPosition)) % spanCount * (dividerItemWidth - eachItemWidth) + spaceWidth;
right = eachItemWidth - left;
bottom = 0;
top = mDividerWidth;
}
outRect.set(left, top, right, bottom);
代码中注释解释了一波,这里大家就看看,理解一下~
3.4 画出每个区域之间的虚线
//绘制不同HeadView之间虚线分割线
private void draw(Canvas canvas, RecyclerView parent) {
int width = mContext.getResources().getDisplayMetrics().widthPixels > mContext.getResources().getDisplayMetrics().heightPixels
? mContext.getResources().getDisplayMetrics().heightPixels : mContext.getResources().getDisplayMetrics().widthPixels;
int spanCount = getSpanCount(parent);
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
int itemPosition = ((RecyclerView.LayoutParams) child.getLayoutParams()).getViewLayoutPosition();
int spanSize = getSpanSize(itemPosition,parent);
if(spanCount == spanSize && itemPosition != 0){
mPath.reset();
mPath.moveTo(child.getLeft()-5,child.getTop()-mDividerWidth);
mPath.lineTo(width-mDividerWidth+5,child.getTop()-mDividerWidth);
canvas.drawPath(mPath,mPaint);
}
}
}
画虚线就比较简单了几大件:Path、Paint、Canvas配置好就行,然后获取每个headView的top、left、屏幕宽度就可以画出虚线,这里就不多说了。
好了,到这里《不嵌套RecyclerView,实现有HeaderView的GridLayoutManager》就讲完了 ,它没有嵌套RecyclerView来实现文中UI,并且能很好的平分间隔,不会使item位置错乱。另外,可能文中一些东西没讲清楚,如若有任何问题,都可以留言,我看到后会第一时间回复!See you next article~
我已将GridDividerItemDecoration,上传到GitHub:https://github.com/haozi5460/GridDividerMoreTypeItemDecoration
申明:禁用于商业用途,如若转载,请附带原文链接。https://www.jianshu.com/p/ea4d9843dada 蟹蟹(#.#)