RecyclerView添加自定義ItemDecoration實現(xiàn)(2)

1.看過第一篇文章的都會注意到在設(shè)置RecyclerView的GridLayoutManager的Item時設(shè)置的是正方形的布局×墩龋可是當(dāng)設(shè)置自定義分割線的時候出現(xiàn)了一些問題。如果對GridLayoutManager添加分割線有疑惑的可以查看上一篇文章盗迟。

RecyclerView添加自定義ItemDecoration實現(xiàn)(1)

具體情況如下圖所示:

GridLayotManager_bug_5dp.png

問題:從圖中可能不能很好的看出存在的問題坤邪。我們不妨加大分割線的寬度之后再觀察。所以我們將之前分割線的寬度改為20dp罚缕。效果如下:

drawable_item_decoration_20dp.png
GridLayoutManager_bug_20dp.png

現(xiàn)在可以很明顯的看出之前遺留下來的bug:第一列和第二列的形狀和第三列的不同艇纺。那么為什么會出現(xiàn)這種情況呢?我們下面從源碼分析一下添加ItemDecoration的原理邮弹。

2.在RecyclerView中有如下一部分代碼

/**
 * Measure a child view using standard measurement policy, taking the padding
 * of the parent RecyclerView and any added item decorations into account.
 *
 * <p>If the RecyclerView can be scrolled in either dimension the caller may
 * pass 0 as the widthUsed or heightUsed parameters as they will be irrelevant.</p>
 *
 * @param child Child view to measure
 * @param widthUsed Width in pixels currently consumed by other views, if relevant
 * @param heightUsed Height in pixels currently consumed by other views, if relevant
 */
public void measureChild(View child, int widthUsed, int heightUsed) {
    final LayoutParams lp = (LayoutParams) child.getLayoutParams();

    final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
    widthUsed += insets.left + insets.right;
    heightUsed += insets.top + insets.bottom;
    final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(),
            getPaddingLeft() + getPaddingRight() + widthUsed, lp.width,
            canScrollHorizontally());
    final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(),
            getPaddingTop() + getPaddingBottom() + heightUsed, lp.height,
            canScrollVertically());
    if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) {
        child.measure(widthSpec, heightSpec);
    }
}



/**
 * Calculate a MeasureSpec value for measuring a child view in one dimension.
 *
 * @param parentSize Size of the parent view where the child will be placed
 * @param parentMode The measurement spec mode of the parent
 * @param padding Total space currently consumed by other elements of parent
 * @param childDimension Desired size of the child view, or MATCH_PARENT/WRAP_CONTENT.
 *                       Generally obtained from the child view's LayoutParams
 * @param canScroll true if the parent RecyclerView can scroll in this dimension
 *
 * @return a MeasureSpec value for the child view
 */
