RecyclerView Divider完美解決方案

局限于整體的設(shè)計(jì),對(duì)于 GridLayoutManager、StaggeredGridLayoutManager 會(huì)導(dǎo)致 Item 的寬度(例如:垂直布局)大小不一致,所以不建議再使用了。這里,推薦另外一個(gè)庫(kù):https://github.com/Fondesa/RecyclerViewDivider

關(guān)于RecyclerView的使用,不是本文介紹的重點(diǎn),還不清楚的同學(xué)可以參考這篇文章: Android RecyclerView 使用完全解析 體驗(yàn)藝術(shù)般的控件。

RecyclerView替代ListView勢(shì)在必行,唯一比較遺憾的是官方?jīng)]有內(nèi)置幾個(gè)好用的ItemDecoration,這使得很多人覺得使用起來(lái)比較麻煩。

有幸站在巨人的肩膀上,github上有大神實(shí)現(xiàn)了一個(gè):RecyclerView-FlexibleDivider,經(jīng)過一番使用,我發(fā)現(xiàn)對(duì)于GridLayoutManager(不支持SpanSizeLookup)和StaggeredGridLayoutManager的支持并不好。

RecyclerView-FlexibleDivider的實(shí)現(xiàn)思路和網(wǎng)上多數(shù)方案是一致的:

  • 從左到右繪制橫向divider
  • 從上至下繪制垂直divider

這種實(shí)現(xiàn)的方式弊端很明顯,就是遇到GridLayoutManager(使用了SpanSizeLookup)和StaggeredGridLayoutManager的時(shí)候,就沒法正常工作了,因?yàn)樗鼈儾皇恰耙?guī)則”的占據(jù)一個(gè)“單元”。所以,本文的實(shí)現(xiàn)思路是這樣的

  • 橫向divider繪制在每個(gè)item的下方
  • 垂直divider繪制在每個(gè)item的右側(cè)

在開始之前,我們有必要了解下ItemDecoration的幾個(gè)方法

  • onDraw(Canvas c, ...):在繪制item views之前繪制decorations,一般我們都用這個(gè)方法
  • onDrawOver(Canvas c, ...):與上面的方法相反,在繪制item views之后繪制decorations
  • getItemOffsets(Rect outRect, ...):為outRect設(shè)置left,top和right,bottom,簡(jiǎn)單的理解就是設(shè)置item view的margin(marginLeft、marginTop、marginRight、marginBottom)

繪制decorations的方法就是使用onDraw方法中的參數(shù)Canvas,比如

  • 繪制一條線:canvas.drawLine
  • 繪制圖片:drawable.draw(canvas)
  • 等等...

不管哪種繪制都需要的一個(gè)參數(shù),那就是繪制的范圍:Rect;另外針對(duì)橫向和垂直divider,getItemOffsets方法中的outRect的設(shè)置,應(yīng)該也是不一樣的。所以抽象出一個(gè)父類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);

            // 當(dāng)前item是否需要divider(通常最后一個(gè)不需要)
            if (!hasDivider(parent, childPosition)) {
                continue;
            }

            // 繪制divider的區(qū)域,由子類具體實(shí)現(xiàn)
            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;
    }
}

上面的代碼看注釋應(yīng)該比較好理解(注意:實(shí)際代碼會(huì)比文章中的復(fù)雜些許)。具體實(shí)現(xiàn)的子類有兩個(gè),分別是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));
  }
}

這里只貼出了關(guān)鍵代碼,因?yàn)閷?shí)際情況,我們可能還需要考慮更多的問題,比如:

  1. 水平divider的marginLeft和marginRight,還需要判斷item是否是最邊上的,因?yàn)橹虚g的divider是不需要margin的
  2. 垂直divider的marginTop和marginBottom,還需要判斷item是否是最邊上的,因?yàn)橹虚g的divider是不需要margin的
  3. 水平和垂直交叉位置的空白問題
  4. getReverseLayout為true和false時(shí),不同的處理

為了支持不同的LayoutManager,以上的判斷策略還不盡相同,因?yàn)槠拗疲唧w的代碼請(qǐng)查看文章最后的源碼,最后我們來(lái)看下最終實(shí)現(xiàn)的效果吧~

divider.gif

divider2.gif

本文源碼地址:https://github.com/hiphonezhu/RecyclerView-FlexibleDivider(由于改動(dòng)較大,fork過來(lái)的代碼沒有pull給作者,如需使用請(qǐng)下載本文的源碼,而不是通過Gradle引入)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 特別聲明: 本文轉(zhuǎn)發(fā)自:【江清清的博客】http://blog.csdn.net/developer_jiangq...
    _猜火車_閱讀 37,851評(píng)論 11 70
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,351評(píng)論 25 708
  • 這篇文章分三個(gè)部分,簡(jiǎn)單跟大家講一下 RecyclerView 的常用方法與奇葩用法;工作原理與ListView比...
    LucasAdam閱讀 4,720評(píng)論 0 27
  • 每年的生日在圣誕節(jié)前后,對(duì)于一個(gè)不愛過生日的人來(lái)說,其實(shí)做什么都沒差。不過,今年我想記錄一下。 說到30,想到的一...
    筱勤閱讀 193評(píng)論 0 0
  • “暴發(fā)戶”心態(tài),我也不認(rèn)為這是一個(gè)貶義詞。 成了暴發(fā)戶,必然有“暴發(fā)戶”心態(tài),不然怎么叫“暴發(fā)戶”心態(tài)呢?這是暴發(fā)...
    夢(mèng)想農(nóng)場(chǎng)主閱讀 352評(píng)論 0 0

友情鏈接更多精彩內(nèi)容