RecyclerView Divider完美解决方案

局限于整体的设计,对于 GridLayoutManager、StaggeredGridLayoutManager 会导致 Item 的宽度(例如:垂直布局)大小不一致,所以不建议再使用了。这里,推荐另外一个库:https://github.com/Fondesa/RecyclerViewDivider

关于RecyclerView的使用,不是本文介绍的重点,还不清楚的同学可以参考这篇文章: Android RecyclerView 使用完全解析 体验艺术般的控件

RecyclerView替代ListView势在必行,唯一比较遗憾的是官方没有内置几个好用的ItemDecoration,这使得很多人觉得使用起来比较麻烦。

有幸站在巨人的肩膀上,github上有大神实现了一个:RecyclerView-FlexibleDivider,经过一番使用,我发现对于GridLayoutManager(不支持SpanSizeLookup)和StaggeredGridLayoutManager的支持并不好。

RecyclerView-FlexibleDivider的实现思路和网上多数方案是一致的:

  • 从左到右绘制横向divider
  • 从上至下绘制垂直divider

这种实现的方式弊端很明显,就是遇到GridLayoutManager(使用了SpanSizeLookup)和StaggeredGridLayoutManager的时候,就没法正常工作了,因为它们不是“规则”的占据一个“单元”。所以,本文的实现思路是这样的

  • 横向divider绘制在每个item的下方
  • 垂直divider绘制在每个item的右侧

在开始之前,我们有必要了解下ItemDecoration的几个方法

  • onDraw(Canvas c, ...):在绘制item views之前绘制decorations,一般我们都用这个方法
  • onDrawOver(Canvas c, ...):与上面的方法相反,在绘制item views之后绘制decorations
  • getItemOffsets(Rect outRect, ...):为outRect设置left,top和right,bottom,简单的理解就是设置item view的margin(marginLeft、marginTop、marginRight、marginBottom)

绘制decorations的方法就是使用onDraw方法中的参数Canvas,比如

  • 绘制一条线:canvas.drawLine
  • 绘制图片:drawable.draw(canvas)
  • 等等...

不管哪种绘制都需要的一个参数,那就是绘制的范围:Rect;另外针对横向和垂直divider,getItemOffsets方法中的outRect的设置,应该也是不一样的。所以抽象出一个父类FlexibleDividerDecoration,继承自RecyclerView.ItemDecoration。

FlexibleDividerDecoration

public abstract class FlexibleDividerDecoration extends RecyclerView.ItemDecoration {
    
    protected abstract Rect getDividerBound(int position, RecyclerView parent, View child);

    protected abstract void setItemOffsets(Rect outRect, int position, RecyclerView parent);

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        int validChildCount = parent.getChildCount();

        for (int i = 0; i < validChildCount; i++) {
            View child = parent.getChildAt(i);
            int childPosition = parent.getChildAdapterPosition(child);

            // 当前item是否需要divider(通常最后一个不需要)
            if (!hasDivider(parent, childPosition)) {
                continue;
            }

            // 绘制divider的区域,由子类具体实现
            Rect bounds = getDividerBound(childPosition, parent, child);

            switch (mDividerType) {
                case DRAWABLE: // 图片类型的divider
                    Drawable drawable = mDrawableProvider.drawableProvider(childPosition, parent);
                    drawable.setBounds(bounds);
                    drawable.draw(c);
                    break;
            }
        }
    }

    @Override
    public void getItemOffsets(Rect rect, View v, RecyclerView parent, RecyclerView.State state) {
        int position = parent.getChildAdapterPosition(v);
        if (!hasDivider(parent, position)) {
            return;
        }
        setItemOffsets(rect, position, parent);
    }

    /**
     * Whether child has divider
     *
     * @param parent
     * @param childPosition
     * @return true if child has divider
     */
    public boolean hasDivider(RecyclerView parent, int childPosition) {
        if (this instanceof VerticalDividerItemDecoration) { // 垂直方向是否有divider
            return hasVerticalDivider(parent, childPosition);
        } else if (this instanceof HorizontalDividerItemDecoration) { // 水平方向是否有divider
            return hasHorizontalDivider(parent, childPosition);
        }
        return false;
    }
}

上面的代码看注释应该比较好理解(注意:实际代码会比文章中的复杂些许)。具体实现的子类有两个,分别是HorizontalDividerItemDecorationVerticalDividerItemDecoration
我们看下HorizontalDividerItemDecoration

public class HorizontalDividerItemDecoration extends FlexibleDividerDecoration {
  protected HorizontalDividerItemDecoration(Builder builder) {
      super(builder);
  }

  @Override
  protected Rect getDividerBound(int position, RecyclerView parent, View child) {
      Rect bounds = new Rect(0, 0, 0, 0);
      RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
      bounds.left = child.getLeft();
      bounds.right = child.getRight();

      // divider大小
      int dividerSize = getDividerSize(position, parent);

      bounds.top = child.getBottom() + params.bottomMargin;
      bounds.bottom = bounds.top + dividerSize;

      return bounds;
  }

  @Override
  protected void setItemOffsets(Rect outRect, int position, RecyclerView parent) {
      outRect.set(0, 0, 0, getDividerSize(position, parent));
  }
}

这里只贴出了关键代码,因为实际情况,我们可能还需要考虑更多的问题,比如:

  1. 水平divider的marginLeft和marginRight,还需要判断item是否是最边上的,因为中间的divider是不需要margin的
  2. 垂直divider的marginTop和marginBottom,还需要判断item是否是最边上的,因为中间的divider是不需要margin的
  3. 水平和垂直交叉位置的空白问题
  4. getReverseLayout为true和false时,不同的处理

为了支持不同的LayoutManager,以上的判断策略还不尽相同,因为篇幅限制,具体的代码请查看文章最后的源码,最后我们来看下最终实现的效果吧~

divider.gif

divider2.gif

本文源码地址:https://github.com/hiphonezhu/RecyclerView-FlexibleDivider(由于改动较大,fork过来的代码没有pull给作者,如需使用请下载本文的源码,而不是通过Gradle引入)

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

推荐阅读更多精彩内容

  • 特别声明: 本文转发自:【江清清的博客】http://blog.csdn.net/developer_jiangq...
    _猜火车_阅读 37,418评论 11 70
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,858评论 25 707
  • 这篇文章分三个部分,简单跟大家讲一下 RecyclerView 的常用方法与奇葩用法;工作原理与ListView比...
    LucasAdam阅读 4,379评论 0 27
  • 每年的生日在圣诞节前后,对于一个不爱过生日的人来说,其实做什么都没差。不过,今年我想记录一下。 说到30,想到的一...
    筱勤阅读 170评论 0 0
  • “暴发户”心态,我也不认为这是一个贬义词。 成了暴发户,必然有“暴发户”心态,不然怎么叫“暴发户”心态呢?这是暴发...
    梦想农场主阅读 286评论 0 0