Android开发实战——通过实现ItemDecoration抽象类来实现recyclerview的吸顶效果
先来看看效果图(博客园不能放视频,蛋疼)
代码注释的还是比较清楚,所以就直接上代码了
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)); } } } } }