recycleView
基本的使用与说明不再赘述,本文主要是通过ItemDecration 实现 分割线,分组 吸顶等效果;学习前你需要知道getTop getBottom 都是指的什么,如果还存在疑惑,可以看看这篇文章
[view坐标系详解](https://blog.youkuaiyun.com/u013872857/article/details/53750682)
主要的方法
要实现上述功能,需要通过I自定义itemDecration ;首先你需要了解这些方法:
-
getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state);
–item的偏移,通过该方法,可以设置item之间预留位置,一般设置分割线需要先在该方法下设置好偏移量; -
onDraw(Canvas c, RecyclerView parent, RecyclerView.State state);
–绘制前调用的,参数有canva 所以可以发现可以绘制一些内容,上个方法只能预留出位置,展示的颜色只能是背景色,想要绘制更加丰富的内容需要重写这个方法;需要注意的是** 该方法绘制的内容,如果计算出现问题,是会被itemView覆盖的** -
onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state);
–绘制后调用,会覆盖itemView,一般用于可以用来绘制分组和吸顶效果
分割线
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
int pos = parent.getChildAdapterPosition(view);
//只要不是第一组就需要设置分割线
if (pos != 0) {
outRect.top = 1;
}
}
- 首先需要 在该方法里面设置好偏移量, outRect.top 表示从第二个item开始距离上衣个item预留出1的高度;其实就这样也可以满足要求,只是分割线的颜色就是背景色,想要定制这条线,可以使用下例;
public class SectionDecoration<T extends IndexIml> extends RecyclerView.ItemDecoration {
private static final String TAG = "SectionDecoration";
private List<T> dataList;
private DecorationCallback callback;
private Paint paint;
private int dvidedHeight;
/**
* @param dataList 数据源 需继承 IndexIml
* @param context
* @param decorationCallback
* @param resId 悬浮条的背景色值
*/
public SectionDecoration(List<T> dataList, Context context, DecorationCallback decorationCallback, int resId) {
Resources res = context.getResources();
this.dataList = dataList;
this.callback = decorationCallback;
//设置悬浮栏的画笔---paint
paint = new Paint();
try {
paint.setColor(res.getColor(resId));
} catch (Resources.NotFoundException e) {
e.printStackTrace();
}
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
int pos = parent.getChildAdapterPosition(view);
//只要不是第一组就需要设置分割线
if (pos != 0) {
outRect.top = 1;
dvidedHeight=1;
}
}
@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);
if(position ==0){
continue;
}
float top=view.getTop()-dvidedHeight;
float bottom=view.getTop();
c.drawRect(left ,top,right ,bottom,paint);
}
}
}
–通过onDraw 在预留出的位置绘画出分割线,也可以拓展出其他,比如在左侧预留出一个位置,来绘画出一张图片,都可以实现(本文不考虑layout实现)
实现分组
如果考虑到拓展,数据是需要统一实现一个接口,来获取一个分组名,接口稍后提供;
public class SectionDecoration<T extends IndexIml> extends RecyclerView.ItemDecoration {
private static final String TAG = "SectionDecoration";
private List<T> dataList;
private DecorationCallback callback;
private TextPaint textPaint;
private Paint paint;
private int topGap;
private int alignBottom;
private Paint.FontMetrics fontMetrics;
/**
* @param dataList 数据源 需继承 IndexIml
* @param context
* @param decorationCallback
* @param resId 悬浮条的背景色值
*/
public SectionDecoration(List<T> dataList, Context context, DecorationCallback decorationCallback, int resId) {
Resources res = context.getResources();
this.dataList = dataList;
this.callback = decorationCallback;
//设置悬浮栏的画笔---paint
paint = new Paint();
try {
paint.setColor(res.getColor(resId));
} catch (Resources.NotFoundException e) {
e.printStackTrace();
}
//设置悬浮栏中文本的画笔
textPaint = new TextPaint();
textPaint.setAntiAlias(true);
textPaint.setTextSize(SaDensityUtils.dp2px(context, 20));
Typeface font = Typeface.create(Typeface.DEFAULT, Typeface.BOLD);
textPaint.setTypeface(font);
textPaint.setColor(context.getResources().getColor(R.color.black_all));
textPaint.setTextAlign(Paint.Align.LEFT);
fontMetrics = new Paint.FontMetrics();
//决定悬浮栏的高度等
topGap = res.getDimensionPixelSize(R.dimen.sectioned_top);
//决定文本的显示位置等
alignBottom = res.getDimensionPixelSize(R.dimen.sectioned_alignBottom);
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
int pos = parent.getChildAdapterPosition(view);
String groupId = callback.getGroupId(pos);
//只有是同一组的第一个才显示悬浮栏
if (pos == 0 || isFirstInGroup(pos)) {
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);
String groupId = callback.getGroupId(position);
String textLine = callback.getGroupFirstLine(position);
if (textLine == "") {
float top = view.getTop();
float bottom = view.getTop();
c.drawRect(left, top, right, bottom, paint);
return;
} else {
//第一组数据或者分组的第一条数据
if (position == 0 || isFirstInGroup(position)) {
float top = view.getTop() - topGap;
float bottom = view.getTop();
//绘制分组条
c.drawRect(left, top, right, bottom, paint);
//绘制文本
c.drawText(textLine, left + 2 * alignBottom, bottom - alignBottom, textPaint);
}
}
}
}
/**
* 判断是不是组中的第一个位置
*
* @param pos
* @return
*/
private boolean isFirstInGroup(int pos) {
if (pos == 0) {
return true;
} else {
// 因为是根据 字符串内容的相同与否 来判断是不是同意组的,所以此处的标记id 要是String类型
// 如果你只是做联系人列表,悬浮框里显示的只是一个字母,则标记id直接用 int 类型就行了
String prevGroupId = callback.getGroupId(pos - 1);
String groupId = callback.getGroupId(pos);
//判断前一个字符串 与 当前字符串 是否相同
if (prevGroupId.equals(groupId)) {
return false;
} else {
return true;
}
}
}
–以上代码就可以实现分组处理了,如果要实现吸顶呢?前文说了,ondraw()方法是绘画前的操作,而吸顶操作是需要覆盖在recyclView上面的,如果继续使用该方法,达不到我们的预期,只会被itemView覆盖住
吸顶处理
这里就用到了 onDrawOver()方法,这个方法调用是在绘制完itemView之后的,因此可以满足我们的需求;
当然前提也是一样的,首先需要在getItemOffsets()方法里面预留下我们的分组条位置;代码一样就不再贴出;主要是接下来的代码:
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
//获取所有的item数量
int itemCount = state.getItemCount();
//当前页面展示的item数量
int childCount = parent.getChildCount();
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
String preGroupId = "";
//数据源做了处理,如果是-1表示没有分组数据,与此例无关
String groupId = "-1";
for (int i = 0; i < childCount; i++) {
View view = parent.getChildAt(i);
int position = parent.getChildAdapterPosition(view);
preGroupId = groupId;
groupId = callback.getGroupId(position);
//同一组数据不需要处理
if (groupId.equals("-1") || groupId.equals(preGroupId)) {
continue;
}
String textLine = callback.getGroupFirstLine(position);
if (TextUtils.isEmpty(textLine)) {
continue;
}
//接下来的是重点
int viewBottom = view.getBottom();
float textY = Math.max(topGap, view.getTop());
//下一个和当前不一样移动当前
if (position + 1 < itemCount) {
String nextGroupId = callback.getGroupId(position + 1);
//组内最后一个view进入了header
if (!TextUtils.equals(nextGroupId, groupId) && viewBottom < textY) {
textY = viewBottom;
}
}
//textY - topGap决定了悬浮栏绘制的高度和位置
c.drawRect(left, textY - topGap, right, textY, paint);
//left+2*alignBottom 决定了文本往右偏移的多少(加-->向左移)
//textY-alignBottom 决定了文本往上偏移的多少 (减-->向上移)
c.drawText(textLine, left + 2 * alignBottom, textY - alignBottom, textPaint);
}
}
– 要达到的预期效果是,
- 屏幕的顶端,无论是否是分组的第一个,都要展示出分组头
- 当新的分组滑动到悬浮窗下部时,需要将上一个分组顶起来,而此时悬浮窗需要向上面移动,滑出屏幕,那么滑出屏幕的开始是什么时候呢,是悬浮窗的底部与itemView的底部重合的时候开始的,仔细想想对不对?
这个时候有个特点就是 view.getBottom 的值会开始小于topGap,而此时的 topGap会开始大于view.getTop(),因为view已经一部分滑出屏幕了;
所以有 Math.max(topGap, view.getTop()),正常情况下,值为view.getTop(),只有在顶端滑动出界面的时候才会是topGap;
这是那个数据接口
public interface IndexIml {
/**
* 获取分组号
*
* @return
*/
String getIndexName();
}
– 以上就是这些。