public static int getChildMeasureSpec(int parentSize, int parentMode, int padding,
        int childDimension, boolean canScroll) {
    int size = Math.max(0, parentSize - padding);
    int resultSize = 0;
    int resultMode = 0;
    if (childDimension >= 0) {
        resultSize = childDimension;
        resultMode = MeasureSpec.EXACTLY;
    } else if (canScroll) {
         if (childDimension == LayoutParams.MATCH_PARENT){
            switch (parentMode) {
                case MeasureSpec.AT_MOST:
                case MeasureSpec.EXACTLY:
                    resultSize = size;
                    resultMode = parentMode;
                    break;
                case MeasureSpec.UNSPECIFIED:
                    resultSize = 0;
                    resultMode = MeasureSpec.UNSPECIFIED;
                    break;
            }
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            resultSize = 0;
            resultMode = MeasureSpec.UNSPECIFIED;
        }
    } else {
        if (childDimension == LayoutParams.MATCH_PARENT) {
            resultSize = size;
            resultMode = parentMode;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            resultSize = size;
            if (parentMode == MeasureSpec.AT_MOST || parentMode == MeasureSpec.EXACTLY) {
                resultMode = MeasureSpec.AT_MOST;
            } else {
                resultMode = MeasureSpec.UNSPECIFIED;
            }

        }
    }
    //noinspection WrongConstant
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

Rect getItemDecorInsetsForChild(View child) {
    final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    if (!lp.mInsetsDirty) {
        return lp.mDecorInsets;
    }

    if (mState.isPreLayout() && (lp.isItemChanged() || lp.isViewInvalid())) {
        // changed/invalid items should not be updated until they are rebound.
        return lp.mDecorInsets;
    }
    final Rect insets = lp.mDecorInsets;
    insets.set(0, 0, 0, 0);
    final int decorCount = mItemDecorations.size();
    for (int i = 0; i < decorCount; i++) {
        mTempRect.set(0, 0, 0, 0);
        mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState);
        insets.left += mTempRect.left;
        insets.top += mTempRect.top;
        insets.right += mTempRect.right;
        insets.bottom += mTempRect.bottom;
    }
    lp.mInsetsDirty = false;
    return insets;
}
  • 首先我們先看 getChildMeasureSpec 方法黔衡。 當(dāng) childDimension >= 0 時讓結(jié)果為 childDimension,否則為 Math.max(0, parentSize - padding)腌乡。這個的意思就是如果子view的尺寸如果是精確值的話盟劫,那么就是精確值的結(jié)果,如果是 MATCH_PARENT 或者 WRAP_CONTENT 的話就開始計算結(jié)果与纽。
  • 其次我們從 measureChild 方法中可以看出侣签,widthSpec 的 getChildMeasureSpec 中可以得到

     paddingg =  getPaddingLeft() + getPaddingRight() + widthUsed;
    

     final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
     widthUsed += insets.left + insets.right;
    
  • 最后我們可以從 getItemDecorInsetsForChild 方法中可以看出 Rect insets就是在自定義 ItemDecoration 時
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {} 中的outRect。所以計算 widthUsed 時候渣锦,與 outRect 有關(guān)硝岗,而我們在 getItemOffsets 方法中有這么一段代碼

     if (!isLastColumn(currentItemPosition, mSpanCount)) {
          outRect.right = mDrawable.getIntrinsicWidth();
     } else {
         outRect.right = 0;
     }
    

    如果是最后一列的話,outRect.right = 0袋毙;這使得第三列子view的 widthUsed 與前兩列的不同型檀,所以第三列子view的 padding 就會比前兩列的少一個分割線的寬度。

3.解決

那我們應(yīng)該怎么解決這個問題呢听盖?問題的關(guān)鍵就是分割線的寬度分配不均導(dǎo)致的胀溺,我們只要均勻分配寬度即可。

statement.png

如圖所示皆看,我們對于有三列的情況來說仓坞,averWidth = (2 * dividerWidth)/3, 將兩個分割線的寬度,均分給3個子 view 的 outRect腰吟。所以為了適配其他情況得到如下式子 :

 averWidth = [(spanCount - 1 ) * dividerWidth ]/spanCount ;

現(xiàn)在是最重要的步驟无埃,如果將averWidth分給每個子 view徙瓶,就會存在如下情況:

leftN + rightN = averWidth;
left(N+1) + rigthN = dividerWidth;
(注:leftN為第N個view的左偏移值,rightN為第N個View的右偏移值,計數(shù)從0開始)

對于 rightN 存在如下的情況:

 right0 = averWidth - left0;           
 right1 = averWidth - left1;           
 right2 = averWidth - left2;           
 .
 .
 .
 rightN = averWidth - leftN;

對于 leftN 存在如下的情況:

left0 = 0;                
left1 =  dividerWidth - right0;
left2 =  dividerWidth - right1;
.
.  
.
leftN = dividerWidth - right(N-1);

