RecyclerView探索之通过ItemDecoration实现StickyHeader效果

我在上一篇《小甜点,RecyclerView 之 ItemDecoration 讲解及高级特性实践 》 讲解了 ItemDecoration 的基本用法及它的一些实践,抱着学习研究的态度,这一篇作为实践篇主要目的是尝试通过 ItemDecoration 来实现 RecyclerView 中的 StickyHeader 功能。

关于 StickyHeader 想必大家已经很清楚了,如果不有不清楚的,看下图:
这里写图片描述

如果要实现 StickyHeader 的话,首先,我们得明白普通的 Header 是怎么实现的。

ItemDecoration 实现普通的 Header

这里写图片描述

上面这张图是我微信的通讯录界面,大家可以看到微信按拼音和英文名首字母给账号进行了分组,上面灰色的 B 和 C 就是 Header。

之前在 ListView 时代,实现头部功能就是通过 ItemView 的 layout 布局实现的。
这里写图片描述

一个 ItemView 分为两个部分,如果这个 ItemView 是小组的第一个,那么它的 Header 就应该显示出来,不然就得隐藏,所以只要好处理分组与 ItemView 的位置关系,这个 Header 功能就很容易实现了。
这里写图片描述

现在,用 ItemDecoration 来实现头部,就不需要在每个 ItemView 中设置这个隐藏的 Header 部分了,ItemView 只需要关心它自己真正要表现的界面效果就好了,像这种零碎的事情就专门交给 ItemDecoration 来处理。

这里写图片描述

但不管是 ItemView 还是 ItemDecoration 来实现 Header,正确的数据分组永远是第一步。

而数据的分组离不开 Adapter 的配合,所以数据的分组应该由外部来完成,而不是 ItemDecoration 本身,那好,创建 ItemDecoration 第一步就是定义一个接口,用来获取分组信息。

public class GroupInfo {
   
    //组号
    private int mGroupID;
    // Header 的 title
    private String mTitle;


    public GroupInfo(int groupId, String title) {
        this.mGroupID = groupId;
        this.mTitle = title;
    }

    public int getGroupID() {
        return mGroupID;
    }

    public void setGroupID(int groupID) {
        this.mGroupID = groupID;
    }

    public String getTitle() {
        return mTitle;
    }

    public void setTitle(String title) {
        this.mTitle = title;
    }
}

上面代码 Header 的相关信息。

public class SectionDecoration extends RecyclerView.ItemDecoration {
   


    public interface GroupInfoCallback {
   
        GroupInfo getGroupInfo(int position);
    }
}

有了 GroupInfoCallback 回调,SectionItemDecoration 就可以通过它的 getGroupInfo() 方法来获取每个 ItemView 对应的分组信息。

我们再回到 Header 话题上来,因为是通过 ItemDecoration 来完成它,所以肯定要借助于它的 getItemOffsets() 方法。我们组与组之间的间隔设置成为一个 Header 的高度,然后组内的 ItemView 之间的间距是指定的间距值,通常为 1 px 或者 2 px。大家看图就明白了。

这张图与上面的那张差不多,但是灰色区域都是通过 ItemDecoration 中 getItemOffsets 方法操纵 outRect 参数撑开的。我们绘制 Header 只要计算出对应的位置然后通过 Canvas 就能为所欲为了。关键的一点在于 Header 只绘制在组内第一个 ItemView 的上方,所以我们还需要一个途径来获知 ItemView 在组内的位置。我们可以升级 GroupInfo 类,添加一个域用来标记 ItemView 在组内的位置,还需要提供一个方法来判断它是不是组内的第一个。

public class GroupInfo {
   
    //组号
    private int mGroupID;
    // Header 的 title
    private String mTitle;
    //ItemView 在组内的位置
    private int position;

    //代码有精简
    ......

    public boolean isFirstViewInGroup () {
        return position == 0<
  • 31
    点赞
  • 70
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 20
    评论
要在 Android RecyclerView 的头部和尾部设置圆角,可以使用 ItemDecoration。具体的实现步骤如下: 1. 创建自定义的 ItemDecoration 类,并在该类的构造函数中传入圆角半径大小等参数。 2. 在该类的 `onDraw()` 方法中,使用 `Canvas` 和 `Paint` 对象绘制圆角矩形。可以通过 `getItemOffsets()` 方法来设置矩形的偏移量,以避免覆盖到 RecyclerView 的内容。 3. 在 RecyclerView 的 Adapter 类中,判断当前的 position 是否为头部或尾部,如果是,则在 `onCreateViewHolder()` 方法中设置对应的圆角布局。 以下是示例代码: ```java public class MyItemDecoration extends RecyclerView.ItemDecoration { private int radius; // 圆角半径 private int padding; // 矩形偏移量 public MyItemDecoration(int radius, int padding) { this.radius = radius; this.padding = padding; } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { super.getItemOffsets(outRect, view, parent, state); if (parent.getChildAdapterPosition(view) == 0) { // 头部 outRect.top = padding; } else if (parent.getChildAdapterPosition(view) == parent.getAdapter().getItemCount() - 1) { // 尾部 outRect.bottom = padding; } } @Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { int childCount = parent.getChildCount(); for (int i = 0; i < childCount; i++) { View child = parent.getChildAt(i); RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); int left = child.getLeft() + params.leftMargin; int right = child.getRight() - params.rightMargin; int top = child.getTop() + params.topMargin; int bottom = child.getBottom() - params.bottomMargin; if (parent.getChildAdapterPosition(child) == 0) { // 头部 drawRoundRect(c, left, top, right, bottom + padding, radius, true, true, false, false); } else if (parent.getChildAdapterPosition(child) == parent.getAdapter().getItemCount() - 1) { // 尾部 drawRoundRect(c, left, top - padding, right, bottom, radius, false, false, true, true); } } } // 绘制圆角矩形 private void drawRoundRect(Canvas canvas, float left, float top, float right, float bottom, float radius, boolean tl, boolean tr, boolean bl, boolean br) { Paint paint = new Paint(); paint.setAntiAlias(true); paint.setColor(Color.WHITE); paint.setStyle(Paint.Style.FILL); Path path = new Path(); float[] radii = new float[8]; if (tl) { radii[0] = radius; radii[1] = radius; } if (tr) { radii[2] = radius; radii[3] = radius; } if (br) { radii[4] = radius; radii[5] = radius; } if (bl) { radii[6] = radius; radii[7] = radius; } path.addRoundRect(new RectF(left, top, right, bottom), radii, Path.Direction.CW); canvas.drawPath(path, paint); } } ``` 然后,在 RecyclerView 的 Adapter 中,可以根据 position 判断是否为头部或尾部,并设置对应的布局: ```java @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == TYPE_HEADER) { // 头部 View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_header, parent, false); return new HeaderViewHolder(view); } else if (viewType == TYPE_FOOTER) { // 尾部 View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_footer, parent, false); return new FooterViewHolder(view); } else { // 正常的 Item View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_normal, parent, false); return new NormalViewHolder(view); } } ```
评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

frank909

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值