先来看看最终实现的效果图:
ItemDecoration简介:
ItemDecoration是RecyclerView中的抽象内部类。Decoration翻译过来是装饰物,也就是item的装饰物,系统默认提供了DividerItemDecoration一个分割线实现类,使用方法如下:
rcy.addItemDecoration(new DividerItemDecoration(this, LinearLayout.HORIZONTAL));
ItemDecoration中有三个方法是我们必须关心的:
// 用于给item隔开距离,类似直接给item设padding。
1.public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state)
// 与自定义View中的方法相同,这里用于给getItemOffsets()隔开的距离填充图形;
2.public void onDraw(Canvas canvas, RecyclerView parent, RecyclerView.State state)
// 这个方法可以将内容覆盖在item上,可用于制作悬停效果,item角标等
3.public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state)
下面附上一张图来方便理解三个方法
ItemDecoration
首先我们假设绿色区域代表的是我们的内容,红色区域代表我们自己绘制的装饰,可以看到:
图1:代表了getItemOffsets(),可以实现类似padding的效果
图2:代表了onDraw(),可以实现类似绘制背景的效果,内容在上面
图3:代表了onDrawOver(),可以绘制在内容的上面,覆盖内容
先来看第一个方法:
参数中传给我们的outRect就是每个item的外围矩形,看到Rect就知道我们可以设置上下左右了,这里我们实现的是在头部加分割线,我们就只指定top值。
getItemOffsets.png
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
outRect.top = 1;
}
运行效果如下:
可以看到我们的Recyclerview每个item上面就隔开了一条分割线,只是颜色是系统默认的,我们来自己指定颜色,就需要用到第二个方法onDraw(),通过canvas在隔开的距离上填充图形,我们先简单画一个红色矩形:
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
int childCount = parent.getChildCount();
final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();
for (int i = 0; i < childCount; i++) {
View view = parent.getChildAt(i);
float top = view.getTop() - 1;
float bottom = view.getTop();
c.drawRect(left, top, right, bottom, mPaint);
}
}
运行效果如下:
下面我们来看第三个方法onDrawOver(),这个方法是在item绘制完成后调用,指定的内容绘制到item view内容之上,先简单画个图形看看效果。
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
mPaint.setColor(Color.BLUE);
c.drawRect(0,0,parent.getRight(),60,mPaint);
}
运行如下:
可以看到蓝色图形是覆盖在itemView之上的,这也是我们后面实现吸顶效果的主要方法。
ItemDecoration的三个主要方法介绍完成。
下面来看看实现吸顶效果的具体实现:
/**
* @author: Endeavor
* @date: 2018/9/25
*/
public class TitleItemDecoration extends RecyclerView.ItemDecoration {
private final int mTitleHeight;
private final int mTitleTextSize;
private final Paint mPaint;
private final Paint mTextPaint;
private final Rect textRect;
private TitleDecorationCallback callback;
private final Paint mGrayPaint;
public TitleItemDecoration(Context context, TitleDecorationCallback callback) {
this.callback = callback;
mTitleHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 30, context.getResources().getDisplayMetrics());
mTitleTextSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16, context.getResources().getDisplayMetrics());
mTextPaint = new Paint();
mTextPaint.setTextSize(mTitleTextSize);
mTextPaint.setAntiAlias(true);
mTextPaint.setColor(Color.WHITE);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(Color.RED);
mGrayPaint = new Paint();
mGrayPaint.setAntiAlias(true);
mGrayPaint.setColor(Color.DKGRAY);
textRect = new Rect();
}
// 这个方法用于给item隔开距离,类似直接给item设padding
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
int position = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition();
if (position == 0 || isFirst(position)) {
outRect.top = mTitleHeight;
} else {
outRect.top = 1;
}
}
// 这个方法用于给getItemOffsets()隔开的距离填充图形,
// 在item绘制之前时被调用,将指定的内容绘制到item view内容之下;
@Override
public void onDraw(Canvas canvas, RecyclerView parent, RecyclerView.State state) {
// 获取当前屏幕可见 item 数量,而不是 RecyclerView 所有的 item 数量
int childCount = parent.getChildCount();
final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();
for (int i = 0; i < childCount; i++) {
final View view = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view
.getLayoutParams();
int position = params.getViewLayoutPosition();
if (position == 0 || isFirst(position)) {
float top = view.getTop() - mTitleHeight;
float bottom = view.getTop();
canvas.drawRect(left, top, right, bottom, mPaint);
String groupName = callback.getGroupName(position);
mTextPaint.getTextBounds(groupName, 0, groupName.length(), textRect);
float x = view.getPaddingLeft();
float y = top + (mTitleHeight - textRect.height()) / 2 + textRect.height();
canvas.drawText(callback.getGroupName(position), x, y, mTextPaint);
} else {
float top = view.getTop() - 1;
float bottom = view.getTop();
canvas.drawRect(left, top, right, bottom, mGrayPaint);
}
}
}
// 在item被绘制之后调用,将指定的内容绘制到item view内容之上
// 这个方法可以将内容覆盖在item上,可用于制作悬停效果,角标等(这里只实现悬停效果)
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
int position = ((LinearLayoutManager) (parent.getLayoutManager())).findFirstVisibleItemPosition();
if (position <= -1 || position >= parent.getAdapter().getItemCount() - 1) {
// 越界检查
return;
}
View firstVisibleView = parent.findViewHolderForAdapterPosition(position).itemView;
final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();
int top = parent.getPaddingTop();
int bottom = top + mTitleHeight;
// 如果当前屏幕上第二个显示的item是下一组的的第一个,并且第一个被title覆盖,则开始移动上个title。
// 原理就是不断改变title所在矩形的top与bottom的值。
if (isFirst(position + 1) && firstVisibleView.getBottom() < mTitleHeight) {
if (mTitleHeight <= firstVisibleView.getHeight()) {
int d = firstVisibleView.getHeight() - mTitleHeight;
top = firstVisibleView.getTop()+d;
}else {
int d = mTitleHeight - firstVisibleView.getHeight();
top = firstVisibleView.getTop()-d;// 这里有bug,mTitleHeight过高时 滑动有问题
}
bottom = firstVisibleView.getBottom();
}
c.drawRect(left, top, right, bottom, mPaint);
String groupName = callback.getGroupName(position);
mTextPaint.getTextBounds(groupName, 0, groupName.length(), textRect);
float x = left + firstVisibleView.getPaddingLeft();
float y = top + (mTitleHeight - textRect.height()) / 2 + textRect.height();
c.drawText(groupName, x, y, mTextPaint);
}
/**
* 判断是否是同一组的第一个item
*
* @param position
* @return
*/
private boolean isFirst(int position) {
if (position == 0) {
return true;
} else {
long prevGroupId = callback.getGroupId(position - 1);
long groupId = callback.getGroupId(position);
return prevGroupId != groupId;
}
}
public interface TitleDecorationCallback {
long getGroupId(int position);
String getGroupName(int position);
}
}
参考:
图片资源:
ItemDecoration.png原图来自小武站台
getItemOffsets.png原图来自Piasy
向作者表示感谢,如作者不许我转图,烦请联系我删除。
网友评论