RecyclerView自定义分割线以及吸顶效果

对于分割线来说RecyclerView是不同于ListView的,它没有类似ListView的driverheight的属性,需要手动实现。RecyclerView为我们提供了一个可扩展的ItemDecoration的类,用来绘制分割线以及其他方面的布局实现。
ItemDecoration有三个主要的方法我们需要去重写:
1、getItemOffsets(@NonNull Rect outRect, int itemPosition,
@NonNull RecyclerView parent):此方法的第一个参数outRect,用来给itemview的上下左右预留空间,例如outRect.set(0, 20, 0, 0); 就是在每一项的上方预留出来一个20px的空间高度。
2、onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull State state) 在getItemOffsets方法预留出一个位置后就可以在该方法中绘制任意布局,例如我们我们可以使用Canvas的drawRect方法绘制一个20px高度的分割线。
3、onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull State state) 此方法是最后执行的,是在item都绘制完成后,遮挡在上层的一个绘制方法,吸顶效果就可以借助此方法来实现。
实现一个简单的例子

public class MyDecoration extends RecyclerView.ItemDecoration {
    private Context context;
    private int dividerHeight = 10;//默认的分割线的高度
    private int headerHeight;//分组的头部的高度
    private Paint dividerPaint;//分割线的画笔
    private Paint headerPaint;//每一组 的画笔
    private Paint headerXdPaint;//吸顶的画笔
    private Paint textPaint;//绘制文字的画笔
    private Rect textRect;

    public MyDecoration(Context context) {
        this.context = context;
        init();
    }

    private void init() {
        headerHeight = dp2px(50);

        dividerPaint = new Paint();
        dividerPaint.setColor(Color.BLACK);

        headerPaint = new Paint();
        headerPaint.setColor(Color.RED);

        headerXdPaint = new Paint();
        headerXdPaint.setColor(Color.RED);

        textPaint = new Paint();
        textPaint.setTextSize(sp2px(16));
        textPaint.setAntiAlias(true);
        textPaint.setColor(Color.BLUE);
        textRect = new Rect();
    }

    @Override
    public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.onDraw(c, parent, state);
        //在这里面绘制每一组的header布局    就是outRect.set(0, 10, 0, 0); 给我们预留的空间上绘制
        if (parent.getAdapter() instanceof MyAdapter) {
            MyAdapter myAdapter = (MyAdapter) parent.getAdapter();
            //此count是当前屏幕中显示的所有的item个数,不是list集合中所有的个数
            int childCount = parent.getChildCount();
            int left = parent.getPaddingLeft();
            int right = parent.getWidth() - parent.getPaddingRight();

            for (int i = 0; i < childCount; i++) {
                View view = parent.getChildAt(i);
                //此position是该view在所有list集合中的位置
                int position = parent.getChildLayoutPosition(view);
                int top = view.getTop();
                boolean isGroupHeader = myAdapter.isGroupHeader(position);
                if(view.getTop() - headerHeight < parent.getPaddingTop()){
                    continue;
                }
                if (isGroupHeader) {
                    //先绘制一个矩形框框,在绘制文字
                    c.drawRect(left, top - headerHeight, right, top, headerPaint);
                    //在矩形里面绘制文字
                    String groupName = myAdapter.getGroupName(position);
                    textPaint.getTextBounds(groupName, 0, groupName.length(), textRect);
                    c.drawText(groupName, 0, groupName.length(), left + 20, top - headerHeight / 2 + textRect.height() / 2, textPaint);
                } else {
                    c.drawRect(left, top - dividerHeight, right, top, dividerPaint);
                }
            }
        }
    }

    @Override
    public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.onDrawOver(c, parent, state);
        //此方法时在onDraw方法执行完毕后才执行,也就是说在onDrawOver方法中绘制的任何布局都会遮挡item项
        //所以吸顶效果我们也可以绘制在顶部
        if (parent.getAdapter() instanceof MyAdapter) {
            MyAdapter myAdapter = (MyAdapter) parent.getAdapter();
            int left = parent.getPaddingLeft();
            int right = parent.getWidth() - parent.getPaddingRight();
            int top = parent.getPaddingTop();
            //屏幕中显示的第一个item
            int position = ((LinearLayoutManager) parent.getLayoutManager()).findFirstVisibleItemPosition();
            //该position下对应的view
            View view = parent.findViewHolderForAdapterPosition(position).itemView;
            //下一个view是否是头部
            boolean nextViewIsGroupHeader = myAdapter.isGroupHeader(position + 1);
            String groupName = myAdapter.getGroupName(position);
            textPaint.getTextBounds(groupName, 0, groupName.length(), textRect);
            if (nextViewIsGroupHeader) {
                int bottom = Math.min(headerHeight, view.getBottom() - top);
                c.drawRect(left, top, right, top+bottom, headerXdPaint);
                Log.e("onDrawOver---", view.getTop() + "&&&" + top);
                if(top + bottom - headerHeight / 2 - textRect.height() / 2 < top){
                    return;
                }
                c.drawText(groupName, 0, groupName.length(), left + 20, top + bottom - headerHeight / 2 + textRect.height() / 2, textPaint);
            } else {
                c.drawRect(left, top, right, top + headerHeight, headerXdPaint);
                c.drawText(groupName, 0, groupName.length(), left + 20, 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);
        //此方法参数的作用是 将每一个item项的左上右下 预留一个空间出来
        //outRect.set(0, 10, 0, 0); 就是在item项的顶部预留出来一个10px的位置,可以是分割线预留的高度,我们只需要在
        //onDraw 里面绘制这个分割线就ok了   也可以是分组使用的groupheader使用配合onDrawOver就可以实现吸顶效果

        if (parent.getAdapter() instanceof MyAdapter) {
            MyAdapter myAdapter = (MyAdapter) parent.getAdapter();
            //当前view在RecyclerView中的位置
            int position = parent.getChildLayoutPosition(view);
            boolean isGroupHeader = myAdapter.isGroupHeader(position);
            if (isGroupHeader) {
                outRect.set(0, headerHeight, 0, 0);
            } else {
                outRect.set(0, dividerHeight, 0, 0);
            }
        }
    }

    private int dp2px(int dp) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, Resources.getSystem().getDisplayMetrics());
    }

    private float sp2px(int sp) {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, Resources.getSystem().getDisplayMetrics());
    }
}

