本文共 4985 字,大约阅读时间需要 16 分钟。
之前用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,一起进步!