> 文档中心 > Android 自定义UI 实战 03 RecyclerView 吸顶 列表实现

Android 自定义UI 实战 03 RecyclerView 吸顶 列表实现


Android 自定义UI 实战 03 RecyclerView 吸顶 列表实现

文章目录

  • Android 自定义UI 实战 03 RecyclerView 吸顶 列表实现
  • 前言
  • 一、吸顶效果准备工作
    • 1、创建item,和item 实现
    • 2、创建一个 Star
    • 3、创建一个 StarAdapter
    • 4、Activity 完整代码
  • 二、吸顶功能实现
    • 1、自定义 ItemDecoration
    • 2、StarDecoration 初始化
    • 3、重写 getItemOffsets() 方法
    • 4、绘制头部预留空间
    • 5、重写 onDraw() 绘制头部
    • 6、绘制吸顶效果
    • 7、StarDecoration 完整代码
  • 总结

前言

使用纯代码 加 注释的方式,可以更快的理解源码
如果你喜欢,请点个赞,后期会不断的深入讲解


一、吸顶效果准备工作

在绘制吸顶效果之前,需要先实现一个RecyclerView功能列表

1、创建item,和item 实现

新建一个item 的Layout 文件

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent">    <TextView android:id="@+id/user_name" android:text="姓名" android:gravity="center" android:layout_width="match_parent" android:layout_height="44dp" android:textSize="20sp" tools:ignore="MissingConstraints" /></RelativeLayout>

RecyclerView 中引用item

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    tools:context=".MainActivity">    <androidx.recyclerview.widget.RecyclerView android:id="@+id/main_rv" android:layout_width="match_parent" android:layout_height="match_parent" tools:listitem="@layout/item" /></LinearLayout>

2、创建一个 Star

public class Star {    String userName;    String groupName;    public String getUserName() { return userName;    }    public void setUserName(String userName) { this.userName = userName;    }    public String getGroupName() { return groupName;    }    public void setGroupName(String groupName) { this.groupName = groupName;    }    public Star(String userName, String groupName) { this.userName = userName; this.groupName = groupName;    }}

3、创建一个 StarAdapter

在StarAdapter 中实现 RecyclerView 功能

public class StarAdapter extends RecyclerView.Adapter<StarAdapter.StarViewHolder> {    private Context mContext;    private List<Star> starList;    private LayoutInflater inflater;   public StarAdapter(Context context, List<Star> stars){ this.mContext = context; this.starList = stars; this.inflater = LayoutInflater.from(mContext);    }    @NonNull    @Override    public StarViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view =  inflater.inflate(R.layout.item, null); return new StarViewHolder(view);    }    @Override    public void onBindViewHolder(@NonNull StarViewHolder holder, int position) { holder.textView.setText(starList.get(position).getUserName());if (position %2 == 0){    holder.textView.setBackgroundColor(Color.YELLOW);}else {    holder.textView.setBackgroundColor(Color.RED);}    }    @Override    public int getItemCount() { return starList.size();    }    public class StarViewHolder extends RecyclerView.ViewHolder {private TextView textView; public StarViewHolder(@NonNull View itemView) {     super(itemView);     textView = itemView.findViewById(R.id.user_name); }    }}

4、Activity 完整代码

public class MainActivity extends AppCompatActivity {    private RecyclerView recyclerView;    private StarAdapter starAdapter;    private List<Star> starList;    @Override    protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); init(); recyclerView = findViewById(R.id.main_rv); starAdapter = new StarAdapter(this, starList); recyclerView.setLayoutManager(new LinearLayoutManager(this)); recyclerView.setAdapter(starAdapter);    }    private void init(){ starList = new ArrayList<>(); 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));  }     } }    }}

上面源码中,我写了一个嵌套的for 循环,用来实现数据的赋值,显示为4组,每组组20条数据。代码运行效果如下:

在这里插入图片描述

二、吸顶功能实现

1、自定义 ItemDecoration

StarDecoration 中,我写了一个分辩率的转换方法

public class StarDecoration extends RecyclerView.ItemDecoration { /     * 分辩率     * @param context     * @param dpValue     * @return     */    private int dp2px(Context context, float dpValue){ return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue,  context.getResources().getDisplayMetrics());    }    }

