本文討論的是關於自定義ItemDecoration容易被忽略的問題,此文適合有過自定義ItemDecoration經驗的同學閱讀,還沒有學習過的可以先去看看相關文章再來看本文。
ItemDecoration 我相信只要使用過RecyclerView的同學肯定都比較熟悉了,我們在使用 RecyclerView 的時候一般都是用這個來畫分隔線的,不得不說十分的好用。但是在最近發現在使用自定義的ItemDecoration上遇到了一些細節上的問題,我這裡自定義了一個GridDividerItemDecoration ,用於網格佈局的分隔,大概效果如下圖所示:
繪製的邏輯大概是這樣的:當 itemView 不是最後一列或者最後一行的時候就繪製右側和底部分隔線,如果是最後一列時則不繪製右側分隔線,如果是最後一行則不繪製底部分隔線。
程式碼大概是這樣的
1 2 3 4 5 6 7 8 9 10 |
if (isLastRow && isLastColumn) {//最後一行最後一列什麼都不繪製 outRect.set(0, 0, 0, 0); } else if (isLastRow) {// 如果是最後一行,則不需要繪製底部 outRect.set(0, 0, mDividerHeight, 0); } else if (isLastColumn) {// 如果是最後一列,則不需要繪製右邊 outRect.set(0, 0, 0, mDividerHeight); } else { outRect.set(0, 0, mDividerHeight, mDividerHeight); } |
這裡的分割線設定的寬度只有1dp,看起來似乎沒有什麼問題,但是如果把分隔線的寬度設定為20dp效果如下圖所示:
會明顯的感覺到最後一列itemView的寬度會比前幾列寬一些,具體的數值就是我們設定的 dividerWidth (也就是分隔線的寬度),正常情況下我們在自定義的 ItemDocration 設定 ItemOffsets 不會影響 itemView 的的大小,然而這裡卻出現了這個問題(其實網上絕大部分流行的關於網格的ItemDocration都存在這個問題),什麼原因呢,看看下面兩段原始碼就會知道了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
private void measureChild(View view, int otherDirParentSpecMode, boolean alreadyMeasured) { final LayoutParams lp = (LayoutParams) view.getLayoutParams(); final Rect decorInsets = lp.mDecorInsets; final int verticalInsets = decorInsets.top + decorInsets.bottom + lp.topMargin + lp.bottomMargin; final int horizontalInsets = decorInsets.left + decorInsets.right + lp.leftMargin + lp.rightMargin; final int availableSpaceInOther = getSpaceForSpanRange(lp.mSpanIndex, lp.mSpanSize); final int wSpec; final int hSpec; if (mOrientation == VERTICAL) { //最後一個引數用來標識在當前的mOrientation 下是否可以滾動, //當mOrientation 是VERTICAL的時候水平方向肯定是不能滾動的 wSpec = getChildMeasureSpec(availableSpaceInOther, otherDirParentSpecMode, horizontalInsets, lp.width, false); hSpec = getChildMeasureSpec(mOrientationHelper.getTotalSpace(), getHeightMode(), verticalInsets, lp.height, true); } else { hSpec = getChildMeasureSpec(availableSpaceInOther, otherDirParentSpecMode, verticalInsets, lp.height, false); wSpec = getChildMeasureSpec(mOrientationHelper.getTotalSpace(), getWidthMode(), horizontalInsets, lp.width, true); } measureChildWithDecorationsAndMargin(view, wSpec, hSpec, alreadyMeasured); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
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 (canScroll) { if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else 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 >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } 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); } |
由於我們這裡討論的是垂直方向上的Grid,所以 mOrientation == VERTICA,從上面的程式碼可以看出當我們的itemView寬度不是精確數值的時候,然後測量出的寬度就為 Math.max(0, parentSize – padding)(這裡的 padding 就是 horizontalInsets = decorInsets.left + decorInsets.right + lp.leftMargin + lp.rightMargin
),原來這裡在實際的寬度下還減去了ItemDecoration的左右偏移量,這也就解釋了上面的那個問題。有人會問我們可不可以把寬度設定為固定值呢?可以當然是可以的,但是又會出現其他問題,下來你可以去嘗試一下,這裡我就不再去細究了。
一般情況下當 mOrientation == VERTICA 的時候itemView的寬度是 match_parent的,當 mOrientation == HORIZONTAL的時候itemView的高度就是 match_parent的,這樣才能更好的去適配各種螢幕的手機。
這裡我們找到了問題的原因所在,應該怎樣去解決呢? 其實也很簡單,就是均勻的分配offset給每一個itemView。
下面我們來計算一下偏移量。
// 每一個itemView的總偏移量(left+right)
eachOffset =(spanCount-1)* dividerWidth / spanCount;
L0=0 , R0=eachOffset;
L1=dividerWidth-R0 , R1=eachOffset-L1;
L2=dividerWidth-R1 , R2=eachOffset-L2;
其中:
Ln:表示第n列itemView left 偏移量。
Rn:表示第n列itemView right 偏移量。
可能有些同學看到上面式子會有點凌亂,這裡我直接告訴你最後推算出的結論好了,Ln 是一個以 dividerWidth-eachOffset 為差值的一個等差數列,Rn就等於 eachWidth-Ln。所以我們最後對 getItemOffsets 做了改進,程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
int left = 0; int top = 0; int right = 0; int bottom = 0; int eachWidth = (spanCount - 1) * mDividerHeight / spanCount; int dl = mDividerHeight - eachWidth; left = itemPosition % spanCount * dl; right = eachWidth - left; bottom = mDividerHeight; if (isLastRow) { bottom = 0; } outRect.set(left, top, right, bottom); |
最後的效果圖如下:
完美的解決了上面出現的問題,這都是些細節上的問題,如果不怎麼注意,還真的很難去注意到,以後如果遇到其他類似的問題也可以很容易的解決了。本文只是討論了在使用ItemDecoration其中的一個問題,並不算難,但是也很重要,所以大家在平時的開發中還是應該多多注意細節上的問題。
最後送上本文原始碼地址:
順便給大家推薦一個十分強大的開源自定義的ItemDecoration ,適用於 LinearLayoutManager作為佈局管理器的RecyclerView : RecyclerView-FlexibleDivider
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!
任選一種支付方式