1、ItemDecoration概念
在使用RecyclerView顯示串列的時候需要分隔線隔開item,此時則可以使用Recycler的內部類ItemDecoration,ItemDecoration是android系統提供的基類,用于繪制RecyclerView的分隔線,我們可以通過繼承ItemDecoration實作豐富的分隔線效果,(系統也提供了實作好的DividerItemDecoration使用,)
2、ItemDecoration的使用
使用ItemDecoration時,需繼承此類并覆寫其中的getItemOffsets()和onDraw()方法即可,其中getItemOffsets()有兩個多載的方法,如下:
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state)
public void getItemOffsets(@NonNull Rect outRect, int itemPosition, @NonNull RecyclerView parent)
其中第二個方法已經過時不推薦使用了,
3、getItemOffsets()
3.1、分析
android官網的說明如下:
Retrieve any offsets for the given item. Each field of outRect specifies the number of pixels that the item view should be inset by, similar to padding or margin.
該方法的outRect引數會與item進行inset運算,從而決定顯示區型域,Rect的inset運算規則如下:
/**
* Insets the rectangle on all sides specified by the insets.
* @hide
* @param left The amount to add from the rectangle's left
* @param top The amount to add from the rectangle's top
* @param right The amount to subtract from the rectangle's right
* @param bottom The amount to subtract from the rectangle's bottom
*/
public void inset(int left, int top, int right, int bottom) {
this.left += left;
this.top += top;
this.right -= right;
this.bottom -= bottom;
}
從上面代碼可以看出系統會根據outRect的四個值來擴大item的區域,給分隔線留下繪制的空間,
If you need to access Adapter for additional data, you can call getChildAdapterPosition(View) to get the adapter position of the View.
如果需要獲取item的資料自然順序,可以呼叫recycler的getChildAdapterPosition(View)方法,
3.2、覆寫
覆寫該方法最核心的就是定義outRect的值,即確定分隔線的繪制區域,注意在繪制的時候需要區分是LinearLayoutManager還是GridLayoutManager,
如下代碼是使用LinearLayout時添加分隔線,
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
LinearLayoutManager lm = (LinearLayoutManager) parent.getLayoutManager();
if (lm.getOrientation() == LinearLayoutManager.HORIZONTAL) {
// 畫垂直線
outRect.set(0, 0, mDividerWidth, 0); // 指在原有的item的基礎上,將右邊加10
} else {
// 畫水平線
outRect.set(0, 0, 0, mDividerWidth); // 指在原有的item基礎上,將底部加10
}
}
在繪制每個item的時候系統都會回呼該方法,在item相應的邊加上分隔線的寬度,
在開發程序中有時不需要最后一行或是最后一列的分隔線,則可以通過邏輯判斷來設定分隔線的區域,
如下判斷是否是最后一行
private boolean isLastRow(GridLayoutManager layoutManager, RecyclerView parent, View view) {
int spanCount = layoutManager.getSpanCount(); // 列數
int itemCount = parent.getAdapter().getItemCount();
int currentPosition = parent.getChildAdapterPosition(view);
int lastRowCount = itemCount % spanCount == 0 ? spanCount : itemCount % spanCount;
if ((currentPosition + lastRowCount) - itemCount >= 0) {
return true;
}
return false;
}
如下判斷是否是最后一列
private boolean isLastColumn(GridLayoutManager layoutManager, RecyclerView parent, View view) {
int spanCount = layoutManager.getSpanCount(); // 現在無法關聯原始碼,后面看一下,這個應該是列數
int currentPosition = parent.getChildAdapterPosition(view);
if ((currentPosition + 1) % spanCount == 0) {
return true;
}
return false;
}
利用如上的方法可以將分隔線的繪制區域設定為0,則可以不顯示分隔線,對于LinearLayoutManager則直接判斷是否是最后一個元素即可,
但在實際的編碼程序中發現,即使這樣加入了處理存在如下問題,
在recyclerview的內容沒有超過recyclerview的高度時(即沒有發生滾動的情況下),即使不預留分割線的寬高也會繪制出分割線,
暫時還沒搞清楚這個問題?
該問題可以通過確定最后一行元素的序號,在onDraw方法中不進行繪制避開,
4、onDraw方法分析
在確定了分隔線的繪制區域后,接下來就要根據區域繪制具體的內容了,
系統提供了兩個繪制分隔線的方法,
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state)
該方法會在繪制item之前呼叫
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state)
該方法會在繪制item之后呼叫,
實際的效果二者差別不是很明顯,一般選用第一個方法,
如下代碼是繪制水平分割線(寬度與item相同),繪制的區域是根據getItemOffsets()確定的(在每個item的底邊繪制),每個item都需要動態計算,
private void drawHorizontal(@NonNull RecyclerView parent, Canvas canvas) {
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
RecyclerView.LayoutParams rlp = (RecyclerView.LayoutParams) child.getLayoutParams();
int left = child.getLeft() - rlp.leftMargin;
int right = child.getRight();
int top = child.getBottom() + rlp.bottomMargin;
int bottom = top + mDividerWidth;
p.setColor(Color.GRAY);
Rect r = new Rect(left, top, right, bottom);
canvas.drawRect(r, p);
}
}
繪制豎直分割線(高度與item相同,在每個item的右邊繪制),同上,
private void drawVertical(@NonNull RecyclerView parent, Canvas canvas) {
// 畫垂直線
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
RecyclerView.LayoutParams rlp = (RecyclerView.LayoutParams) child.getLayoutParams();
int left = child.getRight() + rlp.rightMargin;
int top = child.getTop() - rlp.topMargin;
int right = left + 10;
int bottom = child.getBottom() + rlp.bottomMargin;
p.setColor(Color.RED);
Rect r = new Rect(left, top, right, bottom);
canvas.drawRect(r, p);
}
}
5、改進繪制區域
如果將分割線的寬度進行放大,那么很容易發現,分割線占用了item的寬度,不繪制最后一行帶或最后一列會導致item的寬度大小不一致,
具體分析見此篇文章,分析的很全面,具體的做法如下:
先計算出每個item的偏移寬度,如下:
eachWidth =(spanCount-1)* dividerWidth / spanCount; // spanCount是列數,dividerWidth是分割線寬度,(將兩個divider的寬度均分到3個item上)
在根據每一個item的right和下一個item的left相加等于dividerWidth,從而遞推出每個item的left運算式
left = itemPosition % spanCount * (dividerWidth - eachWidth); // itemPosition是item的順序
那么,right則可以如下表示
right = eachWidth - left;
故對于GridLayoutManager中item的寬度不一致的問題可以修正如下:
if (parent.getLayoutManager() instanceof GridLayoutManager) {
GridLayoutManager layoutManager = (GridLayoutManager) parent.getLayoutManager();
int itemPosition = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition();
int spanCount = layoutManager.getSpanCount(); // 列數
boolean isLastRow = isLastRow(layoutManager, parent, view);
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);
}
6、開源庫及參考
- item decoration
- simple item decoration, 其中StartOffsetItemDecoration關于添加RecyclerView的偏移的思想可以學習一下,
- 寬度不均問題參考
- 啟艦的這篇文章更好理解,
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/261763.html
標籤:其他
