Android RecyclerView-使用Itemdecoration实现粘性头部功能,详细到具体步骤(1)

总结

本文讲解了我对Android开发现状的一些看法,也许有些人会觉得我的观点不对,但我认为没有绝对的对与错,一切交给时间去证明吧!愿与各位坚守的同胞们互相学习,共同进步!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

onDraw(c, parent);
}
@Deprecated
public void onDraw(Canvas c, RecyclerView parent) {
}

/**

  • 绘制除Item内容以外的东西,这个方法是在Item的内容绘制之后才执行的,
  • 所以该方法绘制的东西会将Item的内容覆盖住,既显示在Item之上.
  • 一般配合getItemOffsets来绘制分组的头部等.
  • @param c Canvas 画布
  • @param parent RecyclerView
  • @param state RecyclerView的状态
    */
    public void onDrawOver(Canvas c, RecyclerView parent, State state) {
    onDrawOver(c, parent);
    }

/**

  • @deprecated
  • Override {@link #onDrawOver(Canvas, RecyclerView, RecyclerView.State)}
    */
    @Deprecated
    public void onDrawOver(Canvas c, RecyclerView parent) {
    }

/**

  • @deprecated
  • Use {@link #getItemOffsets(Rect, View, RecyclerView, State)}
    */
    @Deprecated
    public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
    outRect.set(0, 0, 0, 0);
    }

/**

  • 设置Item的布局四周的间隔.
  • @param outRect 确定间隔 Left Top Right Bottom 数值的矩形.
  • @param view RecyclerView的ChildView也就是每个Item的的布局.
  • @param parent RecyclerView本身.
  • @param state RecyclerView的各种状态.
    */
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {
    getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),
    parent);
    }
    }

这里面呢有个问题一定要明白几个问题:

  • getItemOffsets这个方法设置的Item间隔到底是那个间隔?

我们来看一张图.

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们知道getItemOffsets()第一个参数是一个矩形的对象,这个对象的left、 top、right、bottpm四个属性值分别表示图中的outRect.left、outRect.top、outRect.right、outRect.bottom四个线段所表示的空间.也就是说当RecyclerView的Item再确定自己的大小的时候会将getItemOffsets()里面的Rect对象的Left、Top、Right、Bottom属性取出来,看看需要再Item布局的四周留出多大的空间.我们来看下源码:

Rect getItemDecorInsetsForChild(View child) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (!lp.mInsetsDirty) {
return lp.mDecorInsets;
}

if (mState.isPreLayout() && (lp.isItemChanged() || lp.isViewInvalid())) {
// changed/invalid items should not be updated until they are rebound.
return lp.mDecorInsets;
}
final Rect insets = lp.mDecorInsets;
insets.set(0, 0, 0, 0);
final int decorCount = mItemDecorations.size();
for (int i = 0; i < decorCount; i++) {
mTempRect.set(0, 0, 0, 0);
//这里呢mTempRect就是我们再getItemOffsets()里面的第一个Rect的对象,我们再实现类的方法里面给mTempRect赋值.
mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState);
insets.left += mTempRect.left;
insets.top += mTempRect.top;
insets.right += mTempRect.right;
insets.bottom += mTempRect.bottom;
}
lp.mInsetsDirty = false;
return insets;
}

这里呢就是RecyclerView再测量每个Child的大小的时候都把insets这个矩形的l t r b 数值都加上了.insets就是方法getItemDecorInsetsForChild()返回的矩形对象.
/**

  • Measure a child view using standard measurement policy, taking the padding
  • of the parent RecyclerView and any added item decorations into account.
  • If the RecyclerView can be scrolled in either dimension the caller may

  • pass 0 as the widthUsed or heightUsed parameters as they will be irrelevant.
  • @param child Child view to measure
  • @param widthUsed Width in pixels currently consumed by other views, if relevant
  • @param heightUsed Height in pixels currently consumed by other views, if relevant
    */
    public void measureChild(View child, int widthUsed, int heightUsed) {
    final LayoutParams lp = (LayoutParams) child.getLayoutParams();

final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
widthUsed += insets.left + insets.right;
heightUsed += insets.top + insets.bottom;
final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(),
getPaddingLeft() + getPaddingRight() + widthUsed, lp.width,
canScrollHorizontally());
final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(),
getPaddingTop() + getPaddingBottom() + heightUsed, lp.height,
canScrollVertically());
if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) {
child.measure(widthSpec, heightSpec);
}
}

