引入依賴庫:
implementation 'androidx.recyclerview:recyclerview:1.1.0'
一、ItemDecoration概念
給recyclerView的item設定分割線的,
1、itemDecoration允許應用給具體的View添加具體的圖畫或者layout的偏移,對于繪制View之間的分割線,視覺分組邊界等等是非常有用的,
2、當我們呼叫addItemDecoration()方法添加itemDecoration的時候,RecyclerView就會呼叫該類的onDraw方法去繪制分割線,也就是說,分割線是繪制出來的,
3、RecyclerView.ItemDocoration,該類為抽象類,官方目前只提供了一個實作類DivideItemDecoration,
二、ItemDocoration的原始碼,里面提供了三個廢棄的方法和三個可用的方法(onDraw、onDrawOver、getItemOffsets)
public abstract static class ItemDecoration {
/**
* Draw any appropriate decorations into the Canvas supplied to the RecyclerView.
* Any content drawn by this method will be drawn before the item views are drawn,
* and will thus appear underneath the views.
*
* @param c Canvas to draw into
* @param parent RecyclerView this ItemDecoration is drawing into
* @param state The current state of RecyclerView
*/
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull State state) {
onDraw(c, parent);
}
/**
* @deprecated
* Override {@link #onDraw(Canvas, RecyclerView, RecyclerView.State)}
*/
@Deprecated
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent) {
}
/**
* Draw any appropriate decorations into the Canvas supplied to the RecyclerView.
* Any content drawn by this method will be drawn after the item views are drawn
* and will thus appear over the views.
*
* @param c Canvas to draw into
* @param parent RecyclerView this ItemDecoration is drawing into
* @param state The current state of RecyclerView.
*/
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent,
@NonNull State state) {
onDrawOver(c, parent);
}
/**
* @deprecated
* Override {@link #onDrawOver(Canvas, RecyclerView, RecyclerView.State)}
*/
@Deprecated
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent) {
}
/**
* @deprecated
* Use {@link #getItemOffsets(Rect, View, RecyclerView, State)}
*/
@Deprecated
public void getItemOffsets(@NonNull Rect outRect, int itemPosition,
@NonNull RecyclerView parent) {
outRect.set(0, 0, 0, 0);
}
/**
* Retrieve any offsets for the given item. Each field of <code>outRect</code> specifies
* the number of pixels that the item view should be inset by, similar to padding or margin.
* The default implementation sets the bounds of outRect to 0 and returns.
*
* <p>
* If this ItemDecoration does not affect the positioning of item views, it should set
* all four fields of <code>outRect</code> (left, top, right, bottom) to zero
* before returning.
*
* <p>
* If you need to access Adapter for additional data, you can call
* {@link RecyclerView#getChildAdapterPosition(View)} to get the adapter position of the
* View.
*
* @param outRect Rect to receive the output.
* @param view The child view to decorate
* @param parent RecyclerView this ItemDecoration is decorating
* @param state The current state of RecyclerView.
*/
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view,
@NonNull RecyclerView parent, @NonNull State state) {
getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),
parent);
}
}
有6個方法,其中三個有注解@Deprecated表示不能用的,其中OnDraw和onDrawOver代表的是繪制,getItemOffsets設定item的偏移量,通過outRect.set(0, 0, 0, 0);來設定的,
1、getItemOffsets方法
- getItemOffsets方法,主要就是給itemView設定偏移量,比如RecyclerView的設定LinearLayoutManger,使用Vertical垂直方向,那上下item之間的分隔線的空間,就可以在這設定,
- outRect.set(0, 5, 0, 0),就代表top方向偏移5像素點,然后就預留出了5像素點高度的空間,給你繪制分隔線,而不會影響itemView(即垂直的時候設定了上下不會影響item高度,設定了左右會影響item寬度),
- 看注釋,設定偏移量要在后面,就是super要么不寫,要么寫在前面,看原始碼super把全部設定為0,

2、onDraw方法
這個方法繪制的東西,會在RecyclerView的item繪制之前繪制,所以onDraw繪制的東西可能會被壓在item的下面,如果設定了getItemOffsets間距,繪制的距離在這個間距內就不會被遮住,如果onDraw繪制的東西的邊距超過了Offsets設定的間距,就會被遮住,
3、onDrawOver方法
onDrawOver跟onDraw剛好相反,他是在RecyclerView繪制之后繪制,會蓋在RecyclerView上面(如果設定了getItemOffsets間距,onDrawOver繪制的距離在這個間距內就不會遮住itemView,如果onDrawOver繪制的東西的邊距超過了Offsets設定的間距,就會遮住ItemView,)

