最近在項(xiàng)目項(xiàng)目中要做個(gè)類似于美團(tuán)城市列表選擇的頁(yè)面,也就是我們常說(shuō)的帶索引并且粘性頭部的列表。
如下圖是最終效果
大家能直觀的看出:每個(gè)字母開(kāi)頭相同的城市分為了一組,并且滾動(dòng)的時(shí)候那個(gè)頭部都是是固定在其頂部,以此替換榛搔。
在這之前的一個(gè)項(xiàng)目,做過(guò)手機(jī)聯(lián)系人的目錄和這個(gè)需求很類似东揣,本想把之前的東西直接套過(guò)來(lái)践惑。但發(fā)現(xiàn)當(dāng)時(shí)的做法有點(diǎn)不夠優(yōu)雅而且item 的效率很低,實(shí)現(xiàn)方式:
每個(gè)item 都設(shè)有灰色頭部的索引嘶卧,只不過(guò)是 當(dāng)只在每組的第一個(gè)位置可見(jiàn)其他時(shí)候隱藏尔觉。這樣就把整個(gè)列表進(jìn)行了分類處理,至于那個(gè)粘性頭部芥吟,是在父布局外加了一個(gè)頭部View固定在頂部并做滾動(dòng)的處理侦铜。
后來(lái)在網(wǎng)上查看到一種很不錯(cuò)的做法专甩,通過(guò)給recyclerview添加ItemDecoration裝飾的效果。網(wǎng)上關(guān)于這個(gè)知識(shí)的文章很多钉稍,也有很多現(xiàn)成的代碼配深。但是作為一個(gè)合格的程序猿一定不是復(fù)制粘貼,要明白其所以然嫁盲,并運(yùn)用到自己實(shí)際需求當(dāng)中。那我們就通過(guò)代碼一起學(xué)習(xí)ItemDecoration的使用吧烈掠。
通過(guò)繼承ItemDecoration實(shí)現(xiàn)三個(gè)很重要的方法
/**
* 對(duì)itemview矩形區(qū)域進(jìn)行的裝飾羞秤, Rect的上下左右,類似padding的效果
* @param outRect
* @param view
* @param parent
* @param state
*/
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
}
/**
* 就像自定義View中onDraw繪制作用 只不過(guò)是在ItemView的指定位置繪制指定的內(nèi)容
* 當(dāng)然要和onDrawOver有個(gè)細(xì)微的區(qū)分左敌,它繪制的范圍在itemview的下面
* 比如:所繪制的區(qū)域和itemView有重疊的則會(huì)被遮擋瘾蛋。而在onDrawOver中繪制的范圍則相反
* @param c
* @param parent
* @param state
*/
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
}
/**
* @param c
* @param parent
* @param state
*/
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
}
那我們就通過(guò)具體的實(shí)現(xiàn)看看到底有效果
recyclerview.addItemDecoration(new CustomItemDecoration(this));
然后在自定義的Decoration中簡(jiǎn)單的添加的做一下處理,注意:
outRect.bottom = dividerHeight;
outRect.right = dividerHeight;
outRect.left = dividerHeight;
給每個(gè)item的是三個(gè)方向添加個(gè)類似10dp高的padding
public class CustomItemDecoration extends RecyclerView.ItemDecoration {
private final int dividerHeight;
private final int dividerColor;
private final Paint dividerPaint;
private final Paint dividerPaintL;
private final Paint dividerPaintR;
public CustomItemDecoration(Context context) {
dividerPaint =new Paint();
dividerPaintL = new Paint();
dividerPaintR = new Paint();
dividerHeight =context.getResources().getDimensionPixelOffset(R.dimen.divider_height);
dividerColor = context.getResources().getColor(R.color.colorPrimary);
dividerPaint.setColor(dividerColor);
dividerPaintL.setColor(context.getResources().getColor(R.color.colorAccent));
dividerPaintR.setColor(context.getResources().getColor(R.color.colorAccent));
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
outRect.bottom = dividerHeight;
outRect.right = dividerHeight;
outRect.left = dividerHeight;
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
int childCount = parent.getChildCount();
int left = parent.getPaddingLeft()+dividerHeight;
int right = parent.getWidth() - parent.getPaddingRight()+parent.getPaddingLeft()-dividerHeight;
int lLeft=parent.getPaddingLeft() ;
int lRight = lLeft + dividerHeight;
for (int i = 0; i < childCount; i++) {
View view = parent.getChildAt(i);
float top = view.getBottom();
float bottom = view.getBottom() + dividerHeight;
float lTop = view.getTop();
float lBottom = view.getBottom();
int left1 = view.getRight();
int right1 = left1 + dividerHeight;
int top1 = view.getTop();
int bottom1 = view.getBottom();
//底部
c.drawRect(left, top, right, bottom, dividerPaint);
//左側(cè)
c.drawRect(lLeft, lTop, lRight, lBottom, dividerPaintL);
//右側(cè)
c.drawRect(left1, top1, right1, bottom1, dividerPaintR);
}
}
}
那我們來(lái)看效果
可以看到在每個(gè)itemView的左右和下方多出了間隙矫限,并且在onDraw()方法中繪制了不同的顏色哺哼,我們能很直觀的了解每個(gè)方法的作用和它工作的原理。
然后我們運(yùn)用到需求當(dāng)中叼风。
- 只有在同一組中才添加ItemDecoration
- 滾動(dòng)的時(shí)候固定頭部
看下面的代碼實(shí)現(xiàn):
public class SectionDecoration extends RecyclerView.ItemDecoration {
private final String TAG = SectionDecoration.class.getName();
private final Paint paint;
private final TextPaint textPaint;
private final Paint.FontMetrics fontMetrics;
private final int topGap;
private DecorationCallback decorationCallback;
public SectionDecoration(Context context, DecorationCallback callback) {
this.decorationCallback = callback;
Resources resources = context.getResources();
paint = new Paint();
paint.setColor(resources.getColor(R.color.colorf5));
textPaint = new TextPaint();
textPaint.setTypeface(Typeface.DEFAULT_BOLD);
textPaint.setAntiAlias(false);
textPaint.setTextSize(80);
textPaint.setColor(Color.BLACK);
textPaint.setTextAlign(Paint.Align.LEFT);
fontMetrics = new Paint.FontMetrics();
textPaint.getFontMetrics(fontMetrics);
topGap = resources.getDimensionPixelOffset(R.dimen.section_top);
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
int position = parent.getChildAdapterPosition(view);
Log.e(TAG, "getItemOffsets :" + position);
long groupId = decorationCallback.getGroupId(position);
if(groupId<0) return;
if (position == 0 || isFirstInGroup(position)) {//同組的第一個(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 = decorationCallback.getGroupId(position);
if(groupId<0) return;
String textLine = decorationCallback.getGroupFirstLine(position).toUpperCase();
if (position == 0 || isFirstInGroup(position)) {
int top = view.getTop() - topGap;
int bottom = view.getTop();
c.drawRect(left,top,right,bottom,paint);//繪制矩形區(qū)
c.drawText(textLine, left, bottom, textPaint);
}
}
}
@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 = decorationCallback.getGroupId(position);
if (groupId < 0 || groupId == preGroupId) continue;
String textLine = decorationCallback.getGroupFirstLine(position).toUpperCase();
if(TextUtils.isEmpty(textLine)) continue;
int viewBottom = view.getBottom();
int textY = Math.max(topGap, view.getTop());
if (position + 1 < itemCount) {
long nextGroupId = decorationCallback.getGroupId(position + 1);
if (nextGroupId != groupId && viewBottom < textY) {
textY = viewBottom;
}
}
c.drawRect(left, textY - topGap, right, textY, paint);
c.drawText(textLine, left, textY, textPaint);
}
}
private boolean isFirstInGroup(int position) {
if (position == 0) {
return true;
} else {
long prevGroupId = decorationCallback.getGroupId(position - 1);
long groupId = decorationCallback.getGroupId(position);
return prevGroupId != groupId;
}
}
}
定義一個(gè)回調(diào)
public interface DecorationCallback {
long getGroupId(int position);
String getGroupFirstLine(int position);
}
這就完成了簡(jiǎn)單的效果
說(shuō)實(shí)話剛開(kāi)始使用itemDecorartion是比較麻煩的取董,但是當(dāng)掌握上手以后,用起來(lái)真的很爽无宿,避免了很多在代碼中邏輯判斷茵汰,比如有的時(shí)候在列表中只需要在某個(gè)位置才需要添加灰色的間距,如果不用itemDecoration孽鸡,則通常的就是整加視圖的層級(jí)蹂午,通過(guò)if else判斷顯示和隱藏。現(xiàn)在既然在RecyclerView中又itemDecorartion這個(gè)裝飾彬碱,沒(méi)什么不用讓你的代碼實(shí)現(xiàn)的更優(yōu)雅豆胸。
這是寫(xiě)的第一篇博客,也希望自己能堅(jiān)持這個(gè)習(xí)慣巷疼,記錄自己的學(xué)習(xí)過(guò)程
風(fēng)后面是風(fēng)晚胡,天空上面是天空,而你的生活可以與眾不同