前些天利用周末时间捣鼓了一下Recyclerview,其实虽然现在RecyclerView出来了这么久,之所以得到我们的偏爱是因为它的高度解耦和高度可自定义非常灵活。我相信很多人在使用RecyclerView的时候曾经也为分割线头疼过,有人可能使用很简单的方法就是在每一个item里面加一个view设置背景最后在adapter里面处理最后分割线。但是久而久之你会发现你在项目中使用RecyclerView的频率非常高,不停的重写这样的代码难道你不觉得累么?说实话我就是觉得繁琐和重复无用代码所以想造轮子,当初真是很傻很天真啊!好啦,该进入正题了
首先在gradle里面添加依赖如下:
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
testCompile 'junit:junit:4.12'
compile 'com.android.support:design:25.0.0'
compile 'com.android.support:recyclerview-v7:25.0.0'
compile 'com.android.support:appcompat-v7:25.0.0'
}
然后在绑定一些简单的数据如图
对于RecyclerView的layoutManager你了解多少?水平、垂直、网格都少不了它,去看看源码你就会发现新大陆的
if (isVertical) {//水平布局
isVertical = false;
addItemDecoration = new DividerItemDecoration(MainActivity.this, LinearLayoutManager.HORIZONTAL);
recyclerView.setLayoutManager(new LinearLayoutManager(MainActivity.this, LinearLayoutManager.HORIZONTAL, false));
recyclerView.addItemDecoration(addItemDecoration);
} else {//竖直布局
isVertical = true;
addItemDecoration = new DividerItemDecoration(MainActivity.this, LinearLayoutManager.VERTICAL);
recyclerView.setLayoutManager(new LinearLayoutManager(MainActivity.this));
recyclerView.addItemDecoration(addItemDecoration);
}
if (isGrid) {//流式布局
isGrid = false;
recyclerView.setAdapter(new MyStaggedRecyclerAdapter(infoBeans));
recyclerView.setLayoutManager(new StaggeredGridLayoutManager(3,LinearLayoutManager.VERTICAL));
} else {//网格布局
isGrid = true;
dividerGridViewItemDecoration = new DividerGridViewItemDecoration(MainActivity.this);
recyclerView.setLayoutManager(new GridLayoutManager(MainActivity.this,3));
recyclerView.setAdapter(new MyRecyclerAdapter(infoBeans));
recyclerView.addItemDecoration(dividerGridViewItemDecoration);
}
}
这些LayoutManager都是提供不需要自己手动写,下面进入今天主题分割线了。首先,我们可以思考RecyclerView它到底是怎么弄出的分割线呢?
我们进入ItemDecoration就能发现它是RecyclerView的一个抽象内部类还有onDraw方法
/**
* Draw any appropriate decorations into the Canvas supplied to the RecyclerView.
* Any content drawn by this method will be drawn before the item views are drawn,
* and will thus appear underneath the views.
*
* @param c Canvas to draw into
* @param parent RecyclerView this ItemDecoration is drawing into
* @param state The current state of RecyclerView
*/
public void onDraw(Canvas c, RecyclerView parent, State state) {
onDraw(c, parent);
}
/**
* @deprecated
* Override {@link #onDraw(Canvas, RecyclerView, RecyclerView.State)}
*/
@Deprecated
public void onDraw(Canvas c, RecyclerView parent) {
}
/**
* @deprecated
* Use {@link #getItemOffsets(Rect, View, RecyclerView, State)}
*/
@Deprecated
public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
outRect.set(0, 0, 0, 0);
}
/**
* Retrieve any offsets for the given item. Each field of outRect
specifies
* the number of pixels that the item view should be inset by, similar to padding or margin.
* The default implementation sets the bounds of outRect to 0 and returns.
*
*
* If this ItemDecoration does not affect the positioning of item views, it should set
* all four fields of outRect
(left, top, right, bottom) to zero
* before returning.
*
*
* If you need to access Adapter for additional data, you can call
* {@link RecyclerView#getChildAdapterPosition(View)} to get the adapter position of the
* View.
*
* @param outRect Rect to receive the output.
* @param view The child view to decorate
* @param parent RecyclerView this ItemDecoration is decorating
* @param state The current state of RecyclerView.
*/
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {
getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),
parent);
}
没错通过RecyclerView代码可以看出来分割线的绘制就是通过这两个方法确定绘制区域和执行绘制的,下面我们来自定义一个DividerItemDecoration继承ItemDecoration重写onDraw和getItemOffsets方法
@Override
public void onDraw(Canvas c, RecyclerView parent, State state) {
//RecyclerView会调用该方法绘制分割线
if(mOrientation == LinearLayoutManager.VERTICAL){//竖直
drawVertical(c,parent);
}else{//水平
drawHorizontal(c,parent);
}
super.onDraw(c, parent, state);
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
State state) {
// 调用此方法先获取条目之间的宽度并设置outRect矩形区域
if(mOrientation == LinearLayoutManager.VERTICAL){
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
}else{
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0 );
}
}
private void drawHorizontal(Canvas c, RecyclerView parent) {// 画水平线
int top = parent.getPaddingTop();
int bottom = parent.getHeight() - parent.getPaddingBottom();
int childCount = parent.getChildCount();
for (int i = 0; i < childCount ; i++) {
View child = parent.getChildAt(i);
LayoutParams params = (LayoutParams) child.getLayoutParams();
int left = child.getRight() + params.rightMargin + Math.round(ViewCompat.getTranslationX(child));
int right = left + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top , right, bottom);
mDivider.draw(c);
}
}
private void drawVertical(Canvas c, RecyclerView parent) {// 画水竖直
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
int childCount = parent.getChildCount();
for (int i = 0; i < childCount ; i++) {
View child = parent.getChildAt(i);
LayoutParams params = (LayoutParams) child.getLayoutParams();
int top = child.getBottom() + params.bottomMargin + Math.round(ViewCompat.getTranslationY(child));
int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top , right, bottom);
mDivider.draw(c);
}
}
如图我们会发现一个问题,最后一项分割线还是绘制了,那么需要我们自己处理一下
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
State state) {
// 调用此方法先获取条目之间的宽度并设置outRect矩形区域
// 最后一项不需要分割线所以可以设置偏移量都为0
if (parent.getChildViewHolder(view).getAdapterPosition() == parent.getAdapter().getItemCount() - 1) {
outRect.set(0, 0, 0, 0);
return;
}
if(mOrientation == LinearLayoutManager.VERTICAL){
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
}else{
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0 );
}
}
在getItemOffsets方法中加入判断是不是最后一项即可
那对于gridLayout的话稍微麻烦一点,底部和右边都需要我们自己计算上代码
@Override
@Deprecated
public void getItemOffsets(Rect outRect, int itemPosition,
RecyclerView parent) {
// 四个方向的偏移值
int right = mDivider.getIntrinsicWidth();
int bottom = mDivider.getIntrinsicHeight();
if (isLastRow(itemPosition,parent)) {//最后一行
bottom = 0;
}
if (isLastColum(itemPosition,parent)) {//最后一列
right = 0;
}
outRect.set(0, 0, right, bottom);
}
先计算四个方向的偏移量,判断最后一行和最后一列做处理
//最后一行
public boolean isLastRow(int itemPosition, RecyclerView parent) {
int spanCount = getSpanCount(parent);
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof GridLayoutManager) {
int childCount = parent.getAdapter().getItemCount();
int lastRow = childCount % spanCount;
//最后一行的数量小于spanCount
if (lastRow == 0 || lastRow < spanCount) {
return true;
}
}
return false;
}
//最后一列
public boolean isLastColum(int itemPosition, RecyclerView parent) {
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof GridLayoutManager) {
int spanCount = getSpanCount(parent);
if ((itemPosition + 1) % spanCount == 0) {
return true;
}
}
return false;
}
private int getSpanCount(RecyclerView parent){
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
if(layoutManager instanceof GridLayoutManager){
GridLayoutManager lm = (GridLayoutManager)layoutManager;
int spanCount = lm.getSpanCount();
return spanCount;
}
return 0;
}
处理之后效果如下
最后一个是流式布局很简单了,这里就不说了,有兴趣可以自己玩玩,之后其实我也发现一个问题,就是当你的item数很少的时候即使你的recyclerview是全屏,最后一行也会绘制分割线,有解决方案的可以和我分享一下哦!!!
项目链接 https://github.com/389987790/RecyclerViewItemDecoration