二、RecyclerView利用ItemDecoration實作吸頂效果
1.繼承RecyclerView.ItemDecoration,重寫getItemOffsets方法
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
if (parent.getAdapter() instanceof StarAdapter) {
StarAdapter adapter = (StarAdapter) parent.getAdapter();
//RecyclerView的LayoutParams,是有viewHolder的,所以可以通過View 獲取LayoutParams,再拿到ViewHolder
//獲取當前view對應的position
int childLayoutPosition = parent.getChildLayoutPosition(view);
//判斷是否是頭部
boolean isGroupHeader = adapter.isGroupHeader(childLayoutPosition);
if (isGroupHeader) {
//如果當前item是頭部,則預留更大的空間
outRect.set(0, groupHeaderHeight, 0, 0);
} else {
//不是頭部隔開:1像素
outRect.set(0, 1, 0, 0);
}
}
}
如果此時給RecyclerView設定個背景,就可以看到間隔處有顏色了,記住這是recyclerView的背景,并不是我們畫的分割線,我還沒重寫onDraw和onDrawOver呢,(分割線有清晰和不清晰應該是模擬器的問題,我在真機上都是清晰的)

2.重寫onDraw方法,開始在上一步item之間分割出來的間距進行繪制,把畫筆的顏色改成BLUE,即分割線繪制成藍色,并繪制相關字串,
@Override
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.onDraw(c, parent, state);
if (parent.getAdapter() instanceof StarAdapter) {
StarAdapter adapter = (StarAdapter) parent.getAdapter();
//當前螢屏的item個數
int childCount = parent.getChildCount();
//recyclerView的左邊padding值作為繪制分割線的左
int left = parent.getPaddingLeft();
//分割線的右邊
int right = parent.getWidth() - parent.getPaddingRight();
for (int i = 0; i < childCount; i++) {
//獲取對應i的View
View childAt = parent.getChildAt(i);
int childLayoutPosition = parent.getChildLayoutPosition(childAt);
boolean isGroupHeader = adapter.isGroupHeader(childLayoutPosition);
//是否為頭部
if (isGroupHeader && childAt.getTop() - groupHeaderHeight - parent.getPaddingTop() >= 0) {
c.drawRect(left, childAt.getTop() - groupHeaderHeight, right, childAt.getTop(), headerPaint);
String groupName = adapter.getGroupName(childLayoutPosition);
Log.i("BK", groupName + " " + childAt.getTop());
textPaint.getTextBounds(groupName, 0, groupName.length(), textRect);
c.drawText(groupName, left + 20, childAt.getTop() - groupHeaderHeight / 2
+ textRect.height() / 2, textPaint);
} else if (childAt.getTop() - groupHeaderHeight - parent.getPaddingTop() >= 0){
//分割線
c.drawRect(left, childAt.getTop() - 1, right, childAt.getTop(), headerPaint);
}
}
}
}

3.重寫OnDrawOver方法,實作吸頂的效果:因為吸頂效果是覆寫在itemView上的,所以只能用個這個方法來實作,
@Override
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.onDrawOver(c, parent, state);
if (parent.getAdapter() instanceof StarAdapter) {
StarAdapter adapter = (StarAdapter) parent.getAdapter();
//螢屏可視的第一個itemView的位置
int firstVisibleItemPosition = ((LinearLayoutManager) parent.getLayoutManager()).findFirstVisibleItemPosition();
//獲取position對應的view
View itemView = parent.findViewHolderForAdapterPosition(firstVisibleItemPosition).itemView;
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
int top = parent.getPaddingTop();
//當螢屏可視范圍內,第二個itemView是下一組的頭部的時候
boolean isGroupHeader = adapter.isGroupHeader(firstVisibleItemPosition + 1);
if (isGroupHeader) {//這種情況就要將上一個吸頂的慢慢往上頂的效果
Log.i("BK", "onDrawOver1: " + firstVisibleItemPosition);
//bottom會隨著上滑越來越小
int bottom = Math.min(groupHeaderHeight, itemView.getBottom()-parent.getPaddingTop());
c.drawRect(left, top, right, top + bottom, headerPaint);
String groupName = adapter.getGroupName(firstVisibleItemPosition);
textPaint.getTextBounds(groupName, 0, groupName.length(), textRect);
c.drawText(groupName, left + 20, top + bottom - groupHeaderHeight / 2
+ textRect.height() / 2, textPaint);
} else {//固定在頂部的效果
Log.i("BK", "onDrawOver2: " + firstVisibleItemPosition);
c.drawRect(left, top, right, top + groupHeaderHeight, headerPaint);
String groupName = adapter.getGroupName(firstVisibleItemPosition);
textPaint.getTextBounds(groupName, 0, groupName.length(), textRect);
c.drawText(groupName, left + 20, top + groupHeaderHeight / 2
+ textRect.height() / 2, textPaint);
}
}
}
每行代碼的注釋也清楚了,可以自己運行體會體會,其實在getItemOffsets把控制元件預留出來了,剩下的分割線繪制就和自定義View的繪制差不多了,


Github完整專案代碼
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/266395.html
標籤:其他
上一篇:2021-03-04
