之前用RecyclerView實現了寫過一篇城市導航列表:
關於自定義的導航條,滑動監聽,漢字轉拼音等零碎知識,大家可以查看我之前那篇博客。
今天主要說的是懸停列表的實現,之前的實現方式是每一個RecyclerView的item的佈局裏面都包含一個頭部佈局,然後判斷當前item和上一個item的頭部佈局裏的索引字母是否相同,來決定是否展示item的頭部佈局。這種實現方式顯得佈局冗餘,效率降低,而且不夠優雅。今天這裏我用ItemDecoration來實現城市列表的頭部懸停。
ItemDecoration是用來修飾RecyclerView裏的Item,ItemDecoration類主要有三個方法:
public void onDraw(Canvas c, RecyclerView parent, State state)
public void onDrawOver(Canvas c, RecyclerView parent, State state)
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state)
這三個方法作用依次是:
onDraw():可以實現類似繪製背景的效果,item的內容在上面
onDrawOver():可以繪製在內容的上面,覆蓋item的內容
getItemOffsets():可以實現類似padding的效果
onDraw()的繪製會先於子ItemView的繪製,如果你在onDraw()方法中繪製的東西在子ItemView邊界內,就會被ItemView蓋住,所以我們一般先調用getItemOffsets()創造空間。而onDrawOver()會在子ItemView繪製之後再繪製,繪製的內容會覆蓋在子ItemView上。
執行順序是先執行ItemDecoration的onDraw()、再執行子ItemView的onDraw()、再執行ItemDecoration的onDrawOver()。
下面我們通過改造這個例子來分析這三個方法:
1.getItemOffsets()
我移除了之前項目裏用到的懸停,側邊導航欄與滑動監聽還保留,看下現在的效果:
ok,現在我們來自定義一個ItemDecoration:
public class StickyDecoration extends RecyclerView.ItemDecoration {
private int topHeight;
public StickyDecoration(Context context) {
Resources res = context.getResources();
topHeight = res.getDimensionPixelSize(R.dimen.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);
if (isFirstInGroup(position)) {
outRect.top = topHeight;
} else {
outRect.top = 0;
}
}
private boolean isFirstInGroup(int position) {
boolean isFirst;
if (position == 0) {
isFirst = true;
} else {
if (CityActivity.cityList.get(position).getFirstPinYin().
equals(CityActivity.cityList.get(position - 1).getFirstPinYin())) {
isFirst = false;
} else {
isFirst = true;
}
}
return isFirst;
}
}
繼承自RecyclerView.ItemDecoration,然後重寫構造方法,並且重寫getItemOffsets()方法。進行判斷,該item如果是第一次出現該字母,就給這個item的上方繪製一個40dp的高度,否則就不處理。Activity中使用:
recyclerView.addItemDecoration(new StickyDecoration(getApplicationContext()));
最後我們看一下效果:
可以看到,在每一個字母首次出現的item上,都多了一個40dp的空間,這裏用來放我們的索引字母。
2.onDraw()
onDraw()可以實現類似繪製背景的效果,item的內容在上面,我們通過getItemOffsets()創造了空間,然後用onDraw()方法繪製字母索引:
private TextPaint textPaint;
private Paint paint;
private int topHeight;
public StickyDecoration(Context context) {
Resources res = context.getResources();
paint = new Paint();
paint.setColor(res.getColor(R.color.colorAccent));
textPaint = new TextPaint();
textPaint.setAntiAlias(true);
textPaint.setTextSize(60);
textPaint.setColor(Color.WHITE);
topHeight = res.getDimensionPixelSize(R.dimen.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);
if (isFirstInGroup(position)) {
outRect.top = topHeight;
} 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);
String textLine = CityActivity.cityList.get(position).getFirstPinYin();
if (position == 0 || isFirstInGroup(position)) {
float top = view.getTop() - topHeight;
float bottom = view.getTop();
c.drawRect(left, top, right, bottom, paint);//繪製紅色矩形
c.drawText(textLine, left + 30, bottom - 30, textPaint);//繪製文本
}
}
}
這裏的onDraw()方法與自定義View的onDraw()一樣,只不過這裏繪製的對象是RecyclerView子item的view。我們看下最後實現的效果:
OK,在首次出現該字母的item上都出現了字母索引,接下來就是懸停的實現了。
3.onDrawOver()
onDrawOver()方法會繪製在內容的上面,覆蓋item的內容,所以我們拿來做懸停很合適。
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
int position = ((LinearLayoutManager) (parent.getLayoutManager())).findFirstVisibleItemPosition();
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
c.drawRect(left, 0, right, topHeight, paint);//繪製紅色矩形
String text = CityActivity.cityList.get(position).getFirstPinYin();
c.drawText(text, 30, topHeight - 30, textPaint);//繪製文本
}
實現的代碼其實很簡單,拿到RecyclerView可見區域第一個item的position,得到大寫字母。依次繪製紅色背景與文字即可。最後實現的效果如下:
OK,和之前實現的效果還是沒什麼區別的。