在Activity中使用:
public class RecyclerViewActivity extends AppCompatActivity {
    RecyclerView recycler_view;
    MyAdapter myAdapter;
    List<TestInfo> testInfoList;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_recyclerview);
        recycler_view = findViewById(R.id.recycler_view);
        init();
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
        recycler_view.setLayoutManager(linearLayoutManager);
        myAdapter = new MyAdapter(this, testInfoList);
        recycler_view.addItemDecoration(new MyDecoration(this));
        recycler_view.setAdapter(myAdapter);
    }

    void init() {
        testInfoList = new ArrayList<>();
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 15; j++) {
                testInfoList.add(new TestInfo("第" + i + "组测试数据" + j, i, "第" + i + "组"));
            }
        }
    }
}
实体类:
public class TestInfo {

    private String name;
    private int groupId;
    private String groupName;


    public TestInfo(String name, int groupId, String groupName) {
        this.name = name;
        this.groupId = groupId;
        this.groupName = groupName;
    }

    public String getGroupName() {
        return groupName;
    }

    public void setGroupName(String groupName) {
        this.groupName = groupName;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getGroupId() {
        return groupId;
    }

    public void setGroupId(int groupId) {
        this.groupId = groupId;
    }
}
Adapter的实现:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
    Context context;
    List<TestInfo> testInfoList;

    public MyAdapter(Context context, List<TestInfo> testInfoList) {
        this.context = context;
        this.testInfoList = testInfoList;
    }

    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View convertView = LayoutInflater.from(context).inflate(R.layout.rv_item_layout, parent, false);
        return new MyViewHolder(convertView);
    }

    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        holder.tv_item.setText(testInfoList.get(position).getName());
    }

    @Override
    public int getItemCount() {
        return testInfoList == null ? 0 : testInfoList.size();
    }

    public boolean isGroupHeader(int position) {
        if (position == 0) {
            return true;
        }

        int currentGroupId = testInfoList.get(position).getGroupId();
        int preGroupId = testInfoList.get(position - 1).getGroupId();
        return currentGroupId != preGroupId;
    }

    public String getGroupName(int position) {
        return testInfoList.get(position).getGroupName();
    }


    class MyViewHolder extends RecyclerView.ViewHolder {
        TextView tv_item;

        public MyViewHolder(@NonNull View itemView) {
            super(itemView);
            tv_item = itemView.findViewById(R.id.tv_item);
        }
    }
}

布局文件就比较简单了,一个RecyclerView的布局和一个item:

<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/recycler_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent">


</androidx.recyclerview.widget.RecyclerView>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/colorPrimary">

    <TextView
        android:id="@+id/tv_item"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:padding="20dp"
        android:textSize="16sp"/>


</LinearLayout>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,100评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,308评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,718评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,275评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,376评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,454评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,464评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,248评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,686评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,974评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,150评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,817评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,484评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,140评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,374评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,012评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,041评论 2 351

推荐阅读更多精彩内容