Android 自定义UI 实战 03 RecyclerView 吸顶 仿微信好友列表

举报
半身风雪 发表于 2022/06/24 12:03:11 2022/06/24
【摘要】 Android 自定义UI 实战 03 RecyclerView 吸顶@TOC 前言使用纯代码 加 注释的方式,可以更快的理解源码如果你喜欢,请点个赞,后期会不断的深入讲解 一、吸顶效果准备工作在绘制吸顶效果之前,需要先实现一个RecyclerView功能列表 1、创建item,和item 实现新建一个item 的Layout 文件<?xml version="1.0" encoding=...

Android 自定义UI 实战 03 RecyclerView 吸顶

@TOC


前言

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


一、吸顶效果准备工作

在绘制吸顶效果之前,需要先实现一个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());
    }

}

总结

计算的我有点头晕

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。