從上面的式子可以看出嫉称,我們只要得到 leftN 的就能很快的得到 rightN侦镇,所以將 rightN 式子合并到 leftN 中得到如下的結(jié)果:

  left0 = 0;

  left1 = dividerWidth - (averWidth - left0) 
        = dividerWidth - averWidth + left0
        = dividerWidth - averWidth 
  
  left2 = dividerWidth - (averWidth  - left1) 
        = dividerWidth -[averWidth  - (dividerWidth - averWidth)] 
        = 2 * (dividerWidth - averWidth)    
  .
  .
  .
  leftN = N * (dividerWidth - averWidth)

所以,根據(jù)化簡后的結(jié)果更改代碼织阅,修改完后的 getItemOffsets 方法具體如下:

/**
 * @param outRect 用于規(guī)定分割線的范圍
 * @param view    進(jìn)行分割線操作的子view
 * @param parent  父view
 * @param state   (這里暫時不使用)
 */
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
    //notification的時候要獲取
    mTotalItems = parent.getAdapter().getItemCount();
    if (0 == mSpanCount) {
        RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
        //判斷是否為GridLayoutManager
        if (layoutManager instanceof GridLayoutManager) {
            GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
            mSpanCount = gridLayoutManager.getSpanCount();
        } else {
            mSpanCount = 1;
        }
    }
    //在源碼中有一個過時的方法壳繁,里面有獲取當(dāng)前ItemPosition
    int currentItemPosition = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition();
    //
    if (!isLastRow(currentItemPosition, mTotalItems, mSpanCount))
        outRect.bottom = mDrawable.getIntrinsicWidth();
    else
        outRect.bottom = 0;
    //將isLastColumn注釋,添加下面代碼
    //        if (!isLastColumn(currentItemPosition, mSpanCount)) {
    //            outRect.right = mDrawable.getIntrinsicWidth();
    //        } else {
    //            outRect.right = 0;
    //        }
    int averWidth = (mSpanCount - 1) * mDrawable.getIntrinsicWidth() / mSpanCount;
    int dX = mDrawable.getIntrinsicWidth() - averWidth;
    outRect.left = (currentItemPosition % mSpanCount) * dX;
    outRect.right = averWidth - outRect.left;
}

結(jié)果截圖:

final.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末荔棉,一起剝皮案震驚了整個濱河市闹炉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌润樱,老刑警劉巖渣触,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異祥国,居然都是意外死亡昵观,警方通過查閱死者的電腦和手機晾腔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進(jìn)店門舌稀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人灼擂,你說我怎么就攤上這事壁查。” “怎么了剔应?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵睡腿,是天一觀的道長。 經(jīng)常有香客問我峻贮,道長席怪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任纤控,我火速辦了婚禮挂捻,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘船万。我一直安慰自己刻撒,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布耿导。 她就那樣靜靜地躺著声怔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪舱呻。 梳的紋絲不亂的頭發(fā)上醋火,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天,我揣著相機與錄音,去河邊找鬼芥驳。 笑死介粘,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的晚树。 我是一名探鬼主播姻采,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼爵憎!你這毒婦竟也來了慨亲?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤宝鼓,失蹤者是張志新(化名)和其女友劉穎刑棵,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體愚铡,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡蛉签,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了沥寥。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片碍舍。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖邑雅,靈堂內(nèi)的尸體忽然破棺而出片橡,到底是詐尸還是另有隱情,我是刑警寧澤淮野,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布捧书,位于F島的核電站,受9級特大地震影響骤星,放射性物質(zhì)發(fā)生泄漏经瓷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一洞难、第九天 我趴在偏房一處隱蔽的房頂上張望舆吮。 院中可真熱鬧,春花似錦廊营、人聲如沸歪泳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽呐伞。三九已至,卻和暖如春慎式,著一層夾襖步出監(jiān)牢的瞬間伶氢,已是汗流浹背趟径。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留癣防,地道東北人蜗巧。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像蕾盯,于是被迫代替她去往敵國和親幕屹。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內(nèi)容