源码的讲解过于粗糙,希望大家见谅,目的就是为了让大家知道这个getItemOffsets()方法是怎么让RecyclerView再Item之外留出空间的.

  • onDraw()和onDrawOver()方法应该用哪一个?

首先我们看过上面的代码之后知道,onDraw执行再Item的绘制之前,也就是ItemDecoration的onDraw方法先执行,再执行Item的onDraw方法,这样Item的内容就会覆盖在ItemDecoration的onDraw上面.ItemDecoration的onDrawOver()方法执行在Item的绘制之后,那就是onDrawOver()绘制的内容会覆盖再Item内容之上.这样就形成了层层遮盖的问题,那么我们平常的分割线通常绘制在ItemDecoration的onDraw()方法里面,为了避免Item的内容覆盖掉,我们就要getItemOffsets()为我们留出绘制的空间了.这样我们的思路不是不有了呢.

我们可以用onDrawOver()和getItemOffsets()方法一起使用来实现Item的粘性头部和顶部悬浮的效果.

三 代码部分

需求分析:这部分其实是写代码前尤为重要的一部分,再分析的过程中你可以知道我们要完成的是哪些功能,用什么东西去完成,怎么才能更好的去完成.最后自己能确定出一套完美实现需求的方案.

我们要做的是区域分组显示,每个分组的开始要有一个粘性头部.如图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 数据准备

首先后台返回的数据一定要有组类区分,每个分组的标记不能一样,最好是我们方便处理的.该Demo采用的标记位是int类型的标记tag,每组的标记以此+1,每五个城市分为一组,每组的第一个城市当做头部局显示的内容.我们的分组头部的高度为40dp.

  • getItemOffsets()
    该方法再recyclerView的每个Item测量大小的时候都会被调用到, 我们要在该方法里面判断出那个HeadItem并且给HeadItem留出绘制的空间,这里有两种方式.
    第一种方式:
    给Item 的Top留出空间,也就是outRect.top属性赋值.
    第二种方式:
    给Item 的Bottom留出空间也就是outRect.bottpm属性赋值.
    因为我们在列表一开始的时候就要绘制一次Head,也就是说我们要留出Head的空间,那么我们只能选择第一种方法去预留空间了. 当你选择方式1的时候,给outRect.top赋值,这样的话我们判断是否是HeadItem的话就要拿当前Item的标记跟前一个Item的标记判断了.如果用第二种的话就要用当前的标记跟下一个Item的标记判断了.
    下面我来解释下第一种方式,第二种方式雷同:
    a b c d e f g h i
    分组1 abc
    分组2 def
    分组3 ghi
    如果 a d g 是HeadItem . a的tag = 1 , b的tag = 1, c 的tag = 1…d的tag = 2,e的tag = 2 ,f的tag = 2,g的tag = 3…等等 .
    前一个Item的tag用 preTag 来表示 ,初始值为 -1.
    假如当前的Item为a,当前tag = 1,那么它的前一个Item为空,也就是发现preTag和a的tag不一样,那么a就是分组的头部.
    假如当前的Item为b,当前tag = 1,那么它前一个preTag 也就是a的tag = 1,发现一样那就是是同一组的.
    假如当前的Item为d,当前tag = 2,那么它前一个preTag 也就是c的tag = 1,发现前一个的tag跟当前的不一样,那么当前的就是新分组的第一个头部Item.代码是最有说服力的,下面来看代码:

@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
if (citiList == null || citiList.size() == 0) {
return;
}
int adapterPosition = parent.getChildAdapterPosition(view);
RecBean.CitiListBean beanByPosition = getBeanByPosition(adapterPosition);
if(beanByPosition == null){
return;
}
int preTage = -1;
int tage = beanByPosition.getTage();
//一定要记住这个 >= 0
if(adapterPosition - 1 >= 0) {
RecBean.CitiListBean nextBean = getBeanByPosition(adapterPosition - 1);
if (nextBean == null) {
return;
}

最后

针对Android程序员,我这边给大家整理了一些资料,包括不限于高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter等全方面的Android进阶实践技术;希望能帮助到大家,也节省大家在网上搜索资料的时间来学习,也可以分享动态给身边好友一起学习!

  • Android前沿技术大纲

  • 全套体系化高级架构视频

Android高级架构资料、源码、笔记、视频。高级UI、性能优化、架构师课程、混合式开发(ReactNative+Weex)全方面的Android进阶实践技术,群内还有技术大牛一起讨论交流解决问题。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 27
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值