如果用多个recyclerview 或者里面多套个adapter当我没说,这里的实现方式是通过.借助griviewmanager和item
效果图是要达到这样的.当然这也是我最后搞完的图.
需求:
标题作为1行 内容为3列任意行.
标题内容分组 给标题内容包裹区域加圆角.
只有内容无标题的分组 就给内容区域加圆角
遇到的问题
1.当内容未满3列,颜色为recyclerview颜色或背景色.
2.边距写的不对,导致内容被扭曲,某个item出现背景色而且未和其他行对其.
3.滑动的时候发现我绘制的颜色掉了(是我粗心用的i而不是适配器位置导致)
4.判断末尾行
如果是末尾行直接填充白色圆角
5.如果没有标题,直接给内容加上左 和上右圆角.
6.最底部的边距没得.
其它
直接什么都不做的时候,没有圆角,没有分割线.
设置内容item为3列,但是不满3列,会导致出现背景颜色,有些朋友可能直接设置recyclerview背景了,但是你怕是不要处理圆角了??
如何实现?
- 使用GridLayoutManager
2.给GridLayoutManager 设置setSpanSizeLookup 如果是标题就合并 getSpanSize 表示合并的尺寸,如果是内容是3列,那么这里返回3.否则返回1 , - 设置ItemDecoration 用
getItemOffsets
控制间隔 , - 设置ItemDecoration 用
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
int spanSize = layoutManager.getSpanSizeLookup().getSpanSize(childAdapterPosition);
主要用于判断当前组是不是0,最后一组我用的另外的方法,组0 index=0, 代表是头部,如果是标题就绘制整个,如果内容 index=0也绘制 ,额好像不需要用到spanGroupIndex
我记得用到过得
int spanGroupIndex = layoutManager.getSpanSizeLookup().getSpanGroupIndex(childAdapterPosition, spanCount);
int spanIndex = layoutManager.getSpanSizeLookup().getSpanIndex(childAdapterPosition, spanCount); //比如第一个是标题 ,第二列是 3格子,标题的永远是0 ,分3列的则是0 12 这样的循环。groupindex就是 每一列的完毕 就算一组。
用于判断是否是最后一行
int itemCount = parent.getLayoutManager().getItemCount();
用于预测下一个是不是标题,是标题那么现在就应该开始绘制下半身的圆角,以及如果内容item列不满足总列数,就绘制弥补空白处.
int lastspanIndex = layoutManager.getSpanSizeLookup().getSpanIndex(itemCount-1, spanCount); //比如第一个是标题 ,第二列是 3格子,标题的永远是0 ,分3列的则是0 12 这样的循环。groupindex就是 每一列的完毕 就算一组。
下面是具体写完之后的代码.
绘制空缺块和圆角块.
下图红色代表填充的空缺快,最后一组的填充则是绿色 , 头部是蓝色圆角 , 非最后一组底部圆角填充了黄色,我这样做只是为了调试,弄完之后我就把颜色改成白色了..
经常用到的判断
判断当前是跨了几行,用于判断是否是标题
GridLayoutManager layoutManager = new GridLayoutManager(recyclerView.getContext(), spanCount, LinearLayoutManager.VERTICAL, false);
recyclerView.setLayoutManager(layoutManager);
layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
int type = recyclerView.getAdapter().getItemViewType(position);
if (position >= recyclerView.getAdapter().getItemCount()) {
return NOT_FOUND_SPAN_SIZE;
} else if (type == TYPE_MENU_GROUP_NAME) {
return MENU_TITLE_SPAN_SIZE;
} else if (type == TYPE_GROUP_DATA) {
return 1;
} else {
return NOT_FOUND_SPAN_SIZE;//这是 decoration调用的。
}
}
@Override
public int getSpanIndex(int position, int spanCount) {
int spanIndex = super.getSpanIndex(position, spanCount);
Log.w(TAG, "spanIndex" + spanIndex + ",pos" + position + ",spanCount:" + spanCount);
return spanIndex;
}
@Override
public int getSpanGroupIndex(int adapterPosition, int spanCount) {
int spanGroupIndex = super.getSpanGroupIndex(adapterPosition, spanCount);
Log.w(TAG, "spanGroupIndex" + spanGroupIndex + ",adapterPosition" + adapterPosition + ",spanCount:" + spanCount);
return spanGroupIndex;
}
});
绘制圆角和空缺快设置代码
for (int ix = 0; ix < childCount; ix++) {
final View child = parent.getChildAt(ix);
int position = parent.getChildAdapterPosition(child);
GridLayoutManager layoutManager = (GridLayoutManager) parent.getLayoutManager();
int spanCount = layoutManager.getSpanCount();//表示整个列表分多少列
int spanSize = layoutManager.getSpanSizeLookup().getSpanSize(position);//表示当前格所占有的格子数
int spanIndex = layoutManager.getSpanSizeLookup().getSpanIndex(position, spanCount); //这里参数是spanCount,不是spanSize,否则不对,比如第一个是标题 ,第二列是 3格子,标题的永远是0 ,分3列的则是0 12 这样的循环。groupindex就是 每一列的完毕 就算一组。
if (BuildConfig.DEBUG) {
// MyAppListAdapter adapter = (MyAppListAdapter) parent.getAdapter();
// SubMenuItemI subMenuItemI = adapter.getData().get(position);
// int spanSizeNext = layoutManager.getSpanSizeLookup().getSpanSize(position + 1);
}
if (spanSize != MENU_TITLE_SPAN_SIZE) {//普通item只会跨1个。 除非是菜单
int spanGroupIndex = layoutManager.getSpanSizeLookup().getSpanGroupIndex(position, spanCount);
int spanSizeNext = layoutManager.getSpanSizeLookup().getSpanSize(position + 1);
if (spanIndex < MENU_TITLE_SPAN_SIZE - 1) {// MENU_TITLE_SPAN_SIZE 等于new GridManager里面的Count,这里用合并的数来判断也可以做,是否属于最后一个格子,但是最后一个格子又是否铺满整个屏幕是最后一个但是没有刚好没铺满,就把它给铺满。
if (spanSizeNext == NOT_FOUND_SPAN_SIZE || spanSizeNext == MENU_TITLE_SPAN_SIZE) {//如果下一个不存在那么是最后一个,或者下一个是标题了。
drawable = generateDrawable(-1);//补空缺
boundAndDraw(c, child.getRight(), right, drawable, child.getTop(), child.getBottom());//这里没有任何圆角
if (BuildConfig.DEBUG) {
/* MyAppListAdapter adapter = (MyAppListAdapter) parent.getAdapter();
SubMenuItemI subMenuItemI = adapter.getData().get(position);
Log.w(TAG, subMenuItemI + ",为最后一个前后面超出!spanIndex:" + spanIndex + ",spanSize:" + spanSize + ",height:" + child.getHeight());
*/
}
}
}
if (spanSizeNext == NOT_FOUND_SPAN_SIZE || spanSizeNext == MENU_TITLE_SPAN_SIZE) {// 刚好铺满的那种 铺满还需要判断是否下一行是否是标题.
//左下角圆角.
drawable = generateDrawable(5);//,spanSizeNext == NOT_FOUND_SPAN_SIZE ?Color.GREEN:Color.YELLOW);
int top = child.getTop() + child.getHeight();
int bottom = (int) (top + radius);
// Log.w(TAG,"LEFT:"+left+",top:"+top+",right:"+right+",bottom:"+bottom);
boundAndDraw(c, left, right, drawable, top, bottom);
}
if( spanGroupIndex==0&&position==0){//没有头部 的顶
drawable = generateDrawable(4);
int top = (int) (child.getTop() - radius);
int bottom = child.getTop();
boundAndDraw(c, left, right, drawable, top, bottom);
}
} else {//标题头
drawable = generateDrawable(4);
int top = (int) (child.getTop() - radius);
int bottom = child.getTop();
boundAndDraw(c, left, right, drawable, top, bottom);
}
}
}
private void debugDrwa(Canvas c, int left, int right, View child) {
//以下计算主要用来确定绘制的位置
final int top1 = child.getBottom() + 1;
ColorDrawable drawable1 = new ColorDrawable(Color.BLUE);
final int bottom = top1 + drawable1.getIntrinsicHeight();
boundAndDraw(c, left, right, drawable1, top1, bottom);
}
/**
* /外矩形 0左上、1右上、2 右下、3左下的圆角半 4 上 左和右边 5 ,下 左右。
*
* @param type
* @return
*/
private GradientDrawable generateDrawable(int type, int color) {
this.color = color;
return generateDrawable(type);
}
测距代码 测距就是给view留空间,不留空间就绘制在一坨了.
int childAdapterPosition = parent.getChildAdapterPosition(view);
GridLayoutManager layoutManager = (GridLayoutManager) parent.getLayoutManager();
int spanCount = layoutManager.getSpanCount(); //构造时传递的参数.
//DefaultSpanSizeLookup
int spanSize = layoutManager.getSpanSizeLookup().getSpanSize(childAdapterPosition);
int spanGroupIndex = layoutManager.getSpanSizeLookup().getSpanGroupIndex(childAdapterPosition, spanCount);
if (spanSize == MENU_TITLE_SPAN_SIZE) { //标题才跨 3个。
// if (needTop) {
if (spanGroupIndex == 0) {
outRect.top = (int) (getSpace() + radius);
} else {
outRect.top = (int) (getSpace() + (radius*2));
}
// }
} else {
outRect.top = 0;
outRect.left = 0;
outRect.right = 0;
if( spanGroupIndex==0){//没有头部 的顶
outRect.top = (int) (getSpace() + radius);
}else{
outRect.top = 0;
}
int spanIndex = layoutManager.getSpanSizeLookup().getSpanIndex(childAdapterPosition, spanCount); //比如第一个是标题 ,第二列是 3格子,标题的永远是0 ,分3列的则是0 12 这样的循环。groupindex就是 每一列的完毕 就算一组。
int itemCount = parent.getLayoutManager().getItemCount();
int lastspanIndex = layoutManager.getSpanSizeLookup().getSpanIndex(itemCount-1, spanCount); //比如第一个是标题 ,第二列是 3格子,标题的永远是0 ,分3列的则是0 12 这样的循环。groupindex就是 每一列的完毕 就算一组。
if (childAdapterPosition>=itemCount-(lastspanIndex+1)) {//最后一行
outRect.bottom= (int) (getSpace()+(radius/2));
if(lastspanIndex==spanIndex&&(1+spanIndex!=MENU_TITLE_SPAN_SIZE)){
// outRect.right=30;//=parent.getRight()-parent.getPaddingRight();//写不写其实都一样,因为不绘制右边?不,应该说都铺满屏幕了,不怕控件没给够
}//不能加right,会影响空间
}
}
if (BuildConfig.DEBUG) {
// int spanGroupIndex =;//1 23格子 或者 1列 都算一组,
int spanIndex = layoutManager.getSpanSizeLookup().getSpanIndex(childAdapterPosition, spanCount); //比如第一个是标题 ,第二列是 3格子,标题的永远是0 ,分3列的则是0 12 这样的循环。groupindex就是 每一列的完毕 就算一组。
/* SubMenuItemI subMenuItemI = ((MyAppListAdapter) parent.getAdapter()).getData().get(childAdapterPosition);
String title = subMenuItemI.getTitle();*/
Log.w(TAG, "Decoration SpanIndex:" + spanIndex + ",groupindex:" + spanGroupIndex + ",spanSize:" + spanSize + ",model:" + "title" + ",adapterposition:" + childAdapterPosition);
}
最终源码
public class MenuDecoration extends SpacesItemDecoration {
private static final int NOT_FOUND_SPAN_SIZE = -1;
private static int MENU_TITLE_SPAN_SIZE = 3;
private final int shadowSize=2;
private float radius = 30;
private int color = Color.RED;
private boolean needTop;
public static final int TYPE_GROUP_DATA = 1;
public static final int TYPE_MENU_GROUP_NAME = 0;
/**
* @param recyclerView
* @param spanCount
* @param needLeft
* @param needRight
* @param isneddTop
*/
public MenuDecoration(RecyclerView recyclerView, int spanCount, boolean needLeft, boolean needRight, boolean isneddTop, int space, float radius, int color) {
super((int) (space));
this.radius = radius;
this.color = color;
GridLayoutManager layoutManager = new GridLayoutManager(recyclerView.getContext(), spanCount, LinearLayoutManager.VERTICAL, false);
recyclerView.setLayoutManager(layoutManager);
layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
int type = recyclerView.getAdapter().getItemViewType(position);
if (position >= recyclerView.getAdapter().getItemCount()) {
return NOT_FOUND_SPAN_SIZE;
} else if (type == TYPE_MENU_GROUP_NAME) {
return MENU_TITLE_SPAN_SIZE;
} else if (type == TYPE_GROUP_DATA) {
return 1;
} else {
return NOT_FOUND_SPAN_SIZE;//这是 decoration调用的。
}
}
@Override
public int getSpanIndex(int position, int spanCount) {
int spanIndex = super.getSpanIndex(position, spanCount);
Log.w(TAG, "spanIndex" + spanIndex + ",pos" + position + ",spanCount:" + spanCount);
return spanIndex;
}
@Override
public int getSpanGroupIndex(int adapterPosition, int spanCount) {
int spanGroupIndex = super.getSpanGroupIndex(adapterPosition, spanCount);
Log.w(TAG, "spanGroupIndex" + spanGroupIndex + ",adapterPosition" + adapterPosition + ",spanCount:" + spanCount);
return spanGroupIndex;
}
});
/**
* 顶部 下面实现了
*/
this.needTop = isneddTop;
MENU_TITLE_SPAN_SIZE = spanCount;
recyclerView.setPadding(needLeft ? getSpace() : 0, isneddTop ? getSpace() / 2 : 0, needRight ? getSpace() : 0, getSpace() / 2);
}
private static final String TAG = "Gridlayout";
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
// int itemCount = parent.getLayoutManager().getItemCount();
int childAdapterPosition = parent.getChildAdapterPosition(view);
GridLayoutManager layoutManager = (GridLayoutManager) parent.getLayoutManager();
int spanCount = layoutManager.getSpanCount(); //构造时传递的参数.
//DefaultSpanSizeLookup
int spanSize = layoutManager.getSpanSizeLookup().getSpanSize(childAdapterPosition);
int spanGroupIndex = layoutManager.getSpanSizeLookup().getSpanGroupIndex(childAdapterPosition, spanCount);
if (spanSize == MENU_TITLE_SPAN_SIZE) { //标题才跨 3个。
// if (needTop) {
if (spanGroupIndex == 0) {
outRect.top = (int) (getSpace() + radius);
} else {
outRect.top = (int) (getSpace() + (radius*2));
}
// }
} else {
outRect.top = 0;
outRect.left = 0;
outRect.right = 0;
if( spanGroupIndex==0){//没有头部 的顶
outRect.top = (int) (getSpace() + radius);
}else{
outRect.top = 0;
}
int spanIndex = layoutManager.getSpanSizeLookup().getSpanIndex(childAdapterPosition, spanCount); //比如第一个是标题 ,第二列是 3格子,标题的永远是0 ,分3列的则是0 12 这样的循环。groupindex就是 每一列的完毕 就算一组。
int itemCount = parent.getLayoutManager().getItemCount();
int lastspanIndex = layoutManager.getSpanSizeLookup().getSpanIndex(itemCount-1, spanCount); //比如第一个是标题 ,第二列是 3格子,标题的永远是0 ,分3列的则是0 12 这样的循环。groupindex就是 每一列的完毕 就算一组。
if (childAdapterPosition>=itemCount-(lastspanIndex+1)) {//最后一行
outRect.bottom= (int) (getSpace()+(radius/2));
if(lastspanIndex==spanIndex&&(1+spanIndex!=MENU_TITLE_SPAN_SIZE)){
// outRect.right=30;//=parent.getRight()-parent.getPaddingRight();//写不写其实都一样,因为不绘制右边?不,应该说都铺满屏幕了,不怕控件没给够
}//不能加right,会影响空间
}
}
if (BuildConfig.DEBUG) {
// int spanGroupIndex =;//1 23格子 或者 1列 都算一组,
int spanIndex = layoutManager.getSpanSizeLookup().getSpanIndex(childAdapterPosition, spanCount); //比如第一个是标题 ,第二列是 3格子,标题的永远是0 ,分3列的则是0 12 这样的循环。groupindex就是 每一列的完毕 就算一组。
/* SubMenuItemI subMenuItemI = ((MyAppListAdapter) parent.getAdapter()).getData().get(childAdapterPosition);
String title = subMenuItemI.getTitle();*/
Log.w(TAG, "Decoration SpanIndex:" + spanIndex + ",groupindex:" + spanGroupIndex + ",spanSize:" + spanSize + ",model:" + "title" + ",adapterposition:" + childAdapterPosition);
}
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
// public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
doDraw(c, parent);
}
private void doDraw(Canvas c, RecyclerView parent) {
final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();
final int childCount = parent.getChildCount();
Drawable drawable;
for (int ix = 0; ix < childCount; ix++) {
final View child = parent.getChildAt(ix);
/*
if (true) {
debugDrwa(c, left, right, child);
continue;
}*/
int position = parent.getChildAdapterPosition(child);
// parent.getChildLayoutPosition()
GridLayoutManager layoutManager = (GridLayoutManager) parent.getLayoutManager();
int spanCount = layoutManager.getSpanCount();//表示整个列表分多少列
int spanSize = layoutManager.getSpanSizeLookup().getSpanSize(position);//表示当前格所占有的格子数
int spanIndex = layoutManager.getSpanSizeLookup().getSpanIndex(position, spanCount); //这里参数是spanCount,不是spanSize,否则不对,比如第一个是标题 ,第二列是 3格子,标题的永远是0 ,分3列的则是0 12 这样的循环。groupindex就是 每一列的完毕 就算一组。
if (BuildConfig.DEBUG) {
// MyAppListAdapter adapter = (MyAppListAdapter) parent.getAdapter();
// SubMenuItemI subMenuItemI = adapter.getData().get(position);
// int spanSizeNext = layoutManager.getSpanSizeLookup().getSpanSize(position + 1);
}
if (spanSize != MENU_TITLE_SPAN_SIZE) {//普通item只会跨1个。 除非是菜单
int spanGroupIndex = layoutManager.getSpanSizeLookup().getSpanGroupIndex(position, spanCount);
int spanSizeNext = layoutManager.getSpanSizeLookup().getSpanSize(position + 1);
if (spanIndex < MENU_TITLE_SPAN_SIZE - 1) {// MENU_TITLE_SPAN_SIZE 等于new GridManager里面的Count,这里用合并的数来判断也可以做,是否属于最后一个格子,但是最后一个格子又是否铺满整个屏幕是最后一个但是没有刚好没铺满,就把它给铺满。
if (spanSizeNext == NOT_FOUND_SPAN_SIZE || spanSizeNext == MENU_TITLE_SPAN_SIZE) {//如果下一个不存在那么是最后一个,或者下一个是标题了。
drawable = generateDrawable(-1);//补空缺
boundAndDraw(c, child.getRight(), right, drawable, child.getTop(), child.getBottom());//这里没有任何圆角
if (BuildConfig.DEBUG) {
/* MyAppListAdapter adapter = (MyAppListAdapter) parent.getAdapter();
SubMenuItemI subMenuItemI = adapter.getData().get(position);
Log.w(TAG, subMenuItemI + ",为最后一个前后面超出!spanIndex:" + spanIndex + ",spanSize:" + spanSize + ",height:" + child.getHeight());
*/
}
}
}
if (spanSizeNext == NOT_FOUND_SPAN_SIZE || spanSizeNext == MENU_TITLE_SPAN_SIZE) {// 刚好铺满的那种 铺满还需要判断是否下一行是否是标题.
//左下角圆角.
drawable = generateDrawable(5);//,spanSizeNext == NOT_FOUND_SPAN_SIZE ?Color.GREEN:Color.YELLOW);
int top = child.getTop() + child.getHeight();
int bottom = (int) (top + radius);
// Log.w(TAG,"LEFT:"+left+",top:"+top+",right:"+right+",bottom:"+bottom);
boundAndDraw(c, left, right, drawable, top, bottom);
}
if( spanGroupIndex==0&&position==0){//没有头部 的顶
drawable = generateDrawable(4);
int top = (int) (child.getTop() - radius);
int bottom = child.getTop();
boundAndDraw(c, left, right, drawable, top, bottom);
}
} else {//标题头
drawable = generateDrawable(4);
int top = (int) (child.getTop() - radius);
int bottom = child.getTop();
boundAndDraw(c, left, right, drawable, top, bottom);
}
}
}
private void debugDrwa(Canvas c, int left, int right, View child) {
//以下计算主要用来确定绘制的位置
final int top1 = child.getBottom() + 1;
ColorDrawable drawable1 = new ColorDrawable(Color.BLUE);
final int bottom = top1 + drawable1.getIntrinsicHeight();
boundAndDraw(c, left, right, drawable1, top1, bottom);
}
private void boundAndDraw(Canvas c, int left, int right, Drawable drawable, int top, int bottom) {
// Log.w(TAG,"DrawCall LEFT:"+left+",righ:"+right+",top:"+top+",bottom:"+bottom);
drawable.setBounds(left, top, right, bottom);
// drawable.setBounds(left, (int) (child.getTop()+child.getHeight()-radius), left + child.getWidth(), child.getBottom());
drawable.draw(c);
}
/**
* /外矩形 0左上、1右上、2 右下、3左下的圆角半 4 上 左和右边 5 ,下 左右。
*
* @param type
* @return
*/
private Drawable generateDrawable(int type, int color) {
this.color = color;
return generateDrawable(type);
}
private Drawable generateDrawable(int type) {
GradientDrawable gd = new GradientDrawable();
// color=color|0xff000000;
gd.setColor(color);
switch (type) {
case 0:
gd.setCornerRadii(new float[]{radius, radius, 0, 0, 0, 0, 0, 0});
break;
case 1:
gd.setCornerRadii(new float[]{0, 0, radius, radius, 0, 0, 0, 0});
break;
case 2:
gd.setCornerRadii(new float[]{0, 0, 0, 0, radius, radius, 0, 0});
break;
case 3:
gd.setCornerRadii(new float[]{0, 0, 0, 0, 0, 0, radius, radius});
break;
case 4:
gd.setCornerRadii(new float[]{radius, radius, radius, radius, 0, 0, 0, 0});
break;
case 5:
gd.setCornerRadii(new float[]{0, 0, 0, 0, radius, radius, radius, radius});
break;
case 6:
gd.setCornerRadii(new float[]{radius, radius, radius, radius, radius, radius, radius, radius});
break;
default:
gd.setCornerRadii(new float[]{0, 0, 0, 0, 0, 0, 0, 0});
break;
}
/*
Drawable[] drawables=new Drawable[2];
GradientDrawable gdshadow = new GradientDrawable();
//#ffdb8f #ffdb8f
gdshadow.setColor(Color.parseColor("#00ff00"));
drawables[0]=gdshadow;
drawables[1]=gd;
LayerDrawable layerDrawable=new LayerDrawable(drawables);
*/
return gd;
}