解決RecyclerView GridLayoutManager 設定分割線出現item寬度不等的問題
一、概述
最近研究RecyclerView給GridLayoutManager模式設定分割線的問題,在網上找了一些案例,卻都有著item寬度大小不等的問題,以鴻洋大神的這篇http://blog.csdn.net/lmj623565791/article/details/45059587為例,divider的寬度變寬了之後,就明視訊記憶體在問題,如下圖所示:
最右邊的寬度明顯大於前面兩個。
這是為什麼呢?
二、分析
進入RecyclerView原始碼,原始碼中肯定有測量子控制元件寬高的方法,該方法為measureChild,程式碼如下:
/**
* 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);
}
}
這裡就先看寬度,也就是widthSpec變數,該值通過getChildMeasureSpec方法得到,進入該方法,程式碼如下:
/**
* 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 (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);
}
發現所需要的寬度也就是size變數,而size = Math.max(0, parentSize - padding),通過傳進來的引數,可以得到size=getWidth()-(getPaddingLeft() + getPaddingRight() + widthUsed),說明子控制元件的寬度等於RecyclerView的寬度減去RecyclerView自身左右的padding值外,還要減去子控制元件的左右偏移量,因此觀察hongyang大神的程式碼:
@Override
public void getItemOffsets(Rect outRect, int itemPosition,
RecyclerView parent) {
int spanCount = getSpanCount(parent);
int childCount = parent.getAdapter().getItemCount();
if (isLastRaw(parent, itemPosition, spanCount, childCount))// 如果是最後一行,則不需要繪製底部
{
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
} else if (isLastColum(parent, itemPosition, spanCount, childCount))// 如果是最後一列,則不需要繪製右邊
{
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else {
outRect.set(0, 0, mDivider.getIntrinsicWidth(),
mDivider.getIntrinsicHeight());
}
}
呼叫outRect.set(int left, int top, int right, int bottom)方法時,left一直為0,right一直為divider的寬度,而每一項item的寬度都要減去(left+right)大小,我這裡設divider寬度為20,因此第一項item的寬度要減,20,第二項item寬度要減20,第三項item的left為0,right也為0,寬度要減0。因此第三項就比前面兩項都寬20了,item寬度就明顯不相同了。
三、實現
因此要讓每一項item寬度都相同,就要在outRect.set方法呼叫時讓每一項left+right的大小相同,同時第一項的right加上第二項的left大小等於divider的寬度(後面就叫dividerWidth了)。先給張實現後的圖:
圖中每行itemView有3個,有2條divider,每一項itemView的偏移大小就是(2*dividerWidth)/3了,同理,給定每一行itemView的個數為spanCount,也就是RecyclerView的列數,則divider的條數為(spanCount-1),
因此定義每一項itemView的偏移寬度為:
eachWidth=(spanCount-1)* dividerWidth / spanCount;
因為第一項的left(之後用L0表示,以此類推,L1、L2…代表第二、三…項的left
)為0,所以第一項的right(之後用R0表示)為eachWidth-0,也就是說:
L0 = 0; R0=eachWidth;
第一項的right加上第二項的left等於dividerWidth,因此L1=dividerWidth-R0=dividerWidth-eachWidth,
R1=eachWidth-L1=eachWidth-(dividerWidth-eachWidth)=2eachWidth-dividerWidth
L1=dividerWidth-eachWidth; R1=2eachWidth-dividerWidth
因此L2=dividerWidth-R1=dividerWidth-(2eachWidth-dividerWidth)=2dividerWidth-2eachWidth=2L1,同理可得L3=3L1,L4=4L1……Ln=nL1,因此left可以用以下程式碼來表示:
left = itemPosition % spanCount * (dividerWidth - eachWidth); //itemPositison代表當前item位置,0,1,2
因此right可以表示為:
right = eachWidth - left;
因此可以這樣重新getItemOffsets方法:
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
int itemPosition = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition();
int spanCount = getSpanCount(parent);
int childCount = parent.getAdapter().getItemCount();
boolean isLastRow = isLastRow(parent, itemPosition, spanCount, childCount);
int top = 0;
int left;
int right;
int bottom;
int eachWidth = (spanCount - 1) * mDividerWidth / spanCount;
left = itemPosition % spanCount * (mDividerWidth - eachWidth);
right = eachWidth - left;
bottom = mDividerWidth;
if (isLastRow){
bottom = 0;
}
outRect.set(left, top, right, bottom);
}