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,566評論 11 70
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,556評論 25 708
  • 這篇文章分三個部分,簡單跟大家講一下 RecyclerView 的常用方法與奇葩用法;工作原理與ListView比...
    LucasAdam閱讀 4,426評論 0 27
  • 每年的生日在圣誕節前后,對于一個不愛過生日的人來說,其實做什么都沒差。不過,今年我想記錄一下。 說到30,想到的一...
    筱勤閱讀 178評論 0 0
  • “暴發戶”心態,我也不認為這是一個貶義詞。 成了暴發戶,必然有“暴發戶”心態,不然怎么叫“暴發戶”心態呢?這是暴發...
    夢想農場主閱讀 308評論 0 0