关于RecyclerView的使用,相信大家都不陌生,并且功能的强大早已让众多开发者臣服,本篇主要讲解联系人列表效果的悬浮头部分组列表的实现,先上效果图:
一般的思路应该是利用RecyclerView的itemType来区分标题和下面的子数据而采用不同的布局,再加上RecyclerView的滚动监听来实现头部的悬浮标题移动、隐藏、显示。本篇的主要思路是从ItemDecoration下手,说到这里我想你应该心中有个眉目了,如果对ItemDecoration还不熟悉的朋友,可以去看我的前两篇文章:
先上代码:
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
int pos=parent.getChildViewHolder(view).getAdapterPosition();
if(keys.containsKey(pos)){//留出头部偏移
outRect.set(0,mTitleHeight,0,0);
}else{
outRect.set(0,dividerHeight,0,0);
}
}
首先就是根据当前view的position判断需要在上方留出分割线空隙还是标题栏头部空隙。
然后是onDraw方法绘制具体的分割线和标题栏,看代码:
private void drawVertical(Canvas c, RecyclerView parent){
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
int top=0;
int bottom=0;
for (int i = 0; i < parent.getChildCount(); i++) {
View child=parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
if(!keys.containsKey(params.getViewLayoutPosition())){
//画普通分割线
top=child.getTop()-params.topMargin-dividerHeight;
bottom=top+dividerHeight;
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}else{
//画头部
top=child.getTop()-params.topMargin-mTitleHeight;
bottom=top+mTitleHeight;
c.drawRect(left,top,right,bottom,mBackgroundPaint);
float x=TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,10,mContext.getResources().getDisplayMetrics());
float y=bottom - (mTitleHeight - mTextHeight) / 2 - mTextBaselineOffset;//计算文字baseLine
c.drawText(keys.get(params.getViewLayoutPosition()),x,y,mTextPaint);
}
}
}
其中用到了canvas.drawText方法,这里需要注意y坐标的计算,文本的绘制是根据baseLine来进行的,不懂的朋友可以去网上查阅相关的知识,本文不再重点讲解。
最后就是onDrawOver方法的实现,该方法主要用来在RecyclerView最上层绘制,这样当列表滚动的时候,可以绘制标题栏,这样就看上去就一直悬浮在页面顶部,主要难点就是去判断上下两组标题栏的碰撞,下面看代码:
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
if(!showFloatingHeaderOnScrolling){
return;
}
int firstVisiblePos=((LinearLayoutManager)parent.getLayoutManager()).findFirstVisibleItemPosition();
if(firstVisiblePos==RecyclerView.NO_POSITION){
return;
}
String title=getTitle(firstVisiblePos);
if(TextUtils.isEmpty(title)){
return;
}
boolean flag=false;
if(getTitle(firstVisiblePos+1)!=null&&!title.equals(getTitle(firstVisiblePos+1))){
//说明是当前组最后一个元素,但不一定碰撞了
View child=parent.findViewHolderForAdapterPosition(firstVisiblePos).itemView;
if(child.getTop()+child.getMeasuredHeight()//进一步检测碰撞
c.save();//保存画布当前的状态
flag=true;
c.translate(0,child.getTop()+child.getMeasuredHeight()-mTitleHeight);//负的代表向上
}
}
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
int top=parent.getPaddingTop();
int bottom=top+mTitleHeight;
c.drawRect(left,top,right,bottom,mBackgroundPaint);
float x=TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,10,mContext.getResources().getDisplayMetrics());
float y=bottom - (mTitleHeight - mTextHeight) / 2 - mTextBaselineOffset;//计算文字baseLine
c.drawText(title,x,y,mTextPaint);
if(flag){
//还原画布为初始状态
c.restore();
}
}
注意第四行,我加了个变量来控制是否需要悬浮头部的效果,如果不需要则直接返回,只有分组列表的效果。而getTitle方法主要是用来获取每个item的title,碰撞的检测主要是根据当前第一个可见元素和下一个元素进行对比,也就是代码第16行,如果这两个元素所在的title不一样,就说明是属于不同的分组,标题栏即将进行碰撞了,当然这样检测还是不够的,还需要根据item的高度和标题栏的高度结合item的getTop值来共同判断,这样就可以去移动画布,造成标题栏被下一个挤上去的效果,同时需要在下面还原移动过的画布。
怎么样,整个逻辑和过程就讲解完了,实现起来简单吧,有疑问的朋友可以在下面留言~~~