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引入)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

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