先来看看效果图
代码注释的还是比较清楚,所以就直接上代码了
StarDecoration.java
public class StarDecoration extends RecyclerView.ItemDecoration {
private int headerHeight;
private Context context;
private Paint headerPaint;
private Paint textPaint;
private Rect textRect;
public StarDecoration(Context context) {
this.context = context;
headerHeight = dp2px(context, 100);
// 头部画笔
headerPaint = new Paint();
headerPaint.setColor(Color.RED);
// 文字画笔
textPaint = new Paint();
textPaint.setColor(Color.WHITE);
textPaint.setTextSize(50);
textRect = new Rect();
}
/*
*
* recyclerView绘制流程 onDraw-->绘制itemview-->onDrawOver
* 所以在绘制之后,itemview会覆盖onDraw,onDrawOver会覆盖itemview
* 因此我们在onDraw方法内绘制移动的header,在onDrawOver方法内绘制吸顶的header
* 具体区别细节可以去网上搜索,这里不再做另外的分析
*
* */
@Override
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.onDraw(c, parent, state);
if (parent.getAdapter() instanceof StarAdapter) {
StarAdapter adapter = (StarAdapter) parent.getAdapter();
// 当前屏幕范围内显示的条数
int count = parent.getChildCount();
// 设置绘制时的左右位置
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
// 循环当前屏幕范围内的item
for (int i = 0; i < count; i++) {
// 获取view
View view = parent.getChildAt(i);
// 拿到该view在adapter里面的position
int position = parent.getChildLayoutPosition(view);
// 通过position判断view是否为组头部
boolean isGroupHeader = adapter.isGroupHeader(position);
// 如果recyclerview有paddingTop,不加下面判断绘制会出错。会使padding部分也跟着绘制
// 可以不加这个判断,试试看效果到底是如何
if (view.getTop() - parent.getPaddingTop() - headerHeight >= 0) {
if (isGroupHeader) {
// 如果为组头部,画头部
c.drawRect(left, view.getTop() - headerHeight, right, view.getTop(), headerPaint);
// 画文字
String groupName = adapter.getGroupName(position);
textPaint.getTextBounds(groupName, 0, groupName.length(), textRect);
c.drawText(groupName, left + 10, view.getTop() - (headerHeight - textRect.height()) / 2, textPaint);
} else {
//如果不为组头部,添加4dp分割线
c.drawRect(left, view.getTop() - 4, right, view.getTop(), headerPaint);
}
}
}
}
}
@Override
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.onDrawOver(c, parent, state);
if (parent.getAdapter() instanceof StarAdapter) {
StarAdapter adapter = (StarAdapter) 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();
// 由于是吸顶,所以直接设置top的位置
int top = parent.getPaddingTop();
// 判断第二个item是否为组的头部 如果是,表示下一组的头部即将到来,我们要准备在顶部绘制下一组的头部,且当前头部准备移除
boolean isGroupHeader = adapter.isGroupHeader(position + 1);
if (isGroupHeader) {
// itemView.getBottom() - top表示当前view在屏幕内显示的高度,与headerHeight对比 取最小值,谁的值小就以谁为基准开始移除
int bottom = Math.min(headerHeight, itemView.getBottom() - top);
// 头部根据bottom的值逐渐变小而逐渐往上移除
c.drawRect(left, top, right, top + bottom, headerPaint);
// 画文字
String groupName = adapter.getGroupName(position);
textPaint.getTextBounds(groupName, 0, groupName.length(), textRect);
// 绘制的文字的高度不能超出的区域,不加此行代码,当设置了paddingtop值的时候,文字会在padding部分绘制
// 可以不加这行代码,试试看效果到底是如何
c.clipRect(left, top, right, top + bottom);
c.drawText(groupName, left + 10, top + bottom - headerHeight / 2 + textRect.height() / 2, textPaint);
} else {
// 否 表示头部不变,则绘制内容不变
c.drawRect(left, top, right, top + headerHeight, headerPaint);
String groupName = adapter.getGroupName(position);
textPaint.getTextBounds(groupName, 0, groupName.length(), textRect);
c.drawText(groupName, left + 10, top + headerHeight / 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 StarAdapter) {
StarAdapter adapter = (StarAdapter) parent.getAdapter();
int position = parent.getChildLayoutPosition(view);
// 判断itemview是否为头部
boolean isGroupHeader = adapter.isGroupHeader(position);
if (isGroupHeader) {
//如果为头部,预留大空间
outRect.set(0, headerHeight, 0, 0);
} else {
//如果不为头部,预留4dp分割线
outRect.set(0, 4, 0, 0);
}
}
}
private int dp2px(Context context, float dpValue) {
float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale * 0.5f);
}
}
StarAdapter.java
public class StarAdapter extends RecyclerView.Adapter<StarAdapter.ViewHolder> {
private Context context;
private List<Star> starList;
public StarAdapter(Context context, List<Star> starList) {
this.context = context;
this.starList = starList;
}
/*
* 判断是否为组的第一项,如果是的话return true
* */
public boolean isGroupHeader(int position){
if(position == 0){
return true;
} else {
// 如果GroupName与前一个GroupName相等,表示是同一组,该item则不需要加头部
if(getGroupName(position).equals(getGroupName(position - 1))){
return false;
} else {
return true;
}
}
}
/*
* 获取GroupName
* */
public String getGroupName(int position){
return starList.get(position).getGroupName();
}
@NonNull
@Override
public StarAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.rv_top_item, null);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull StarAdapter.ViewHolder holder, int position) {
holder.tv.setText(starList.get(position).getName());
}
@Override
public int getItemCount() {
return starList == null ? 0 : starList.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
private TextView tv;
public ViewHolder(@NonNull View itemView) {
super(itemView);
tv = itemView.findViewById(R.id.tv_star);
}
}
}
Activity.java
public class RvTopActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private List<Star> starList = new ArrayList<>();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_recyclerview_top);
initData();
recyclerView = findViewById(R.id.rv_top);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
//自定义分割线
recyclerView.addItemDecoration(new StarDecoration(this));
recyclerView.setAdapter(new StarAdapter(this, starList));
}
private void initData() {
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 20; j++) {
if (i % 2 == 0) {
starList.add(new Star("何炅" + j, "快乐家族" + i));
} else {
starList.add(new Star("汪涵" + j, "天天兄弟" + i));
}
}
}
}
}
完。