2、StarDecoration 初始化

  //    分组栏的高度    private int headerHeight;    private int valueHeight = 50;    private Paint headPaint;    private Paint drawTextPaint;    private Rect textRect;    public StarDecoration(Context context){ //    顶部吸顶栏的高度 headerHeight = dp2px(context, valueHeight);// 每一组的头部的Paint headPaint = new Paint(); headPaint.setColor(Color.RED); drawTextPaint = new Paint(); drawTextPaint.setColor(Color.YELLOW); drawTextPaint.setTextSize(50); textRect = new Rect();    }

3、重写 getItemOffsets() 方法

ItemDecoration 想要绘制的话,就必须得重写 getItemOffsets() 方法。代码如下

    @Override    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view,   @NonNull RecyclerView parent, @NonNull RecyclerView.State state) { super.getItemOffsets(outRect, view, parent, state);// 绑定自己的 Adapter if (parent.getAdapter() instanceof StarAdapter){     StarAdapter starAdapter = (StarAdapter) parent.getAdapter();//     当前item 的位置     int position = parent.getChildLayoutPosition(view);//     分割线预留的空间     outRect.set(10, 10, 0, 0); }    }

上面的代码中,我们给ItemDecoration 预留了一个划线的空间

运行如下:

在这里插入图片描述

4、绘制头部预留空间

  1. 在Adapter 中判断当前组的第一个item 是不是头部
//    是否是组的第一个Item    public boolean isFirstItemOfGroup(int position) { if (position == 0) {     return true; } else {//    拿到当前位置的和前一个位置的 组名     String currentItemGroupName = getGroupName(position);     String preItemGroupName = getGroupName(position - 1);//    如果相等,则表示position 的 item 不是第一个,否则是     if (currentItemGroupName.equals(preItemGroupName)) {  return false;     } else {  return true;     } }    }
  1. 在 获取到头部 item 之后,预留一个绘制空间
//    判断 Item 是头部     boolean isGroupHeader = starAdapter.isFirstItemOfGroup(position);     if (isGroupHeader){//  headerHeight 是头部 Item 的高度  outRect.set(0, headerHeight, 0, 0);     }else {//  decorationTop   分割线的高度  outRect.set(0, decorationTop, 0, 0);     }

getItemOffsets() 中添加上面代码后,预留的 item 的头部空间就出来了,运行如下

在这里插入图片描述

5、重写 onDraw() 绘制头部

//  绘制自己    @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 starAdapter = (StarAdapter) parent.getAdapter();//      当前屏幕上展示的     int count = parent.getChildCount();//     实现 itemView 的宽度和分割线的宽度一样     int left = parent.getPaddingLeft();     int right = parent.getWidth() - parent.getPaddingLeft();     for (int i = 0; i < count; i++) {  View view = parent.getChildAt(i);//  当前item 的位置  int position = parent.getChildLayoutPosition(view);//  判断是否是头部  boolean isGroupHeader = starAdapter.isFirstItemOfGroup(position);  if (isGroupHeader){      c.drawRect(left, view.getTop() - headerHeight, right, view.getTop(), headPaint);//      获取当前组名      String groupName = starAdapter.getGroupName(position);      c.drawText(groupName,left + groupNameLeftPadding,view.getTop() - headerHeight / 2 + textRect.height() / 2,drawTextPaint);  }else {      c.drawRect(left, view.getTop() - valueHeight, right, view.getTop(), headPaint);  }     } }    }

代码运行结果如下:

在这里插入图片描述

6、绘制吸顶效果

//    绘制吸顶效果    @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 starAdapter = (StarAdapter) parent.getAdapter();     int left = parent.getPaddingLeft();     int right = parent.getRight();     int top = parent.getTop();//     当前显示在界面的第一个 item     int position = ((LinearLayoutManager)parent.getLayoutManager()).findFirstVisibleItemPosition();     View itemView = parent.findViewHolderForAdapterPosition(position).itemView;//     获取当前的上一个 头部 item     boolean isFirstItemOfGroup = starAdapter.isFirstItemOfGroup(position +1 );     if(isFirstItemOfGroup){  int bottom = Math.min(top + headerHeight, itemView.getBottom());  c.drawRect(left, top, right, bottom, headPaint);  String groupName = starAdapter.getGroupName(position);  drawTextPaint.getTextBounds(groupName, 0, groupName.length(), textRect);  c.clipRect(left, top, right, bottom);  c.drawText(groupName, left + 20,   bottom - headerHeight / 2 + textRect.height() / 2, drawTextPaint);     }else {//  固定的头部信息  c.drawRect(left, top, right, top + headerHeight, headPaint);  String groupName = starAdapter.getGroupName(position);  drawTextPaint.getTextBounds(groupName, 0, groupName.length(), textRect);//  绘制文字  c.drawText(groupName, left + 20, top + headerHeight / 2 + textRect.height() /2, drawTextPaint);     } }    }

运行结果如下:

在这里插入图片描述

7、StarDecoration 完整代码

public class StarDecoration extends RecyclerView.ItemDecoration {    //    分组栏的高度    private int headerHeight;    private int valueHeight = 50;    private Paint headPaint;    private Paint drawTextPaint;    private Rect textRect;    private int decorationTop = 4;//    组名的 padding    private int groupNameLeftPadding = 20;    public StarDecoration(Context context){ //    顶部吸顶栏的高度 headerHeight = dp2px(context, valueHeight);// 每一组的头部的Paint headPaint = new Paint(); headPaint.setColor(Color.RED); drawTextPaint = new Paint(); drawTextPaint.setColor(Color.YELLOW); drawTextPaint.setTextSize(50); textRect = new Rect();    }//  绘制自己    @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 starAdapter = (StarAdapter) parent.getAdapter();//      当前屏幕上展示的     int count = parent.getChildCount();//     实现 itemView 的宽度和分割线的宽度一样     int left = parent.getPaddingLeft();     int right = parent.getWidth() - parent.getPaddingLeft();     for (int i = 0; i < count; i++) {  View view = parent.getChildAt(i);//  当前item 的位置  int position = parent.getChildLayoutPosition(view);//  判断是否是头部  boolean isGroupHeader = starAdapter.isFirstItemOfGroup(position);  if (isGroupHeader){      c.drawRect(left, view.getTop() - headerHeight, right, view.getTop(), headPaint);//      获取当前组名      String groupName = starAdapter.getGroupName(position);      c.drawText(groupName,left + groupNameLeftPadding,view.getTop() - headerHeight / 2 + textRect.height() / 2,drawTextPaint);  }else {      c.drawRect(left, view.getTop() - decorationTop, 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 StarAdapter){     StarAdapter starAdapter = (StarAdapter) parent.getAdapter();     int left = parent.getPaddingLeft();     int right = parent.getRight();     int top = parent.getTop();//     当前显示在界面的第一个 item     int position = ((LinearLayoutManager)parent.getLayoutManager()).findFirstVisibleItemPosition();     View itemView = parent.findViewHolderForAdapterPosition(position).itemView;//     获取当前的上一个 头部 item     boolean isFirstItemOfGroup = starAdapter.isFirstItemOfGroup(position +1 );     if(isFirstItemOfGroup){  int bottom = Math.min(top + headerHeight, itemView.getBottom());  c.drawRect(left, top, right, bottom, headPaint);  String groupName = starAdapter.getGroupName(position);  drawTextPaint.getTextBounds(groupName, 0, groupName.length(), textRect);  c.clipRect(left, top, right, bottom);  c.drawText(groupName, left + 20,   bottom - headerHeight / 2 + textRect.height() / 2, drawTextPaint);     }else {//  固定的头部信息  c.drawRect(left, top, right, top + headerHeight, headPaint);  String groupName = starAdapter.getGroupName(position);  drawTextPaint.getTextBounds(groupName, 0, groupName.length(), textRect);//  绘制文字  c.drawText(groupName, left + 20, top + headerHeight / 2 + textRect.height() /2, drawTextPaint);     } }    }    @Override    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view,   @NonNull RecyclerView parent, @NonNull RecyclerView.State state) { super.getItemOffsets(outRect, view, parent, state);// 绑定自己的 Adapter if (parent.getAdapter() instanceof StarAdapter){     StarAdapter starAdapter = (StarAdapter) parent.getAdapter();//     当前item 的位置     int position = parent.getChildLayoutPosition(view);//    判断 Item 是头部     boolean isGroupHeader = starAdapter.isFirstItemOfGroup(position);     if (isGroupHeader){//  headerHeight 是头部 Item 的高度  outRect.set(0, headerHeight, 0, 0);     }else {//  decorationTop   分割线的高度  outRect.set(0, decorationTop, 0, 0);     } }    }    /     * 分辩率     * @param context     * @param dpValue     * @return     */    private int dp2px(Context context, float dpValue){ return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue,  context.getResources().getDisplayMetrics());    }}

总结

计算的我有点头晕