RecyclerView中ItemDecoration的精妙用法,實(shí)現(xiàn)自定義分隔線、邊距和背景效果
ItemDecoration 是 RecyclerView 組件的一個(gè)非常有用的功能,用于添加自定義的裝飾項(xiàng)(如分隔線、邊距、背景等)到 RecyclerView 的每個(gè) item 之間或周圍。
recyclerView.addItemDecoration()
ItemDecoration主要的三個(gè)方法:
- onDraw(Canvas c, RecyclerView parent, RecyclerView.State state): 在 RecyclerView 的 canvas 上繪制自定義的裝飾項(xiàng),通常用于繪制分隔線或背景。
- onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state): 與 onDraw 類似,但繪制的內(nèi)容會(huì)出現(xiàn)在 item 的視圖之上。在 item 視圖上方繪制內(nèi)容(如高亮或選擇效果),可以使用這個(gè)方法。
- getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state): 設(shè)置 item 的邊距。outRect 參數(shù)是一個(gè) Rect 對象,可以設(shè)置它的 left、top、right 和 bottom 屬性來定義 item 的額外空間。這些額外的空間會(huì)用于繪制分隔線或邊距。
圖片
- 圖1:代表了getItemOffsets(),可以實(shí)現(xiàn)類似padding的效果。
- 圖2:代表了onDraw(),可以實(shí)現(xiàn)類似繪制背景的效果,內(nèi)容在上面。
- 圖3:代表了onDrawOver(),可以繪制在內(nèi)容的上面,覆蓋內(nèi)容。
分割線
實(shí)現(xiàn)分割線效果需要 getItemOffsets()和 onDraw()2個(gè)方法,首先用 getItemOffsets給item下方空出一定高度的空間(例子中是1dp),然后用onDraw繪制這個(gè)空間。
public class SimpleDividerDecoration extends RecyclerView.ItemDecoration {
private int dividerHeight;
private Paint dividerPaint;
public SimpleDividerDecoration(Context context) {
dividerPaint = new Paint();
dividerPaint.setColor(context.getResources().getColor(R.color.colorAccent));
dividerHeight = context.getResources().getDimensionPixelSize(R.dimen.divider_height);
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
outRect.bottom = dividerHeight;
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
int childCount = parent.getChildCount();
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
for (int i = 0; i < childCount - 1; i++) {
View view = parent.getChildAt(i);
float top = view.getBottom();
float bottom = view.getBottom() + dividerHeight;
c.drawRect(left, top, right, bottom, dividerPaint);
}
}
}
圖片
標(biāo)簽
標(biāo)簽都是覆蓋在內(nèi)容之上的,可以用onDrawOver()來實(shí)現(xiàn),這里簡單實(shí)現(xiàn)一個(gè)顏色標(biāo)簽。
public class LeftAndRightTagDecoration extends RecyclerView.ItemDecoration {
private int tagWidth;
private Paint leftPaint;
private Paint rightPaint;
public LeftAndRightTagDecoration(Context context) {
leftPaint = new Paint();
leftPaint.setColor(context.getResources().getColor(R.color.colorAccent));
rightPaint = new Paint();
rightPaint.setColor(context.getResources().getColor(R.color.colorPrimary));
tagWidth = context.getResources().getDimensionPixelSize(R.dimen.tag_width);
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
int pos = parent.getChildAdapterPosition(child);
boolean isLeft = pos % 2 == 0;
if (isLeft) {
float left = child.getLeft();
float right = left + tagWidth;
float top = child.getTop();
float bottom = child.getBottom();
c.drawRect(left, top, right, bottom, leftPaint);
} else {
float right = child.getRight();
float left = right - tagWidth;
float top = child.getTop();
float bottom = child.getBottom();
c.drawRect(left, top, right, bottom, rightPaint);
}
}
}
}
圖片
ItemDecoration組合
ItemDecoration是可以疊加的,可以將多個(gè)效果通過addItemDecoration方法疊加,將上面兩種效果疊加。
recyclerView.addItemDecoration(new LeftAndRightTagDecoration(this));
recyclerView.addItemDecoration(new SimpleDividerDecoration(this));
圖片
Section分組
定義接口用來進(jìn)行數(shù)據(jù)分組和獲取首字母,重寫getItemOffsets()和onDraw()方法,并根據(jù)數(shù)據(jù)進(jìn)行分組處理。
public interface DecorationCallback {
long getGroupId(int position);
String getGroupFirstLine(int position);
}
public class SectionDecoration extends RecyclerView.ItemDecoration {
private static final String TAG = "SectionDecoration";
private DecorationCallback callback;
private TextPaint textPaint;
private Paint paint;
private int topGap;
private Paint.FontMetrics fontMetrics;
public SectionDecoration(Context context, DecorationCallback decorationCallback) {
Resources res = context.getResources();
this.callback = decorationCallback;
paint = new Paint();
paint.setColor(res.getColor(R.color.colorAccent));
textPaint = new TextPaint();
textPaint.setTypeface(Typeface.DEFAULT_BOLD);
textPaint.setAntiAlias(true);
textPaint.setTextSize(80);
textPaint.setColor(Color.BLACK);
textPaint.getFontMetrics(fontMetrics);
textPaint.setTextAlign(Paint.Align.LEFT);
fontMetrics = new Paint.FontMetrics();
topGap = res.getDimensionPixelSize(R.dimen.sectioned_top);//32dp
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
int pos = parent.getChildAdapterPosition(view);
Log.i(TAG, "getItemOffsets:" + pos);
long groupId = callback.getGroupId(pos);
if (groupId < 0) return;
if (pos == 0 || isFirstInGroup(pos)) {//同組的第一個(gè)才添加padding
outRect.top = topGap;
} else {
outRect.top = 0;
}
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View view = parent.getChildAt(i);
int position = parent.getChildAdapterPosition(view);
long groupId = callback.getGroupId(position);
if (groupId < 0) return;
String textLine = callback.getGroupFirstLine(position).toUpperCase();
if (position == 0 || isFirstInGroup(position)) {
float top = view.getTop() - topGap;
float bottom = view.getTop();
c.drawRect(left, top, right, bottom, paint);//繪制紅色矩形
c.drawText(textLine, left, bottom, textPaint);//繪制文本
}
}
}
private boolean isFirstInGroup(int pos) {
if (pos == 0) {
return true;
} else {
long prevGroupId = callback.getGroupId(pos - 1);
long groupId = callback.getGroupId(pos);
return prevGroupId != groupId;
}
}
public interface DecorationCallback {
long getGroupId(int position);
String getGroupFirstLine(int position);
}
}
recyclerView.addItemDecoration(new SectionDecoration(this, new SectionDecoration.DecorationCallback() {
@Override
public long getGroupId(int position) {
return Character.toUpperCase(dataList.get(position).getName().charAt(0));
}
@Override
public String getGroupFirstLine(int position) {
return dataList.get(position).getName().substring(0, 1).toUpperCase();
}
}));
StickyHeader
頭部吸頂效果,header不動(dòng)肯定是要繪制item內(nèi)容之上,需要重寫onDrawOver()方法,其和Section實(shí)現(xiàn)一樣。
public class PinnedSectionDecoration extends RecyclerView.ItemDecoration {
private static final String TAG = "PinnedSectionDecoration";
private DecorationCallback callback;
private TextPaint textPaint;
private Paint paint;
private int topGap;
private Paint.FontMetrics fontMetrics;
public PinnedSectionDecoration(Context context, DecorationCallback decorationCallback) {
Resources res = context.getResources();
this.callback = decorationCallback;
paint = new Paint();
paint.setColor(res.getColor(R.color.colorAccent));
textPaint = new TextPaint();
textPaint.setTypeface(Typeface.DEFAULT_BOLD);
textPaint.setAntiAlias(true);
textPaint.setTextSize(80);
textPaint.setColor(Color.BLACK);
textPaint.getFontMetrics(fontMetrics);
textPaint.setTextAlign(Paint.Align.LEFT);
fontMetrics = new Paint.FontMetrics();
topGap = res.getDimensionPixelSize(R.dimen.sectioned_top);
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
int pos = parent.getChildAdapterPosition(view);
long groupId = callback.getGroupId(pos);
if (groupId < 0) return;
if (pos == 0 || isFirstInGroup(pos)) {
outRect.top = topGap;
} else {
outRect.top = 0;
}
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
int itemCount = state.getItemCount();
int childCount = parent.getChildCount();
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
float lineHeight = textPaint.getTextSize() + fontMetrics.descent;
long preGroupId, groupId = -1;
for (int i = 0; i < childCount; i++) {
View view = parent.getChildAt(i);
int position = parent.getChildAdapterPosition(view);
preGroupId = groupId;
groupId = callback.getGroupId(position);
if (groupId < 0 || groupId == preGroupId) continue;
String textLine = callback.getGroupFirstLine(position).toUpperCase();
if (TextUtils.isEmpty(textLine)) continue;
int viewBottom = view.getBottom();
float textY = Math.max(topGap, view.getTop());
if (position + 1 < itemCount) { //下一個(gè)和當(dāng)前不一樣移動(dòng)當(dāng)前
long nextGroupId = callback.getGroupId(position + 1);
if (nextGroupId != groupId && viewBottom < textY ) {//組內(nèi)最后一個(gè)view進(jìn)入了header
textY = viewBottom;
}
}
c.drawRect(left, textY - topGap, right, textY, paint);
c.drawText(textLine, left, textY, textPaint);
}
}
}
圖片