之前用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,和之前实现的效果还是没什么区别的。
项目完整源码已经上传到我的github上,源码地址:
欢迎Star,fork,提issues,一起进步!