- 对于RecyclerView的使用想必大家都很熟悉,下面先看两张LinearLayoutManager和GridLayoutManager的示例
- 上面展示的是没有分割线的示例,一般情况下我们是通过继承RecyclerView.ItemDecoration类并重写 getItemOffsets 与 onDraw 来实现自定义分割线。
代码:
public class CommonDecoration extends RecyclerView.ItemDecoration {
public CommonDecoration() {
}
/**
* @param outRect 用于规定分割线的范围
* @param view 进行分割线操作的子view
* @param parent 父view
* @param state (这里暂时不使用)
*/
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
}
}
SquareTextView.java
public class SquareTextView extends AppCompatTextView {
public SquareTextView(Context context) {
super(context);
}
public SquareTextView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public SquareTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int size = MeasureSpec.getSize(widthMeasureSpec);
setMeasuredDimension(size, size);
}
}
item_grid.xml
<?xml version="1.0" encoding="utf-8"?>
<com.zzz.recyclerviewdemo.view.SquareTextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/tv_letter"
android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/holo_red_light"
android:gravity="center"
android:text="A"
android:textColor="@android:color/white"
android:textSize="20dp" />
item_linear.xml
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/tv_letter"
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="@android:color/holo_red_light"
android:gravity="center"
android:text="A"
android:textColor="@android:color/white"
android:textSize="20dp" />
分割线使用:
mRecyclerView.addItemDecoration(new CommonDecoration(this, R.drawable.drawable_item_decoration));
LinearLayoutManager实现分割线
drawable_itemdecoration.xml:
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<!--用于设置分割线的宽-->
<size android:width="5dp" />
<solid android:color="@android:color/holo_orange_dark" />
</shape>
代码:
public class CommonDecoration extends RecyclerView.ItemDecoration {
private Context mContext;
private Drawable mDrawable;
public CommonDecoration(Context context, int drawableId) {
this.mContext = context;
this.mDrawable = ContextCompat.getDrawable(this.mContext, drawableId);
}
/**
* @param outRect 用于规定分割线的范围
* @param view 进行分割线操作的子view
* @param parent 父view
* @param state (这里暂时不使用)
*/
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
outRect.bottom = mDrawable.getIntrinsicHeight();
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
for (int i = 0; i < parent.getChildCount(); i++) {
drawHorizontalDecoration(c, parent.getChildAt(i));
}
}
private void drawHorizontalDecoration(Canvas canvas, View childView) {
Rect rect = new Rect(0, 0, 0, 0);
rect.top = childView.getBottom();
rect.bottom = rect.top + mDrawable.getIntrinsicHeight();
rect.left = childView.getLeft();
rect.right = childView.getRight();
mDrawable.setBounds(rect);
mDrawable.draw(canvas);
}
}
- 简单的分割线的截图
问题:很明显可以看出在最后一个Item("Z")也出现了分割线,即对于显示LinearLayoutManager的最后一个子view的时候不应该存在分割线。
解决:在getItemOffsets方法中进行判断是否为最后一行,使用(currentItemPosition + 1) 与 totalItems比较(currentItemPosition从0开始计数)。如果是最后一行就不给其留出空间,即让outRect.bottom = 0。
代码:
public class CommonDecoration extends RecyclerView.ItemDecoration {
private Context mContext;
private Drawable mDrawable;
//
private int mTotalItems;//总Item数
public CommonDecoration(Context context, int drawableId) {
this.mContext = context;
this.mDrawable = ContextCompat.getDrawable(this.mContext, drawableId);
}
/**
* @param outRect 用于规定分割线的范围
* @param view 进行分割线操作的子view
* @param parent 父view
* @param state (这里暂时不使用)
*/
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
if (0 == mTotalItems)
mTotalItems = parent.getAdapter().getItemCount();
//在源码中有一个过时的方法,里面有获取当前ItemPosition
int currentItemPosition = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition();
if (!isLastRow(currentItemPosition, mTotalItems))
outRect.bottom = mDrawable.getIntrinsicWidth();
else
outRect.bottom = 0;
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
for (int i = 0; i < parent.getChildCount(); i++) {
drawHorizontalDecoration(c, parent.getChildAt(i));
}
}
private void drawHorizontalDecoration(Canvas canvas, View childView) {
Rect rect = new Rect(0, 0, 0, 0);
rect.top = childView.getBottom();
rect.bottom = rect.top + mDrawable.getIntrinsicWidth();
rect.left = childView.getLeft();
rect.right = childView.getRight();
mDrawable.setBounds(rect);
mDrawable.draw(canvas);
}
private boolean isLastRow(int currentItemPosition, int totalItems) {
boolean result = false;
if (currentItemPosition + 1 >= totalItems)
result = true;
return result;
}
}
- 解决问题后的截图
问题:对比两张图可以看出如果子view的数量充满整个屏幕的话是可以不显示分割线的,但是如果子view的数量无法充满整个屏幕的话,分割线又出现了。
解决:上一步我们只更改了getItemOffsets方法中如果是最后一行不给出画分割线的空间,但是如果子view的数量不够的话还是存在画分割线的空间。所以我们还要修改drawHorizontalDecoration方法 ,在该方法中同样判断是否为最后一行。
代码:
private void drawHorizontalDecoration(Canvas canvas, View childView) {
int currentItemPosition = ((RecyclerView.LayoutParams) childView.getLayoutParams()).getViewLayoutPosition();
if (isLastRow(currentItemPosition, mTotalItems)) {
return;
}
//
Rect rect = new Rect(0, 0, 0, 0);
rect.top = childView.getBottom();
rect.bottom = rect.top + mDrawable.getIntrinsicWidth();
rect.left = childView.getLeft();
rect.right = childView.getRight();
mDrawable.setBounds(rect);
mDrawable.draw(canvas);
}
- 解决问题后的截图
GridLayoutManager实现分割线
- 使用LinearLayoutManager的Decoration截图
问题:可以看出使用了LinearLayoutManager的话只有view的下部分存在分割线,而且它最后一行存在一些问题,我们只去掉了最后一个子view的分割线,而不是一行的分割线。
解决:
- 最后一行分割线的问题,所以需要更改 isLastRow 方法。从下示例图中可以看出最后一行跟RecyclerView的列数有关。所以只要判断(currentItemPosition + 1) > ((行数 - 1) x 列数)即可。这样也不会对LinearLayoutManger产生影响。
- 因为除了View在下部要有分割线,在子view右部也要有,所以必须设置outRect.right,为右部留出画分割线的空间。
代码:
public class CommonDecoration extends RecyclerView.ItemDecoration {
private Context mContext;
private Drawable mDrawable;
//
private int mTotalItems;//总Item数
private int mSpanCount;//总列数
public CommonDecoration(Context context, int drawableId) {
this.mContext = context;
this.mDrawable = ContextCompat.getDrawable(this.mContext, drawableId);
}
/**
* @param outRect 用于规定分割线的范围
* @param view 进行分割线操作的子view
* @param parent 父view
* @param state (这里暂时不使用)
*/
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
if (0 == mTotalItems)
mTotalItems = parent.getAdapter().getItemCount();
if (0 == mSpanCount) {
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
//判断是否为GridLayoutManager
if (layoutManager instanceof GridLayoutManager) {
GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
mSpanCount = gridLayoutManager.getSpanCount();
} else {
mSpanCount = 1;
}
}
//在源码中有一个过时的方法,里面有获取当前ItemPosition
int currentItemPosition = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition();
//
if (!isLastRow(currentItemPosition, mTotalItems, mSpanCount))
outRect.bottom = mDrawable.getIntrinsicWidth();
else
outRect.bottom = 0;
//
outRect.right = mDrawable.getIntrinsicWidth();
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
for (int i = 0; i < parent.getChildCount(); i++) {
drawHorizontalDecoration(c, parent.getChildAt(i));
drawVerticalDecoration(c, parent.getChildAt(i));
}
}
private void drawHorizontalDecoration(Canvas canvas, View childView) {
int currentItemPosition = ((RecyclerView.LayoutParams) childView.getLayoutParams()).getViewLayoutPosition();
if (isLastRow(currentItemPosition, mTotalItems, mSpanCount)) {
return;
}
//
Rect rect = new Rect(0, 0, 0, 0);
rect.top = childView.getBottom();
rect.bottom = rect.top + mDrawable.getIntrinsicWidth();
rect.left = childView.getLeft();
rect.right = childView.getRight();
mDrawable.setBounds(rect);
mDrawable.draw(canvas);
}
private void drawVerticalDecoration(Canvas canvas, View childView) {
Rect rect = new Rect(0, 0, 0, 0);
rect.top = childView.getTop();
rect.bottom = childView.getBottom();
rect.left = childView.getRight();
rect.right = rect.left + mDrawable.getIntrinsicWidth();
mDrawable.setBounds(rect);
mDrawable.draw(canvas);
}
private boolean isLastRow(int currentItemPosition, int totalItems, int spanCount) {
boolean result = false;
int rowCount = 0;
if (0 == totalItems % spanCount) {
rowCount = totalItems / spanCount;
} else {
rowCount = totalItems / spanCount + 1;
}
if ((currentItemPosition + 1) > (rowCount - 1) * spanCount)
result = true;
return result;
}
}
- 解决问题后截图
问题: 从图中可以看出在水平与垂直方向相接处的分割线没有颜色填充;并且最后一列的的子view不应该有垂直方向的分割线。
解决:
- 在 drawHorizontalDecoration 方法中的 rect.right 添加上分割线的宽度。
- 添加一个方法判断是否为最后一列使用(currentItemPosition + 1) % 3是否等于0来判断,如果是最后一列的话,则不留出画垂直方向分割线的空间。
代码:
public class CommonDecoration extends RecyclerView.ItemDecoration {
private Context mContext;
private Drawable mDrawable;
//
private int mTotalItems;//总Item数
private int mSpanCount;//总列数
public CommonDecoration(Context context, int drawableId) {
this.mContext = context;
this.mDrawable = ContextCompat.getDrawable(this.mContext, drawableId);
}
/**
* @param outRect 用于规定分割线的范围
* @param view 进行分割线操作的子view
* @param parent 父view
* @param state (这里暂时不使用)
*/
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
if (0 == mTotalItems)
mTotalItems = parent.getAdapter().getItemCount();
if (0 == mSpanCount) {
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
//判断是否为GridLayoutManager
if (layoutManager instanceof GridLayoutManager) {
GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
mSpanCount = gridLayoutManager.getSpanCount();
} else {
mSpanCount = 1;
}
}
//在源码中有一个过时的方法,里面有获取当前ItemPosition
int currentItemPosition = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition();
//
if (!isLastRow(currentItemPosition, mTotalItems, mSpanCount))
outRect.bottom = mDrawable.getIntrinsicWidth();
else
outRect.bottom = 0;
//
if (!isLastColumn(currentItemPosition, mSpanCount)) {
outRect.right = mDrawable.getIntrinsicWidth();
} else {
outRect.right = 0;
}
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
for (int i = 0; i < parent.getChildCount(); i++) {
drawHorizontalDecoration(c, parent.getChildAt(i));
drawVerticalDecoration(c, parent.getChildAt(i));
}
}
private void drawHorizontalDecoration(Canvas canvas, View childView) {
int currentItemPosition = ((RecyclerView.LayoutParams) childView.getLayoutParams()).getViewLayoutPosition();
if (isLastRow(currentItemPosition, mTotalItems, mSpanCount)) {
return;
}
//
Rect rect = new Rect(0, 0, 0, 0);
rect.top = childView.getBottom();
rect.bottom = rect.top + mDrawable.getIntrinsicWidth();
rect.left = childView.getLeft();
rect.right = childView.getRight() + mDrawable.getIntrinsicWidth();
mDrawable.setBounds(rect);
mDrawable.draw(canvas);
}
private void drawVerticalDecoration(Canvas canvas, View childView) {
Rect rect = new Rect(0, 0, 0, 0);
rect.top = childView.getTop();
rect.bottom = childView.getBottom();
rect.left = childView.getRight();
rect.right = rect.left + mDrawable.getIntrinsicWidth();
mDrawable.setBounds(rect);
mDrawable.draw(canvas);
}
private boolean isLastRow(int currentItemPosition, int totalItems, int spanCount) {
boolean result = false;
int rowCount = 0;
if (0 == totalItems % spanCount) {
rowCount = totalItems / spanCount;
} else {
rowCount = totalItems / spanCount + 1;
}
if ((currentItemPosition + 1) > (rowCount - 1) * spanCount)
result = true;
return result;
}
private boolean isLastColumn(int currentItemPosition, int spanCount) {
boolean result = false;
if (0 == (currentItemPosition + 1) % spanCount)
result = true;
return result;
}
}
解决问题后截图:
下一次将会从源码的角度 ,进一步分析当前存在的BUG。
RecyclerView添加自定义ItemDecoration实现(2)