转载请标明出处:http://blog.youkuaiyun.com/zhaoyanjun6/article/details/111302734
本文出自【赵彦军的博客】
文章目录
如何设置分割线
RecycleView
没有像ListView
一样可以直接在 xml 中或者通过 setDivider()方法设置分割线的方法。它是通过 RecycleView
的 addItemDecoration(ItemDecoration decor)
方法来设置的。很显然,我们需要传入一个 ItemDecoration 对象,这个对象是一个抽象类,官方已经提供了一种常用分割线类:DividerItemDecoration
。来看一下用法:
recyclerView.addItemDecoration(itemDecoration)
public void addItemDecoration(@NonNull ItemDecoration decor) {
addItemDecoration(decor, -1);
}
ItemDecoration 是一个抽象类
public abstract static class ItemDecoration {
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull State state) {
onDraw(c, parent);
}
@Deprecated
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent) {
}
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent,
@NonNull State state) {
onDrawOver(c, parent);
}
@Deprecated
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent) {
}
@Deprecated
public void getItemOffsets(@NonNull Rect outRect, int itemPosition,
@NonNull RecyclerView parent) {
outRect.set(0, 0, 0, 0);
}
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view,
@NonNull RecyclerView parent, @NonNull State state) {
getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),
parent);
}
}
从源码的注释文档可以看出:
- ItemDecoration 可以为 item 添加绘图,还可以设置偏移量
- ItemDecoration 可以用于实现 item 之间的分隔线、高亮显示、可视分组等功能
- ItemDecoration 中的 onDraw() 方法先于 item 绘制,onDrawOver(Canvas, RecyclerView, RecyclerView.State方法执行顺序在 item 的绘制之后
DividerItemDecoration使用
DividerItemDecoration 是 Google 官方实现的分割线实现类,DividerItemDecoration 继承 ItemDecoration
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
...
}
recyclerView如何使用
recyclerView.adapter = UserAdapter(getData())
recyclerView.layoutManager = LinearLayoutManager(this)
var dividerItemDecoration = DividerItemDecoration(this,DividerItemDecoration.VERTICAL)
//设置一个分割线
recyclerView.addItemDecoration(dividerItemDecoration)
代码运行起来,来看看效果:
recyclerView 已经显示了一条细细的分割线,美观度还算可以。
通过源码打断点调试,默认的分割线高度是 3 pix .
如果我们要改变分割线的颜色,该怎么做呢?
答案其实还挺简单,dividerItemDecoration
需要设置一个 drawable
, 这个 drawable
就是 分割线。
在 drawable
文件夹里面新建 list_item_bg
文件
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<!-- 自定义分割线高度 -->
<size android:height="5dp" />
<!-- 自定义分割线圆角 -->
<corners android:radius="1dp" />
<!-- 自定义分割线渐变 -->
<gradient
android:angle="0"
android:endColor="#FFDC9E "
android:startColor="#D9983B"
android:type="linear" />
</shape>
修复后的代码如下:
recyclerView.adapter = UserAdapter(getData())
recyclerView.layoutManager = LinearLayoutManager(this)
var dividerItemDecoration = DividerItemDecoration(this,DividerItemDecoration.VERTICAL)
//设置一个分割线背景
dividerItemDecoration.setDrawable(resources.getDrawable(R.drawable.list_item_bg))
//添加一个分割线
recyclerView.addItemDecoration(dividerItemDecoration)
我们把代码运行起来,看看效果:
自定义的分割线美观度还可以,但是我们发现一个问题,recyclerView 的最后一行底部也有一个分割线,这就不太美观了,效果如下:
问题又来了,怎么去掉最后一行的分割线?答案是做不到,系统提供的 DividerItemDecoration 类没有相关api。只能我们自定义 ItemDecoration 。
自定义 ItemDecoration getItemOffsets
第一步,重写 getItemOffsets
方法,如下
package com.cootek.recyclerviewdemo;
import android.graphics.Rect;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
/**
* @author yanjun.zhao
* @time 2020/12/21 2:57 PM
* @desc
*/
class MyListItemDecoration extends RecyclerView.ItemDecoration {
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
}
}
getItemOffsets
可以通过outRect.set(l,t,r,b)
设置指定itemview
的偏移量。
接下来,我们实验一下,把每一个 item 底部都留 10 px 的位置。
/**
* @author yanjun.zhao
* @time 2020/12/21 2:57 PM
* @desc
*/
class MyListItemDecoration extends RecyclerView.ItemDecoration {
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
outRect.bottom = 10 ; //底部偏移10px
}
}
使用如下:
//添加自定义 ItemDecoration
var dividerItemDecoration = MyListItemDecoration()
recyclerView.addItemDecoration(dividerItemDecoration)
recyclerView.adapter = UserAdapter(getData())
recyclerView.layoutManager = LinearLayoutManager(this)
运行起来,效果如下:
如果我们把右边也设置一个偏移,代码如下:
/**
* @author yanjun.zhao
* @time 2020/12/21 2:57 PM
* @desc 自定义 ItemDecoration
*/
class MyListItemDecoration extends RecyclerView.ItemDecoration {
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
outRect.bottom = 10 ; //底部偏移
outRect.right = 10; //右边偏移
}
}
效果如下:
可以看到 recyclerView 的每一个item 底部、右边都有一个偏移。
如果最后一个 item 不绘制偏移量,怎么处理:
/**
* @author yanjun.zhao
* @time 2020/12/21 2:57 PM
* @desc 自定义 ItemDecoration
*/
class MyListItemDecoration extends RecyclerView.ItemDecoration {
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
int childPosition = parent.getChildAdapterPosition(view);
int itemCount = parent.getAdapter().getItemCount();
//最后一个item 不绘制
if (childPosition != itemCount - 1) {
outRect.bottom = 10; //底部偏移
outRect.right = 10; //右边偏移
}
}
}
效果如下:
自定义 ItemDecoration onDraw onDrawOver
在官方的开发文档中有指出,onDraw
是在itemview
绘制之前,onDrawOver
是在 itemview
绘制之后。
可以看看下面的部分源码:
/**
* RecyclerView的draw方法
* @param c
*/
@Override
public void draw(Canvas c) {
// 调用父类也就是View的draw方法
super.draw(c);
final int count = mItemDecorations.size();
for (int i = 0; i < count; i++) {
// 执行ItemDecorations的onDrawOver方法
mItemDecorations.get(i).onDrawOver(c, this, mState);
}
}
/**
* View的draw方法
* @param canvas
*/
@CallSuper
public void draw(Canvas canvas) {
....
// View会继续调用onDraw
if (!dirtyOpaque) onDraw(canvas);
....
}
/**
* RecyclerView的onDraw方法
* @param c
*/
@Override
public void onDraw(Canvas c) {
super.onDraw(c);
final int count = mItemDecorations.size();
for (int i = 0; i < count; i++) {
// 执行ItemDecorations的onDraw方法
mItemDecorations.get(i).onDraw(c, this, mState);
}
}
实战1:使用 onDrawOver() 方法在 item 的上画一个半透明的蒙版
效果如下:
颜色值:
<resources>
<!--半透明-->
<color name="item_bg">#80000000</color>
</resources>
MyListItemDecoration 源码如下:
package com.cootek.recyclerviewdemo;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.Log;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
/**
* @author yanjun.zhao
* @time 2020/12/21 2:57 PM
* @desc 自定义 ItemDecoration
*/
class MyListItemDecoration extends RecyclerView.ItemDecoration {
private Paint mPaint; //分割线画笔
MyListItemDecoration() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
}
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
outRect.bottom = 20; //底部偏移量
outRect.right = 10; //右边偏移量
outRect.left = 10; //左边偏移量
}
@Override
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.onDrawOver(c, parent, state);
//设置半透明颜色
mPaint.setColor(parent.getResources().getColor(R.color.item_bg));
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
if (i % 2 == 0) {
continue;
}
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int top = child.getTop();
final int bottom = top + params.height;
final int left = child.getLeft();
final int right = child.getWidth() + left;
//在recyclerView item 上画一个半透明的蒙版
c.drawRect(left, top, right, bottom, mPaint);
}
}
}
使用如下:
var dividerItemDecoration = MyListItemDecoration()
recyclerView.addItemDecoration(dividerItemDecoration)
recyclerView.adapter = UserAdapter(getData())
recyclerView.layoutManager = GridLayoutManager(this, 1)
//两列
findViewById<Button>(R.id.bt1).setOnClickListener {
recyclerView.layoutManager = GridLayoutManager(this, 2)
}
//三列
findViewById<Button>(R.id.bt2).setOnClickListener {
recyclerView.layoutManager = GridLayoutManager(this, 3)
}
//四列
findViewById<Button>(R.id.bt3).setOnClickListener {
recyclerView.layoutManager = GridLayoutManager(this, 4)
}
实战2:自定义分割线颜色
废话不多说,线上效果图:
package com.cootek.recyclerviewdemo;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
/**
* @author yanjun.zhao
* @time 2020/12/21 2:57 PM
* @desc 自定义 ItemDecoration
*/
class MyListItemDecoration extends RecyclerView.ItemDecoration {
private Paint mPaint; //分割线画笔
private int lingHeight = 0; //分割线高度
MyListItemDecoration(Context context) {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(Color.BLACK); //设置分割线颜色
lingHeight = dpToPx(context, 1);
}
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
outRect.bottom = lingHeight; //底部偏移量
}
@Override
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.onDrawOver(c, parent, state);
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int top = child.getBottom() + params.bottomMargin;
final int bottom = top + lingHeight;
final int left = child.getLeft();
final int right = child.getWidth() + left;
c.drawRect(left, top, right, bottom, mPaint);
}
}
int dpToPx(Context context, int dps) {
return Math.round(context.getResources().getDisplayMetrics().density * dps);
}
}
实战3:自定义分割线左侧偏移量
先看一个 iphone 设置页面,请认真观察分割线,如下图:
我们注意到一个细节,分割线的宽度不是充满屏幕的,而是在左边有一个偏移量,这种效果怎么做呢?
代码如下:
package com.cootek.recyclerviewdemo;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
/**
* @author yanjun.zhao
* @time 2020/12/21 2:57 PM
* @desc 自定义 ItemDecoration
*/
class MyListItemDecoration extends RecyclerView.ItemDecoration {
private Paint mPaint; //分割线画笔
private int lingHeight = 0; //分割线高度
private int lineMarginLeft = 0;
MyListItemDecoration(Context context) {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(Color.BLACK); //设置分割线颜色
lingHeight = dpToPx(context, 1);
lineMarginLeft = dpToPx(context, 20); //设置左侧偏移量
}
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
outRect.bottom = lingHeight; //底部偏移量
}
@Override
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.onDrawOver(c, parent, state);
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int top = child.getBottom() + params.bottomMargin;
final int bottom = top + lingHeight;
final int left = child.getLeft() + lineMarginLeft;
final int right = child.getWidth() + left - lineMarginLeft;
c.drawRect(left, top, right, bottom, mPaint);
}
}
int dpToPx(Context context, int dps) {
return Math.round(context.getResources().getDisplayMetrics().density * dps);
}
}
运行效果图如下: