热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Android自定义RecyclerView分割线,打造无边缘分割线

前言: 现在的RecyclerView几乎已经完全取代ListView和GridView了,已经几年没使用ListView和GridView了,想当年还需要自己在getView方法中复用c

前言:

 现在的RecyclerView几乎已经完全取代ListView和GridView了,已经几年没使用ListView和GridView了,想当年还需要自己在getView方法中复用convertView。而现在的RecyclerView一出生就被设计成convertView复用的,尽管你不想复用(才怪)。RecyclerView功能如此强大的同时就会面临许多需求,如给RecyclerView的item添加分割线,给RecyclerView添加下拉刷新,上拉加载更多的功能等等。这篇文章就将为RecyclerView添加中间分割线做个简单的介绍,其实给RecyclerView添加分割线并没有你想象的那么难,只要你肯动手,你就会拥有属于你自己的分割线,想怎么画就怎么画。

实现效果图:

这里写图片描述这里写图片描述

为RecyclerView添加分割线需通过RecyclerView的addItemDecoration方法,addItemDecoration方法需接受一个ItemDecoration类型的对象。为此,我们首先需要自定义一个类继承于ItemDecoration并实现getItemOffsets和SpaceItemDecoration两个方法,步骤如下。

  1. 重写getItemOffsets方法,给每个Item设置合适的偏移量(left,top,right,bottom);
  2. 重写onDraw方法,给每个item绘制分隔线(矩形)。

1. 重写getItemOffsets

 这个方法在RecyclerView渲染item的时候通过getItemOffsets方法的Rect类型的参数outRect的left,top,right,bottom四个属性为item设置相应的偏移量,如:

public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
    super.getItemOffsets(outRect, view, parent, state);
    // 获取位置
    int position = parent.getChildAdapterPosition(view);
    view.setTag(position);

    RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
    if(layoutManager instanceof GridLayoutManager) {
        GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
        GridLayoutManager.SpanSizeLookup spanSizeLookup = gridLayoutManager.getSpanSizeLookup();
        mSpanCount =  gridLayoutManager.getSpanCount();
        mMaxSpanGroupIndex = spanSizeLookup.getSpanGroupIndex(parent.getAdapter().getItemCount() - 1, mSpanCount);
        int spanSize = spanSizeLookup.getSpanSize(position);
        int spanIndex = spanSizeLookup.getSpanIndex(position, mSpanCount);
        int spanGroupIndex = spanSizeLookup.getSpanGroupIndex(position, mSpanCount);
        Log.d(TAG, "getItemOffsets: spanIndex:" + spanIndex);
        if (spanSize 0) {
            // 左边需要偏移
            outRect.left = mSpace;
        }
        if(spanGroupIndex != 0) {
            outRect.top = mSpace;
        }
    }else if(layoutManager instanceof LinearLayoutManager){
        LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
        if(position != 0) {
            if (linearLayoutManager.getOrientation() == LinearLayoutManager.HORIZONTAL) {
                outRect.left = mSpace;
            } else {
                outRect.top = mSpace;
            }
        }
    }
}

 在上面getItemOffsets方法中首先通过RecyclerView的getChildAdapterPosition方法获得item在Adapter中的位置并设置给它的tag属性保存下来,然后判断布局管理器的类型。如果布局管理器是GridLayoutManager类型,那么就需要判断当前Item不是位于该行的第一个(spanIndex != 0)并且没有占据一行(spanSize

2. 重写onDraw,绘制分割线

 由于布局管理器的不同,绘制分割线的方法也不同,所以首先需要先判断一下布局管理器的类型

**
* 绘制分割线
 * @param c
 * @param parent
 * @param state
 */
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
    super.onDraw(c, parent, state);
    RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
    if(layoutManager instanceof GridLayoutManager) {
        drawSpace(c, parent);
    }else if(layoutManager instanceof LinearLayoutManager){
        LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
        if(linearLayoutManager.getOrientation() == LinearLayoutManager.HORIZONTAL){
            // 画竖直分割线
            drawVertical(c,parent);
        }else{
            // 画横向分割线
            drawHorizontal(c,parent);
        }
    }
}
  • 如果布局管理器为GridLayoutManager方法,那么我们就通过drawSpace方法完成分割线的绘制,如下:
/** * 绘制分割线 * @param canvas * @param parent */
private void drawSpace(Canvas canvas, RecyclerView parent) {
    GridLayoutManager gridLayoutManager = (GridLayoutManager) parent.getLayoutManager();
    int spanCount = gridLayoutManager.getSpanCount();
    GridLayoutManager.SpanSizeLookup spanSizeLookup = gridLayoutManager.getSpanSizeLookup();
    int childCount = parent.getChildCount();
    int top,bottom,left,right;
    for(int i=0;i// 绘制思路,以绘制bottom和left为主,top和right不绘制,需要判断出当前的item是否位于边缘,位于边缘的item不绘制bottom和left,你懂得
        View child = parent.getChildAt(i);
        int position = (int) child.getTag();
        RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();

        int spanGroupIndex = spanSizeLookup.getSpanGroupIndex(position, spanCount);
        int spanSize = spanSizeLookup.getSpanSize(position);
        int spanIndex = spanSizeLookup.getSpanIndex(position, spanCount);
        // 画bottom分割线,如果没到达底部,绘制bottom
        if(spanGroupIndex // 不需要外边缘
            right = child.getRight() + layoutParams.rightMargin + mSpace;
            canvas.drawRect(left, top, right, bottom, mPaint);
        }
        // 画left分割线
       if (spanSize != mSpanCount && spanIndex!=0) {
            // 左边需要分割线,开始绘制
           top = child.getTop() - layoutParams.topMargin;
           bottom = child.getBottom() + layoutParams.bottomMargin;
           left = child.getLeft() - layoutParams.leftMargin - mSpace;
           right = left + mSpace;
           canvas.drawRect(left, top, right, bottom, mPaint);
        }
    }
}

 这里你需要理解的是绘制思路,我们先判断当前Item所在的行是否位于最后一行,如果不是最后一行,那么需要绘制底部分割线(横向)。需要注意的是计算分割线的right需加上Space,不然竖直分割线和水平分割线交界处就会出现空白。当当前Item所占的SpanSize不是整行,并且当前Item的所在的行标不为0的时候(spanSize != mSpanCount && spanIndex!=0)需要绘制竖直分割线。

  • 如果布局管理器是LinearLayoutManager,那么我们需要判断布局方法是纵向还是横向,如果是纵向,那么我们需要绘制横线(底部),如果是横向,那么我们需要绘制竖线(右侧)。
private void drawHorizontal(Canvas c, RecyclerView parent) {
    // 画竖直分割线
    LinearLayoutManager linearLayoutManager = (LinearLayoutManager) parent.getLayoutManager();
    int top,bottom,left,right;
    for(int i=0;iint position = (int) child.getTag();
        // 判断是否位于边缘
        if(position == linearLayoutManager.getItemCount()-1) continue;
        RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();
        top = child.getBottom()+layoutParams.bottomMargin;
        bottom = top + mSpace;
        left = child.getLeft() - layoutParams.leftMargin;
        right = child.getRight() + layoutParams.rightMargin;
        c.drawRect(left,top,right,bottom,mPaint);
    }
}

private void drawVertical(Canvas c, RecyclerView parent) {
    // 画竖直分割线
    LinearLayoutManager linearLayoutManager = (LinearLayoutManager) parent.getLayoutManager();
    int top,bottom,left,right;
    for(int i=0;iint position = (int) child.getTag();
        // 判断是否位于边缘
        if(position == 0) continue;
        RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();
        top = child.getTop()-layoutParams.topMargin;
        bottom = child.getBottom()+layoutParams.bottomMargin;
        left = child.getLeft() - layoutParams.leftMargin - mSpace;
        right = left + mSpace;
        c.drawRect(left,top,right,bottom,mPaint);
    }
}

到这里为止,一个item内部无边缘的分割线就绘制完了,整个绘制过程中需要理解的是我们绘制的思路以Item left偏移和top偏移以画底部分割线和画右侧分割线并判断Item是否位于边缘而决定要不要绘制分割线达到无边缘效果。如果你对于上面的偏移量的计算不是很清楚的话你不妨动手将上面的代码敲一遍并改变left,top,right,bottom看看效果,只有这样你才能理解这几个偏移量的含义。点此,github直通车,也查看如下完整代码:

package com.lt.demo;

import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;


/** * Created by lt on 2018/4/9. */
public class SpaceItemDecoration extends RecyclerView.ItemDecoration{

    private static final java.lang.String TAG = "SpaceItemDecoration";
    private int mSpanCount;
    private int mSpace;
    private Paint mPaint;
    private int mMaxSpanGroupIndex;

    /** * 获取Item的偏移量 * @param outRect * @param view * @param parent * @param state */
    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        // 获取位置
        int position = parent.getChildAdapterPosition(view);
        view.setTag(position);

        RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
        if(layoutManager instanceof GridLayoutManager) {
            GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
            GridLayoutManager.SpanSizeLookup spanSizeLookup = gridLayoutManager.getSpanSizeLookup();
            mSpanCount =  gridLayoutManager.getSpanCount();
            mMaxSpanGroupIndex = spanSizeLookup.getSpanGroupIndex(parent.getAdapter().getItemCount() - 1, mSpanCount);
            int spanSize = spanSizeLookup.getSpanSize(position);
            int spanIndex = spanSizeLookup.getSpanIndex(position, mSpanCount);
            int spanGroupIndex = spanSizeLookup.getSpanGroupIndex(position, mSpanCount);
            Log.d(TAG, "getItemOffsets: spanIndex:" + spanIndex);
            if (spanSize 0) {
                // 左边需要偏移
                outRect.left = mSpace;
            }
            if(spanGroupIndex != 0) {
                outRect.top = mSpace;
            }
        }else if(layoutManager instanceof LinearLayoutManager){
            LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
            if(position != 0) {
                if (linearLayoutManager.getOrientation() == LinearLayoutManager.HORIZONTAL) {
                    outRect.left = mSpace;
                } else {
                    outRect.top = mSpace;
                }
            }
        }
    }

    public SpaceItemDecoration(int space, int spaceColor) {
        this.mSpace = space;
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(spaceColor);
        mPaint.setStyle(Paint.Style.FILL);
    }

    /** * 绘制分割线 * @param c * @param parent * @param state */
    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDraw(c, parent, state);
        RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
        if(layoutManager instanceof GridLayoutManager) {
            drawSpace(c, parent);
        }else if(layoutManager instanceof LinearLayoutManager){
            LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
            if(linearLayoutManager.getOrientation() == LinearLayoutManager.HORIZONTAL){
                // 画竖直分割线
                drawVertical(c,parent);
            }else{
                // 画横向分割线
                drawHorizontal(c,parent);
            }
        }
    }

    private void drawHorizontal(Canvas c, RecyclerView parent) {
        // 画竖直分割线
        LinearLayoutManager linearLayoutManager = (LinearLayoutManager) parent.getLayoutManager();
        int top,bottom,left,right;
        for(int i=0;iint position = (int) child.getTag();
            // 判断是否位于边缘
            if(position == linearLayoutManager.getItemCount()-1) continue;
            RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();
            top = child.getBottom()+layoutParams.bottomMargin;
            bottom = top + mSpace;
            left = child.getLeft() - layoutParams.leftMargin;
            right = child.getRight() + layoutParams.rightMargin;
            c.drawRect(left,top,right,bottom,mPaint);
        }
    }

    private void drawVertical(Canvas c, RecyclerView parent) {
        // 画竖直分割线
        LinearLayoutManager linearLayoutManager = (LinearLayoutManager) parent.getLayoutManager();
        int top,bottom,left,right;
        for(int i=0;iint position = (int) child.getTag();
            // 判断是否位于边缘
            if(position == 0) continue;
            RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();
            top = child.getTop()-layoutParams.topMargin;
            bottom = child.getBottom()+layoutParams.bottomMargin;
            left = child.getLeft() - layoutParams.leftMargin - mSpace;
            right = left + mSpace;
            c.drawRect(left,top,right,bottom,mPaint);
        }
    }

    /** * 绘制分割线 * @param canvas * @param parent */
    private void drawSpace(Canvas canvas, RecyclerView parent) {
        GridLayoutManager gridLayoutManager = (GridLayoutManager) parent.getLayoutManager();
        int spanCount = gridLayoutManager.getSpanCount();
        GridLayoutManager.SpanSizeLookup spanSizeLookup = gridLayoutManager.getSpanSizeLookup();
        int childCount = parent.getChildCount();
        int top,bottom,left,right;
        for(int i=0;i// 绘制思路,以绘制bottom和left为主,top和right不绘制,需要判断出当前的item是否位于边缘,位于边缘的item不绘制bottom和left,你懂得
            View child = parent.getChildAt(i);
            int position = (int) child.getTag();
            RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();

            int spanGroupIndex = spanSizeLookup.getSpanGroupIndex(position, spanCount);
            int spanSize = spanSizeLookup.getSpanSize(position);
            int spanIndex = spanSizeLookup.getSpanIndex(position, spanCount);
            // 画bottom分割线,如果没到达底部,绘制bottom
            if(spanGroupIndex // 不需要外边缘
                right = child.getRight() + layoutParams.rightMargin + mSpace;
                canvas.drawRect(left, top, right, bottom, mPaint);
            }
            // 画left分割线
           if (spanSize != mSpanCount && spanIndex!=0) {
                // 左边需要分割线,开始绘制
               top = child.getTop() - layoutParams.topMargin;
               bottom = child.getBottom() + layoutParams.bottomMargin;
               left = child.getLeft() - layoutParams.leftMargin - mSpace;
               right = left + mSpace;
               canvas.drawRect(left, top, right, bottom, mPaint);
            }
        }
    }
}

