RecyclerView高级用法——吸顶效果
引言
我们对RecyclerView的使用不能仅仅停留在表面,可以探索一些高级的用法——自定义,不过要实现这些的前提是必须对RecycleView的源码流程有所了解。今天小编就略讲一下,主要是和今天的主体有关的内容。我们今天就来实现如下的吸顶效果。
通过绘制实现RecyclerView的吸顶效果
ItemDecoration
ItemDecoration允许应用给具体的View添加具体的图画或者layout的偏移,对于绘制View之间的分割线,视觉分组边界等等是非常有用的。
当我们调用addItemDecoration()方法添加decoration的时候,RecyclerView就会调用该类的onDraw方法区绘制分割线,也就是说:分割线是绘制出来的。
RecyclerView.ItemDecoration,该类为抽象类,官方目前只提供了一个实现类DividerItemDecoration。
并且只有LinearLayoutManager能够使用
要实现这样的吸顶效果,首先,就需要了解一下RecyclerView.ItemDecoration的绘制流程。
1.onDraw
在绘制每个itemView之前绘制,上图中的每个跟着滑动的头部就是在此处绘制
2.onDrawOver
在绘制了每个ItemView之后绘制,上图中固定的顶部在此处绘制
3.getItemOffsets
设置偏移值,可以在绘制itemView时设置偏移值,其实上demo中的每个头部(红色区域)就是在绘制itemView预留了部分空间用于绘制头部。
也就是说绘制流程是:onDraw()->ItemView->onDrawOver
具体实现
- 在Adapter中创建两个方法
- 1.获取组名
- 2.判断是否是组的头部
- 首先,position=0一定是组的头部
- 其次,可以通过前后两次组名判断当前item是不是组的头部
//是否是 组的第一个 |
自定义分割线
@Override
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.onDraw(c, parent, state);
if (parent.getAdapter() instanceof ModelAdapter){
ModelAdapter adapter = (ModelAdapter) parent.getAdapter();
int count = parent.getChildCount();
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
for (int i = 0; i < count; i++) {
//获取对应的View
View view = parent.getChildAt(i);
//获取View的布局位置
int position = parent.getChildLayoutPosition(view);
//判断是不是头部
boolean isGroupHeader = adapter.isGroupHeader(position);
if (isGroupHeader && view.getTop() - groupHeaderHeight - parent.getPaddingTop() >= 0){
c.drawRect(left,view.getTop()-groupHeaderHeight,right,view.getTop(),headPaint);
String groupName = adapter.getGroupName(position);
textPaint.getTextBounds(groupName, 0, groupName.length(), textRect);
c.drawText(groupName,left+20,view.getTop()-groupHeaderHeight/2+textRect.height()/2,textPaint);
}else if (view.getTop() - groupHeaderHeight - parent.getPaddingTop() >= 0){
//分割线
c.drawRect(left,view.getTop()-4,right,view.getTop(),headPaint);
}
}
}
}@Override
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.onDrawOver(c, parent, state);
if (parent.getAdapter() instanceof ModelAdapter){
ModelAdapter adapter = (ModelAdapter) parent.getAdapter();
//返回可见区域的第一个item的position
int position = ((LinearLayoutManager)parent.getLayoutManager()).findFirstVisibleItemPosition();
//获取对应的position的View
View itemView = parent.findViewHolderForAdapterPosition(position).itemView;
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
int top = parent.getPaddingTop();
//当下一个是组的头部时
boolean isGroupHeader = adapter.isGroupHeader(position+1);
String groupName = adapter.getGroupName(position);
textPaint.getTextBounds(groupName,0,groupName.length(),textRect);
if (isGroupHeader){
//对顶部进行缩小效果,在视觉上是被下一个组的顶部推进去的
int bottom = Math.min(groupHeaderHeight,itemView.getBottom()-parent.getPaddingTop());
c.drawRect(left,top,right,top+bottom,headPaint);
c.clipRect(left,top,right,top+bottom);
c.drawText(groupName,left+20,top+bottom-groupHeaderHeight/2+textRect.height()/2,textPaint);
}else {
c.drawRect(left,top,right,top+groupHeaderHeight,headPaint);
c.drawText(groupName,left+20,top+groupHeaderHeight/2+textRect.height()/2,textPaint);
}
}
}@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
if (parent.getAdapter() instanceof ModelAdapter){
ModelAdapter adapter = (ModelAdapter) parent.getAdapter();
int position = parent.getChildAdapterPosition(view);
boolean isGroupHeader = adapter.isGroupHeader(position);
//判断itemView是不是头部
if (isGroupHeader){
//如果是头部,预留更大的地方
outRect.set(0,groupHeaderHeight,0,0);
}else {
outRect.set(0,4,0,0);
}
}
}
总结
其实这部分的重点就是自定义分割线(ItemDecoration),弄清绘制顺序以及设置偏移值的作用就简单多了。当然,难点(也不是难点)就是弄清除尺寸参数,特别是尺寸变化引起的换组顶部推动效果。
其次就是特别注意有padding的情况,相信大家看出小编在这设置了顶部padding,有padding的时候需要注意尺寸。(不过有设置padding的情况很少)