先来看看效果图

android 吸顶效果开源库 recyclerview吸顶效果_分割线

android 吸顶效果开源库 recyclerview吸顶效果_android 吸顶效果开源库_02

android 吸顶效果开源库 recyclerview吸顶效果_android 吸顶效果开源库_03

代码注释的还是比较清楚,所以就直接上代码了

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));
                }
            }
        }
    }
}

完。