这是我见过有关Android RecyclerView最好的一篇文章:深入解析 RecyclerView.ItemDecoration类(含实例讲解)

转载自:https://www.aliyun.com/jiaocheng/10353.html

  • 摘要:前言RecyclerView在Android开发中非常常用,如果能结合ItemDecoration类使用,那么将大大提高RecyclerView的表现效果本文全面解析了ItemDecoration类,包括ItemDecoration类简介、使用方法&;实例讲解,最终结合自定义View实现时间轴UI开发,希望你们会喜欢。ItemDecoration类属于RecyclerView的高级用法阅读本文前请先学习RecyclerView的使用:Android开发:ListVie


  • 这是我见过有关Android RecyclerView最好的一篇文章:深入解析 RecyclerView.ItemDecoration类(含实例讲解)_第1张图片

    前言 

    RecyclerView在 Android开发中非常常用,如果能结合 ItemDecoration类使用,那么将大大提高 RecyclerView的表现效果 
    本文全面解析了 ItemDecoration类,包括 ItemDecoration类简介、使用方法 &; 实例讲解,最终结合 自定义View实现 时间轴UI开发,希望你们会喜欢。


    ItemDecoration类属于RecyclerView的高级用法
    阅读本文前请先学习RecyclerView的使用:Android开发:ListView、AdapterView、RecyclerView全面解析



    目录 
    这是我见过有关Android RecyclerView最好的一篇文章:深入解析 RecyclerView.ItemDecoration类(含实例讲解)_第2张图片
    目录 

    1. ItemDecoration类 简介 
    1.1 定义 

    RecyclerView类的静态内部类


    1.2 作用 

    向 RecyclerView中的 ItemView 添加装饰

     

     

    即绘制更多内容,丰富ItemView的UI效果

     


    2. 具体使用 

    ItemDecoration类中仅有3个方法,具体如下:


    public class TestDividerItemDecoration extends RecyclerView.ItemDecoration { 
    // 方法1:getItemOffsets() 
    // 作用:设置ItemView的内嵌偏移长度(inset) 
    @Override 
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { 
    ... 

    // 方法2:onDraw() 
    // 作用:在子视图上设置绘制范围,并绘制内容 
    // 类似平时自定义View时写onDraw()一样 
    // 绘制图层在ItemView以下,所以如果绘制区域与ItemView区域相重叠,会被遮挡 
    @Override 
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { 
    ... 

    // 方法3:onDrawOver() 
    // 作用:同样是绘制内容,但与onDraw()的区别是:绘制在图层的最上层 
    @Override 
    public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { 
    ... 

    下面,我将详细介绍这3个方法。


    2.1 getItemOffsets() 
    2.1.1 作用 

    设置ItemView的内嵌偏移长度(inset)


    如图,其实 RecyclerView 中的 ItemView 外面会包裹着一个矩形( outRect) 
    内嵌偏移长度 是指:该矩形( outRect)与 ItemView的间隔 

    这是我见过有关Android RecyclerView最好的一篇文章:深入解析 RecyclerView.ItemDecoration类(含实例讲解)_第3张图片
    示意图 

    内嵌偏移长度分为4个方向:上、下、左、右,并由 outRect 中的 top、left、right、bottom参数 控制

     

    top、left、right、bottom参数默认 = 0,即矩形和Item重叠,所以看起来矩形就消失了

     



    这是我见过有关Android RecyclerView最好的一篇文章:深入解析 RecyclerView.ItemDecoration类(含实例讲解)_第4张图片
    示意图 
    2.1.2 具体使用 
    @Override 
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { 
    // 参数说明: 
    // 1. outRect:全为 0 的 Rect(包括着Item) 
    // 2. view:RecyclerView 中的 视图Item 
    // 3. parent:RecyclerView 本身 
    // 4. state:状态 
    outRect.set(50, 0, 0,50); 
    // 4个参数分别对应左(Left)、上(Top)、右(Right)、下(Bottom) 
    // 上述语句代表:左&;下偏移长度=50px,右 &; 上 偏移长度 = 0 
    ... 

    这是我见过有关Android RecyclerView最好的一篇文章:深入解析 RecyclerView.ItemDecoration类(含实例讲解)_第5张图片
    示意图 
    2.1.3 源码分析 

    RecyclerView本质上是一个自定义 ViewGroup,子视图 child = 每个 ItemView 
    其通过 LayoutManager测量并布局 ItemView 
    public void measureChild(View child, int widthUsed, int heightUsed) { 
    // 参数说明: 
    // 1. child:要测量的子view(ItemView) 
    // 2. widthUsed: 一个ItemView的所有ItemDecoration占用的宽度(px) 
    // 3. heightUsed:一个ItemView的所有ItemDecoration占用的高度(px) 
    final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 
    final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child); 
    // 累加当前ItemDecoration 4个属性值->>分析1 
    widthUsed += insets.left + insets.right; 
    // 计算每个ItemView的所有ItemDecoration的宽度 
    heightUsed += insets.top + insets.bottom; 
    // 计算每个ItemView的所有ItemDecoration的高度 
    final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(), 
    getPaddingLeft() + getPaddingRight() + widthUsed, lp.width, 
    canScrollHorizontally()); 
    // 测量child view(ItemView)的宽度 
    // 第三个参数设置 child view 的 padding,即ItemView的Padding 
    // 而该参数把 insets 的值算进去,所以insets 值影响了每个 ItemView 的 padding值 
    // 高度同上 
    final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(), 
    getPaddingTop() + getPaddingBottom() + heightUsed, lp.height, 
    canScrollVertically()); 
    if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) { 
    child.measure(widthSpec, heightSpec); 


    // 分析完毕,请跳出 
    <-- 分析1:getItemDecorInsetsForChild()--> 
    Rect getItemDecorInsetsForChild(View child) { 
    final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 
    insets.set(0, 0, 0, 0); 
    for (int i = 0; i < decorCount; i++) { 
    mTempRect.set(0, 0, 0, 0); 
    // 获取getItemOffsets() 中设置的值 
    mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState); 
    // 将getItemOffsets() 中设置的值添加到insets 变量中 
    insets.left += mTempRect.left; 
    insets.top += mTempRect.top; 
    insets.right += mTempRect.right; 
    insets.bottom += mTempRect.bottom; 

    // 最终返回 
    return insets; 

    // insets介绍 
    // 1. 作用: 
    // a. 把每个ItemView的所有 ItemDecoration 的 getItemOffsets 中设置的值累加起来,(每个ItemView可添加多个ItemDecoration) 
    // 即把每个ItemDecoration的left, top, right, bottom 4个属性分别累加 
    // b. 记录上述结果 
    // c. inset就像padding和margin一样,会影响view的尺寸和位置 
    // 2. 使用场景:设置View的边界大小,使得其大小>View的背景大小 
    // 如 按钮图标(View的背景)较小,但是我们希望按钮有较大的点击热区(View的边界大小) 
    // 返回到分析1进来的原处 
    总结 
    结论: outRect4个属性值影响着 ItemView的Padding值 
    具体过程:在 RecyclerView进行子 View宽高测量时( measureChild()),会将 getItemOffsets()里设置的 outRect4个属性值( Top、Bottom、Left、Right)通过 insert值累加 ,并最终添加到子 View的 Padding属性中 

    2.2 onDraw() 
    2.2.1 作用 

    通过 Canvas 对象绘制内容


    2.2.2 具体使用 
    使用方法类似自定义View时的 onDraw()

     

    请看我写的自定义View文章:自定义View Draw过程- 最易懂的自定义View原理系列(4)

     



    @Override 
    public void onDraw(Canvas c, RecyclerView parent, 
    RecyclerView.State state) { 
    .... 
    // 使用类似自定义View时的 onDraw() 

    2.2.3 特别注意 

    注意点1:Itemdecoration的onDraw()绘制会先于ItemView的onDraw()绘制,所以如果在Itemdecoration的onDraw()中绘制的内容在ItemView边界内,就会被ItemView遮挡住。如下图:

     

     

    此现象称为onDraw()的 OverDraw现象

     


    这是我见过有关Android RecyclerView最好的一篇文章:深入解析 RecyclerView.ItemDecoration类(含实例讲解)_第6张图片
    示意图 

    解决方案:配合前面的 getItemOffsets() 一起使用在outRect矩形 与 ItemView的间隔区域 绘制内容

     

     

    即:通过getItemOffsets() 设置与 Item 的间隔区域,从而获得与ItemView不重叠的绘制区域

     


    这是我见过有关Android RecyclerView最好的一篇文章:深入解析 RecyclerView.ItemDecoration类(含实例讲解)_第7张图片
    示意图 

    注意点2: getItemOffsets() 针对是每一个 ItemView的,而 onDraw() 针对 RecyclerView 本身

     

    解决方案:在 使用onDraw()绘制时,需要先遍历RecyclerView 的所有ItemView分别获取它们的位置信息,然后再绘制内容

     


    此处遍历的RecyclerView的ItemView(即Child view),并不是 Adapter 设置的每一个 item,而是可见的 item 
    因为只有可见的Item 才是RecyclerView的 Child view


    @Override 
    public void onDraw(Canvas c, RecyclerView parent, 
    RecyclerView.State state) { 
    // RecyclerView 的左边界加上 paddingLeft距离 后的坐标位置 
    final int left = parent.getPaddingLeft(); 
    // RecyclerView 的右边界减去 paddingRight 后的坐标位置 
    final int right = parent.getWidth() - parent.getPaddingRight(); 
    // 即左右边界就是 RecyclerView 的 ItemView区域 
    // 获取RecyclerView的Child view的个数 
    final int childCount = parent.getChildCount(); 
    // 设置布局参数 
    final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child 
    .getLayoutParams(); 
    // 遍历每个RecyclerView的Child view 
    // 分别获取它们的位置信息,然后再绘制内容 
    for (int i = 0; i < childCount; i++) { 
    final View child = parent.getChildAt(i); 
    int index = parent.getChildAdapterPosition(view); 
    // 第一个Item不需要绘制 
    if ( index == 0 ) { 
    continue; 

    // ItemView的下边界:ItemView 的 bottom坐标 + 距离RecyclerView底部距离 +translationY 
    final int top = child.getBottom() + params.bottomMargin + 
    Math.round(ViewCompat.getTranslationY(child)); 
    // 绘制分割线的下边界 = ItemView的下边界+分割线的高度 
    final int bottom = top + mDivider.getIntrinsicHeight(); 
    mDivider.setBounds(left, top, right, bottom); 
    mDivider.draw(c); 



    2.2.4 应用场景 

    在丰富 ItemView 的显示效果,即在ItemView 的基础上绘制内容

     

     

    如分割线等等

     


    2.2.5 实例讲解 
    实例说明:在 ItemView设计一个高度为 10 px 的红色分割线 
    思路 
    通过 getItemOffsets()设置与 Item 的下间隔区域 = 10 px

     

    设置好onDraw()可绘制的区域

     


    通过 onDraw()绘制一个高度 = 10px的矩形(填充颜色=红色) 


    这是我见过有关Android RecyclerView最好的一篇文章:深入解析 RecyclerView.ItemDecoration类(含实例讲解)_第8张图片
    示意图 

    具体实现 

    步骤1:自定义ItemDecoration类

     

    ItemDecoration.java


    public class DividerItemDecoration extends RecyclerView.ItemDecoration { 
    private Paint mPaint; 
    // 在构造函数里进行绘制的初始化,如画笔属性设置等 
    public DividerItemDecoration() { 
    mPaint = new Paint(); 
    mPaint.setColor(Color.RED); 
    // 画笔颜色设置为红色 

    // 重写getItemOffsets()方法 
    // 作用:设置矩形OutRect 与 Item 的间隔区域 
    @Override 
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { 
    super.getItemOffsets(outRect, view, parent, state); 

    int itemPosition = parent.getChildAdapterPosition(view); 
    // 获得每个Item的位置 
    // 第1个Item不绘制分割线 
    if (itemPosition != 0) { 
    outRect.set(0, 0, 0, 10); 
    // 设置间隔区域为10px,即onDraw()可绘制的区域为10px 


    // 重写onDraw() 
    // 作用:在间隔区域里绘制一个矩形,即分割线 
    @Override 
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { 
    super.onDraw(c, parent, state); 
    // 获取RecyclerView的Child view的个数 
    int childCount = parent.getChildCount(); 
    // 遍历每个Item,分别获取它们的位置信息,然后再绘制对应的分割线 
    for ( int i = 0; i < childCount; i++ ) { 
    // 获取每个Item的位置 
    final View child = parent.getChildAt(i); 
    int index = parent.getChildAdapterPosition(child); 
    // 第1个Item不需要绘制 
    if ( index == 0 ) { 
    continue; 

    // 获取布局参数 
    final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child 
    .getLayoutParams(); 
    // 设置矩形(分割线)的宽度为10px 
    final int mDivider = 10; 
    // 根据子视图的位置 &; 间隔区域,设置矩形(分割线)的2个顶点坐标(左上 &; 右下) 
    // 矩形左上顶点 = (ItemView的左边界,ItemView的下边界) 
    // ItemView的左边界 = RecyclerView 的左边界 + paddingLeft距离 后的位置 
    final int left = parent.getPaddingLeft(); 
    // ItemView的下边界:ItemView 的 bottom坐标 + 距离RecyclerView底部距离 +translationY 
    final int top = child.getBottom() + params.bottomMargin + 
    Math.round(ViewCompat.getTranslationY(child)); 
    // 矩形右下顶点 = (ItemView的右边界,矩形的下边界) 
    // ItemView的右边界 = RecyclerView 的右边界减去 paddingRight 后的坐标位置 
    final int right = parent.getWidth() - parent.getPaddingRight(); 
    // 绘制分割线的下边界 = ItemView的下边界+分割线的高度 
    final int bottom = top + mDivider; 

    // 通过Canvas绘制矩形(分割线) 
    c.drawRect(left,top,right,bottom,mPaint); 


    步骤2:在设置RecyclerView时添加该分割线即可


    Rv = (RecyclerView) findViewById(R.id.my_recycler_view); 
    //使用线性布局 
    LinearLayoutManager layoutManager = new LinearLayoutManager(this); 
    Rv.setLayoutManager(layoutManager); 
    Rv.setHasFixedSize(true); 
    // 通过自定义分割线类 添加分割线 
    Rv.addItemDecoration(new DividerItemDecoration()); 
    //为ListView绑定适配器 
    myAdapter = new MyAdapter(this,listItem); 
    Rv.setAdapter(myAdapter); 
    myAdapter.setOnItemClickListener(this); 
    2.2.6 结果展示 
    这是我见过有关Android RecyclerView最好的一篇文章:深入解析 RecyclerView.ItemDecoration类(含实例讲解)_第9张图片 
    示意图 
    2.2.7 源码地址 

    Carson_Ho的Github地址:RecyclerView_ItemDecoration


    2.3 onDrawOver() 
    2.3.1 作用 
    与 onDraw()类似,都是绘制内容 
    但与 onDraw()的区别是: Itemdecoration的 onDrawOver()绘制 是后于 ItemView的 onDraw()绘制


    即不需要考虑绘制内容被ItemView遮挡的问题,反而 ItemView会被onDrawOver()绘制的内容遮挡
    绘制时机比较:
    Itemdecoration.onDraw()> ItemView.onDraw() > Itemdecoration.onDrawOver()



    这是我见过有关Android RecyclerView最好的一篇文章:深入解析 RecyclerView.ItemDecoration类(含实例讲解)_第10张图片
    示意图 
    2.3.2 具体使用 
    使用方法类似自定义View时的 onDraw()

     

    请看我写的自定义View文章:自定义View Draw过程- 最易懂的自定义View原理系列(4)

     



    @Override 
    public void onDrawOver(Canvas c, RecyclerView parent, 
    RecyclerView.State state) { 
    .... 
    // 使用类似自定义View时的 onDraw() 

    2.3.3 应用场景 

    在 RecyclerView / 特定的 ItemView 上绘制内容,如蒙层、重叠内容等等


    2.3.4 实例讲解 
    实例说明:在 RecyclerView 上每个 ItemView 上叠加一个角标 

    这是我见过有关Android RecyclerView最好的一篇文章:深入解析 RecyclerView.ItemDecoration类(含实例讲解)_第11张图片 
    角度示意图 

    具体代码实现 

    步骤1:自定义 ItemDecoration类


    public class DividerItemDecoration extends RecyclerView.ItemDecoration { 
    private Paint mPaint; 
    private Bitmap mIcon; 
    // 在构造函数里进行绘制的初始化,如画笔属性设置等 
    public DividerItemDecoration(Context context) { 
    mPaint = new Paint(); 
    mPaint.setColor(Color.RED); 
    // 画笔颜色设置为红色 
    // 获取图片资源 
    mIcon = BitmapFactory.decodeResource(context.getResources(), R.mipmap.logo); 

    // 重写onDrawOver() 
    // 将角度绘制到ItemView上 
    @Override 
    public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { 
    super.onDrawOver(c, parent, state); 
    // 获取Item的总数 
    int childCount = parent.getChildCount(); 
    // 遍历Item 
    for ( int i = 0; i < childCount; i++ ) { 
    // 获取每个Item的位置 
    View view = parent.getChildAt(i); 
    int index = parent.getChildAdapterPosition(view); 
    // 设置绘制内容的坐标(ItemView的左边界,ItemView的上边界) 
    // ItemView的左边界 = RecyclerView 的左边界 = paddingLeft距离 后的位置 
    final int left = parent.getWidth()/2; 
    // ItemView的上边界 
    float top = view.getTop(); 
    // 第1个ItemView不绘制 
    if ( index == 0 ) { 
    continue; 

    // 通过Canvas绘制角标 
    c.drawBitmap(mIcon,left,top,mPaint); 


    步骤2:在设置RecyclerView时添加即可


    Rv = (RecyclerView) findViewById(R.id.my_recycler_view); 
    //使用线性布局 
    LinearLayoutManager layoutManager = new LinearLayoutManager(this); 
    Rv.setLayoutManager(layoutManager); 
    Rv.setHasFixedSize(true); 
    //用自定义分割线类设置分割线 
    Rv.addItemDecoration(new DividerItemDecoration()); 
    //为ListView绑定适配器 
    myAdapter = new MyAdapter(this,listItem); 
    Rv.setAdapter(myAdapter); 
    myAdapter.setOnItemClickListener(this); 
    2.3.5 结果展示 
    这是我见过有关Android RecyclerView最好的一篇文章:深入解析 RecyclerView.ItemDecoration类(含实例讲解)_第12张图片 
    示意图 
    2.3.6 源码地址 

    Carson_Ho的Github地址:RecyclerView_ItemDecoration


    3. 使用总结 

    我用一张图总结RecyclerView ItemDecoration类的使用


    这是我见过有关Android RecyclerView最好的一篇文章:深入解析 RecyclerView.ItemDecoration类(含实例讲解)_第13张图片
    示意图 

    4. 结合自定义View的实践应用:时间轴 
     

    Android开发中,时间轴的 UI需求非常常见,如下图:


    这是我见过有关Android RecyclerView最好的一篇文章:深入解析 RecyclerView.ItemDecoration类(含实例讲解)_第14张图片
    示意图 
     

    本次实例将结合 自定义View &; RecyclerView的知识,手把手教你实现该常见 &; 实用的自定义View:时间轴

     

     

    具体请看文章:Android 自定义View实战系列 :时间轴

     




    下一篇文章我将继续结合 自定义 View &; RecyclerView.ItemDecoration类进行一些有趣的自定义 View实例讲解,有兴趣可以继续关注Carson_Ho的安卓开发笔记 

    请点赞!因为你的鼓励是我写作的最大动力! 

     

    相关文章阅读
    Android开发:最全面、最易懂的Android屏幕适配解决方案
    Android事件分发机制详解:史上最全面、最易懂
    Android开发:史上最全的Android消息推送解决方案
    Android开发:最全面、最易懂的Webview详解
    Android开发:JSON简介及最全面解析方法!
    Android四大组件:Service服务史上最全面解析
    Android四大组件:BroadcastReceiver史上最全面解析

     


    欢迎关注Carson_Ho的简书! 

    不定期分享关于安卓开发的干货,追求短、平、快,但却不缺深度。


    这是我见过有关Android RecyclerView最好的一篇文章:深入解析 RecyclerView.ItemDecoration类(含实例讲解)_第15张图片


     
  • 以上是教你玩转 Android RecyclerView:深入解析 RecyclerView.ItemDecoration类(含实例讲解)的内容,更多 RecyclerViewItemDecoration 讲解 实例 解析 深入 Android 的内容,请您使用右上方搜索功能获取相关信息

你可能感兴趣的:(android)