最近在做一个项目,有一个页面的功能和微信联系人列表相似,只不过没有右边的abc….。
开始的时候思路是让服务端那边帮我把数据分好组,然后包给我,然后我这边通过ExpandListView去实现,然后将分组的点击事件屏蔽掉。但是且不说服务端大哥不同意,因为他说如果他这边分类的话,要多循环3次,所以不同意,要我客户端自己去分组,无奈啊,然后就自己将请求到的数据分组,然后通过ExpandListView去实现。
最后是效果还行,有点low,而且不能实现那种组被顶上去的感觉。后来就百度啦,发现Recycleview可以定制item间隔的样式,也看了一篇文章(附上链接:http://www.jianshu.com/p/b46a4ff7c10a http://www.jianshu.com/p/3eff217839fc http://www.jianshu.com/p/e742df6f59e2)。
下面自己进行总结一下:
RecycleView可以通过addItemDecoration(ItemDecoration i);方法给item添加间隔,支持定制,因为是add,所以可以添加多个ItemDecoration。也可以使用默认的ItemDecoration,默认样式是灰色间隔。
ItemDecoration的作用:给具体的view添加具体的图画或者layout的位移,对于绘制view之间的分割线,视觉分组边界也是很有用的。
继承ItemDecoration,并实现:
onDraw(Canvas c, RecyclerView parent, RecyclerView.State state),
onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state),
getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state)
onDraw():该方法会在绘制ItemView之前调用绘制itemdecoration
onDrawOver():该方法是在绘制itemview之后调用绘制itemdecoration,具体体现就是绘制的东西在itemview上面
getItemoffsets:是控制outRect的大小,就是item之间的间隔的矩形。
源码:
// 添加ItemDecoration
public void addItemDecoration(ItemDecoration decor) {
addItemDecoration(decor, -1);
}
// 添加ItemDecoration
public void addItemDecoration(ItemDecoration decor, int index) {
if (mLayout != null) {
mLayout.assertNotInLayoutOrScroll(“Cannot add item decoration during a scroll or”
+ ” layout”);
}
if (mItemDecorations.isEmpty()) {
setWillNotDraw(false);
}
if (index < 0) {
mItemDecorations.add(decor);
} else {
mItemDecorations.add(index, decor);
}
markItemDecorInsetsDirty();
requestLayout();
}
// onLayout 最终会调用到此方法
Rect getItemDecorInsetsForChild(View child) {
....
final int decorCount = mItemDecorations.size();
for (int i = 0; i < decorCount; i++) {
...
mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState);
...
}
...
}
@Override
public void onDraw(Canvas c) {
super.onDraw(c);
final int count = mItemDecorations.size();
for (int i = 0; i < count; i++) {
mItemDecorations.get(i).onDraw(c, this, mState);
}
}
@Override
public void draw(Canvas c) {
super.draw(c);
final int count = mItemDecorations.size();
for (int i = 0; i < count; i++) {
mItemDecorations.get(i).onDrawOver(c, this, mState);
}
}
可以看出add方法调用后会按照添加的itemDecoration顺序依次调用ItemDecoration的getItemOffsets–>onDraw–>onDrawOver
用法1、给RecycleView设置边距,列举2中使用情况:
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
//介绍3个方法
//1、可以通过下面这个方法拿到当前绘制的itemview的位置
int position = parent.getChildAdapterPosition(view);
//2、可以通过下面这个方法拿到itemview的数量
int itemCount = parent.getAdapter().getItemCount();
//3、可以通过下面这个方法拿到recycleview的方向
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
if(manager instanceOf LinearLayoutManager)
int orientation = ((LinearLayoutManager) layoutManager).getOrientation();
//然后对position进行判断,并设置间隔大小
if(position == 0){//第一个item
outRect.set(left,top,right,bottom);
}else if(position == itemCount-1){//最后一个item
outRect.set(left,top,right,bottom);
}else{
//其他item的间隔
outRect.set(left,top,right,bottom);
}
用法2、绘制分割线,其实就是画矩形,填充颜色
主要是在getitemOffsets方法中设置间隔,然后onDraw方法中计算出上下左右坐标,然后画rect。
用法3、stickHeader效果,类似微信联系人界面,上下滑动时分组会被顶上去的效果。
其实有2种header,一种是和item一个级别,类似divider一样,然后只给统一分组设置header;另一种就是处于item上面一个层级,绘制在item上面一层,然后根据滑动情况顶上去。第一种直接根据ondraw和getitemoffsets方法就可以搞定。第二种可以借助ondrawover方法实现:主要思想就是先根据分组通过onDraw方法绘制每个组的header,然后滑动的时候通过调用ondrawover方法绘制浮在上面的组。下一组的第一个item往上顶的时候,这是一个临界点:一个组的最后一个item的bottom
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
if (如果是头部) {
outRect.set(0, (int) mDividerHeight, 0, 0);
}
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View view = parent.getChildAt(i);
int position = parent.getChildAdapterPosition(view);
// 如果是头
if (position != RecyclerView.NO_POSITION
&& ((SectionBean) view.getTag()).isGroupStart) {
drawHeader(c, parent, view, position);
}
}
}
/**
* 画头部
*
* @param c
* @param parent
* @param view
* @param position
*/
private void drawHeader(Canvas c, RecyclerView parent, View view, int position) {
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();
int bottoom = view.getTop() - params.topMargin - Math.round(ViewCompat.getTranslationY(view));
int top = (int) (bottoom - mDividerHeight);
// 计算文字居中时候的基线
Rect targetRect = new Rect(left, top, right, bottoom);
Paint.FontMetricsInt fontMetrics = mTextPaint.getFontMetricsInt();
int baseline = (targetRect.bottom + targetRect.top - fontMetrics.bottom - fontMetrics.top) / 2;
c.drawRect(left, top, right, bottoom, mPaint);
c.drawText(mDatas.get(position).tag, left, baseline, mTextPaint);
}
接下来处理浮在上面的header:
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
View view = parent.getChildAt(0);
View view2 = parent.getChildAt(1);
if (view != null && view2 != null) {
SectionBean section1 = (SectionBean) view.getTag();
SectionBean section2 = (SectionBean) view2.getTag();
int position = parent.getChildAdapterPosition(view);
final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();
int bottoom = (int) mDividerHeight;
int top = 0;
// 判断是否达到临界点
// (第一个可见item是每组的最后一个,第二个可见tiem是下一组的第一个,并且第一个可见item的底部小于header的高度)
// 这里直接判断item的底部位置小于header的高度有点欠妥,应该还要考虑paddingtop以及margintop,这里展示不考虑了
if (section1.isGroupEnd && section2.isGroupStart && view.getBottom() <= mDividerHeight) {
bottoom = view.getBottom();
top = (int) (bottoom - mDividerHeight);
}
// 计算文字居中时候的基线
Rect targetRect = new Rect(left, top, right, bottoom);
Paint.FontMetricsInt fontMetrics = mTextPaint.getFontMetricsInt();
int baseline = (targetRect.bottom + targetRect.top - fontMetrics.bottom - fontMetrics.top) / 2;
// 背景
c.drawRect(left, top, right, bottoom, mPaint);
// 文字
c.drawText(mDatas.get(position).tag, left, baseline, mTextPaint);
}
}
这样滑动的时候就会实现上一个header被顶上去的效果。