推荐阅读
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
  • 本文介绍了Android 7的学习笔记总结,包括最新的移动架构视频、大厂安卓面试真题和项目实战源码讲义。同时还分享了开源的完整内容,并提醒读者在使用FileProvider适配时要注意不同模块的AndroidManfiest.xml中配置的xml文件名必须不同,否则会出现问题。 ... [详细]
  • Android开发实现的计时器功能示例
    本文分享了Android开发实现的计时器功能示例,包括效果图、布局和按钮的使用。通过使用Chronometer控件,可以实现计时器功能。该示例适用于Android平台,供开发者参考。 ... [详细]
  • 本文介绍了pack布局管理器在Perl/Tk中的使用方法及注意事项。通过调用pack()方法,可以控制部件在显示窗口中的位置和大小。同时,本文还提到了在使用pack布局管理器时,应注意将部件分组以便在水平和垂直方向上进行堆放。此外,还介绍了使用Frame部件或Toplevel部件来组织部件在窗口内的方法。最后,本文强调了在使用pack布局管理器时,应避免在中间切换到grid布局管理器,以免造成混乱。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • ZSI.generate.Wsdl2PythonError: unsupported local simpleType restriction ... [详细]
  • http:my.oschina.netleejun2005blog136820刚看到群里又有同学在说HTTP协议下的Get请求参数长度是有大小限制的,最大不能超过XX ... [详细]
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • FeatureRequestIsyourfeaturerequestrelatedtoaproblem?Please ... [详细]
  • 标题: ... [详细]
  • 本文介绍了如何使用Express App提供静态文件,同时提到了一些不需要使用的文件,如package.json和/.ssh/known_hosts,并解释了为什么app.get('*')无法捕获所有请求以及为什么app.use(express.static(__dirname))可能会提供不需要的文件。 ... [详细]
  • CEPH LIO iSCSI Gateway及其使用参考文档
    本文介绍了CEPH LIO iSCSI Gateway以及使用该网关的参考文档,包括Ceph Block Device、CEPH ISCSI GATEWAY、USING AN ISCSI GATEWAY等。同时提供了多个参考链接,详细介绍了CEPH LIO iSCSI Gateway的配置和使用方法。 ... [详细]
  • 本文介绍了RxJava在Android开发中的广泛应用以及其在事件总线(Event Bus)实现中的使用方法。RxJava是一种基于观察者模式的异步java库,可以提高开发效率、降低维护成本。通过RxJava,开发者可以实现事件的异步处理和链式操作。对于已经具备RxJava基础的开发者来说,本文将详细介绍如何利用RxJava实现事件总线,并提供了使用建议。 ... [详细]
author-avatar
瑞正可樺7991
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有