RecyclerView真的很强大,真心的,这一点毋庸置疑,现在公司的项目中有用到,趁着项目刚上线,空余时间总结一下项目中的知识点。
首先项目中需求如下图(有改动):
消息分为:系统消息和活动消息,服务端返回一个列表,根据列表将相邻的Item分别归类,相同类型Item的同一个标题,不相同的Item,就多一个标题。如上图所示,系统消息在一块,紧接着是活动消息,再紧接着时系统消….。
其实这个需求有一种比较普通的做法,那就是两个RecyclerView嵌套,这样省事省力,这样做的话,等于牺牲了效率,节省了力气~。
这是RecyclerView的ItemDecoration功能就应运而生了。
*
简单功能
*
首先说下ItemDecoration的简单功能。实现如下功能:
没错,就是分割线,使用ItemDecoration实现分割线。我们知道自定义ViewGroup的步骤是先测量View,然后给绘制View,同理RecyclerView也不例外,先给空间,再绘制。
代码如下:
/**
* 给空间
* @param outRect
* @param view
* @param parent
* @param state
*/
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
if(parent.getChildAdapterPosition(view) != 0) {
outRect.top = 1;
dividerHeight = 1;
}
}
/**
* 绘制
* @param c
* @param parent
* @param state
*/
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
int childCount = parent.getChildCount();
for(int i = 0; i < childCount; i++) {
View view = parent.getChildAt(i);
int viewLayoutPosition = ((RecyclerView.LayoutParams) (view.getLayoutParams())).getViewLayoutPosition();
if(viewLayoutPosition == 0) {
continue;
}
float dividerTop = view.getTop() - dividerHeight;
float dividerLeft = parent.getPaddingLeft();
float dividerRight = parent.getWidth() - parent.getPaddingRight();
float dividerBottom = view.getTop();
c.drawRect(dividerLeft, dividerTop, dividerRight, dividerBottom, mPaint);
}
}
新建一个类继承自ItemDecoration,然后重写getItemOffsets方法,该方法就是给分割线所需要的空间,线高:dividerHeight 。至于outRect,看下图:
从图中可以看出,这个内层的矩形代表View,外层的代表outRect,我们给outRect.top = 1,也就是给了分割线的空间,每一个View都有分割线的空间(除了Position = 0)。
空间有了,接下来就是绘制了,也就是onDraw方法,方法的参数含有canvas,就可以知道,我们可以尽情的绘制我们想要绘制的view了。先遍历所有的View, 然后通过view可以得到该view此时此刻再recyclerView中的真实position,通过该position就可以做我们想做的事情了,接下来就是自定义View的东西了。在此不再赘述。
实现文章开头的最开始的需求
上面的看懂了,下面的我相信不会太难的,也就是多了几个if语句而已。
首先开始给空间,空间分两种:平常的间隔和包含标签的间隔(系统消息,活动消息)。通过参数中的view,可以获得该view再recyclerView中的位置position,如果position == 0,表示第一个,那肯定得是包含标签的了,然后就是position != 0 , 此时我们通过position,获得数据源list中的type(活动消息,系统消息)判断,如果前一个preType.equal(currentType),那么就是普通的间隔,反之,就是包含标签的间隔。
先贴上变量的声明:
private Paint mPaint;
private int spaceHeight; //间隔高度(无标签)
private int spaceTotalHeight = 0; //间隔高度(标签)
private int dateBackHeight = 0; //时间日期背景高度
private int dateToBackWidth = 0; //日期背景宽度
private int testSize = 0; //字体大小
private int color_date_back = Color.parseColor("#999999"); //日期背景色
private int color_text = Color.parseColor("#333333"); //字体颜色
private List<DateModel> dateModelList;
代码:
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
int position = ((RecyclerView.LayoutParams)view.getLayoutParams()).getViewLayoutPosition();
if(position > -1) {
if(position == 0) {
outRect.set(0, spaceTotalHeight, 0, 0);
} else {
String preType = dateModelList.get(position - 1).type;
String currentType = dateModelList.get(position).type;
if(preType.equals(currentType)) { //相同类型
outRect.set(0, spaceHeight, 0, 0);
} else { //不同类型
outRect.set(0, spaceTotalHeight, 0, 0);
}
}
}
}
然后就是绘制了,其实绘制一样的呢,跟上面的逻辑一样的,直接上代码吧:
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
int childCount = parent.getChildCount();
for(int i = 0; i < childCount; i++) {
View childView = parent.getChildAt(i);
int viewLayoutPosition = ((RecyclerView.LayoutParams) childView.getLayoutParams()).getViewLayoutPosition();
String type = dateModelList.get(viewLayoutPosition).type;
if(viewLayoutPosition == 0) {
onTextData(c, type, childView, parent);
} else {
String preType = dateModelList.get(viewLayoutPosition - 1).type;
if(preType.equals(type)) {
//do nothing
} else {
onTextData(c, type, childView, parent);
}
}
}
}
private void onTextData(Canvas c, String type, View childView, RecyclerView parent) {
float measureText = mPaint.measureText(type);
//画矩形(日期背景)
float rectBackLeft = parent.getWidth() / 2 - measureText / 2 - dateToBackWidth;
float rectBackRight = parent.getWidth() / 2 + measureText / 2 + dateToBackWidth;
float rectBackTop = childView.getTop() - spaceTotalHeight / 2 - dateBackHeight / 2;
float rectBackBottom = childView.getTop() - (spaceTotalHeight / 2 - dateBackHeight / 2);
mPaint.setColor(color_date_back);
c.drawRect(rectBackLeft, rectBackTop, rectBackRight, rectBackBottom, mPaint);
//画字(日期)
float dateLeft = parent.getWidth() / 2 - measureText / 2 - dateToBackWidth + dateToBackWidth;
Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
//(fontMetrics.descent - fontMetrics.ascent) / 2 - fontMetrics.descent; //baseLine
float baseLine = childView.getTop() - spaceTotalHeight / 2 + (fontMetrics.descent - fontMetrics.ascent) / 2 - fontMetrics.descent;
mPaint.setColor(color_text);
c.drawText(type, dateLeft, baseLine, mPaint);
}
其实感觉RecyclerView将跟多的功能实现都交给了开发者,开发者根据自己的需求,去定制属于自己的代码。很强大~