Android-RecyclerViewItemDecoration的进阶使用

  1. ItemDecoration实现padding
  2. ItemDecoration实现下划线
  3. ItemDecoration实现酷炫吸顶效果
  4. ItemDecoration实现item的拖拽,平移等操作

ItemDecoration

ItemDecoration是RecyclerView内部的一个抽象类,要实现这个抽象类自然需要实现内部的抽象方法,除了deprecated的方法只有下面三个方法:

1
2
3
4
5
6
//可以实现类似padding的效果
public void onDraw(Canvas c, RecyclerView parent, State state)
//可以实现类似绘制背景的效果,内容在上面
public void onDrawOver(Canvas c, RecyclerView parent, State state)
//可以绘制在内容的上面,覆盖内容
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state)

声明下:ItemDecoration的使用必须在setAdapter前,通过recyclerView.addItemDecoration()方法设置

下面讲解ItemDecoration实现的功能

ItemDecoration实现padding

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class PaddingDecoration extends RecyclerView.ItemDecoration{
private int padding;
public PaddingDecoration(Context context) {
//即你要设置的分割线的宽度 --这里设为10dp
padding = context.getResources().getDimensionPixelSize(R.dimen.padding);
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
//outRect就是你那个item条目的矩形
outRect.left = padding; //相当于 设置 left padding
outRect.top = padding; //相当于 设置 top padding
outRect.right = padding; //相当于 设置 right padding
outRect.bottom = padding; //相当于 设置 bottom padding
}
}

ItemDecoration实现下划线

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class DeviderDecoration extends RecyclerView.ItemDecoration {
private int deviderHeight;
private Paint dividerPaint;
public DeviderDecoration(Context context) {
//设置画笔
dividerPaint = new Paint();
//设置分割线颜色
dividerPaint.setColor(context.getResources().getColor(R.color.colorAccent));
//设置分割线宽度
deviderHeight = context.getResources().getDimensionPixelSize(R.dimen.divider_height);
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
//改变宽度
outRect.bottom = deviderHeight;
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
//得到列表所有的条目
int childCount = parent.getChildCount();
//得到条目的宽和高
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
for (int i = 0; i < childCount - 1; i++) {
View view = parent.getChildAt(i);
//计算每一个条目的顶点和底部 float值
float top = view.getBottom();
float bottom = view.getBottom() + deviderHeight;
//重新绘制
c.drawRect(left, top, right, bottom, dividerPaint);
}
}
}

ItemDecoration实现酷炫吸顶效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
public class SectionDecoration extends RecyclerView.ItemDecoration {
private List<String> dataList;
private DecorationCallback callback;
private TextPaint textPaint;
private Paint paint;
private int topGap;
private int alignBottom;
private Paint.FontMetrics fontMetrics;
public SectionDecoration(List<String> dataList, Context context, DecorationCallback decorationCallback) {
Resources res = context.getResources();
this.dataList = dataList;
this.callback = decorationCallback;
//设置悬浮栏的画笔---paint
paint = new Paint();
paint.setColor(res.getColor(R.color.colorGray));
//设置悬浮栏中文本的画笔
textPaint = new TextPaint();
textPaint.setAntiAlias(true);
textPaint.setTextSize(DensityUtil.dip2px(context, 14));
textPaint.setColor(Color.DKGRAY);
textPaint.setTextAlign(Paint.Align.LEFT);
fontMetrics = new Paint.FontMetrics();
//决定悬浮栏的高度等
topGap = res.getDimensionPixelSize(R.dimen.sectioned_top);
//决定文本的显示位置等
alignBottom = res.getDimensionPixelSize(R.dimen.sectioned_alignBottom);
}
//图1:代表了getItemOffsets(),可以实现类似padding的效果
@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 (groupId.equals("-1")) return;
//只有是同一组的第一个才显示悬浮栏
if (pos == 0 || isFirstInGroup(pos)) {
outRect.top = topGap;
if (dataList.get(pos) == "") {
outRect.top = 0;
}
} else {
outRect.top = 0;
}
}
//图2:代表了onDraw(),可以实现类似绘制背景的效果,内容在上面
@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);
if (groupId.equals("-1")) return;
String textLine = callback.getGroupFirstLine(position).toUpperCase();
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, bottom, textPaint);
}
}
}
}
//图3:代表了onDrawOver(),可以绘制在内容的上面,覆盖内容
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
int itemCount = state.getItemCount();
int childCount = parent.getChildCount();
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
float lineHeight = textPaint.getTextSize() + fontMetrics.descent;
String preGroupId = "";
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).toUpperCase();
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 (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);
c.drawText(textLine, left, textY - 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;
}
}
}
//定义一个借口方便外界的调用
public interface DecorationCallback {
String getGroupId(int position);
String getGroupFirstLine(int position);
}
}

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
textRecycler.addItemDecoration(new SectionDecoration(list, this, new SectionDecoration.DecorationCallback() {
@Override
public String getGroupId(int position) {
if(NameBean.get(position)!=null) {
return NameBean.get(position);
}
return "-1";
}
@Override
public String getGroupFirstLine(int position) {
if(NameBean.get(position)!=null) {
return NameBean.get(position);
}
return "";
}
}));

ItemDecoration实现item的拖拽,平移等操作

(穿插还通过OnItemTouchListener封装了列表的点击和长点击事件)

item的点击事件和长点击事件

该功能主要通过addOnItemTouchListener() 来实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
textRecycler.addOnItemTouchListener(new OnRecyclerItemClickListener(textRecycler) {
@Override
public void onItemClick(RecyclerView.ViewHolder viewHolder) {
ToastUtils.showToast(viewHolder.getAdapterPosition()+1+"");
}
@Override
public void onLongClick(RecyclerView.ViewHolder viewHolder) {
ToastUtils.showToast(viewHolder.getAdapterPosition()+1+"");
//当 item 被长按且不是第一个时,开始拖曳这个 item
if (viewHolder.getLayoutPosition() != 0) {
itemTouchHelper.startDrag(viewHolder);
}
}
});

其中 OnRecyclerItemClickListener 是自定义的一个触摸监听器,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public abstract class OnRecyclerItemClickListener implements RecyclerView.OnItemTouchListener {
//手势探测器
private GestureDetectorCompat mGestureDetectorCompat;
private RecyclerView mRecyclerView;
public OnRecyclerItemClickListener(RecyclerView mRecyclerView) {
this.mRecyclerView = mRecyclerView;
mGestureDetectorCompat = new GestureDetectorCompat(mRecyclerView.getContext(),
new ItemTouchHelperGestureListener(mRecyclerView,this));
}
//第一个是拦截触摸事件的,第二个是处理触摸事件的,第三个是处理触摸冲突的。第三个这里我们用不到
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
mGestureDetectorCompat.onTouchEvent(e);
return false;
}
@Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
mGestureDetectorCompat.onTouchEvent(e);
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}
//提供单机 长按的方法
public abstract void onItemClick(RecyclerView.ViewHolder viewHolder);
public abstract void onLongClick(RecyclerView.ViewHolder viewHolder);
}

GestureDetectorCompat 中传入了一个 ItemTouchHelperGestureListener,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener {
private RecyclerView mRecyclerView;
private OnRecyclerItemClickListener onRecyclerItemClickListener;
public ItemTouchHelperGestureListener(RecyclerView mRecyclerView, OnRecyclerItemClickListener onRecyclerItemClickListener) {
this.mRecyclerView = mRecyclerView;
this.onRecyclerItemClickListener = onRecyclerItemClickListener;
}
//一次单独的轻触抬起手指操作,就是普通的点击事件
@Override
public boolean onSingleTapUp(MotionEvent e) {
//这个ChildHelper类,它会协助获取RecyclerView中的childVIew。 可点击看源码
View childViewUnder = mRecyclerView.findChildViewUnder(e.getX(), e.getY());
if (childViewUnder != null) {
RecyclerView.ViewHolder childViewHolder = mRecyclerView.getChildViewHolder(childViewUnder);
onRecyclerItemClickListener.onItemClick(childViewHolder);
}
return true;
}
//长按屏幕超过一定时长,就会触发,就是长按事件
@Override
public void onLongPress(MotionEvent e) {
View childViewUnder = mRecyclerView.findChildViewUnder(e.getX(), e.getY());
if (childViewUnder != null) {
RecyclerView.ViewHolder childViewHolder = mRecyclerView.getChildViewHolder(childViewUnder);
onRecyclerItemClickListener.onLongClick(childViewHolder);
}
}

其实通过一个手势探测器 GestureDetectorCompat 来探测屏幕事件,然后通过手势监听器 SimpleOnGestureListener 来识别手势事件的种类,然后调用我们设置的对应的回调方法。 通过findChildViewUnder()可以知道我们点击的是哪个item,可看源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public View findChildViewUnder(float x, float y) {
final int count = mChildHelper.getChildCount();
for (int i = count - 1; i >= 0; i--) {
final View child = mChildHelper.getChildAt(i);
final float translationX = ViewCompat.getTranslationX(child);
final float translationY = ViewCompat.getTranslationY(child);
if (x >= child.getLeft() + translationX &&
x <= child.getRight() + translationX &&
y >= child.getTop() + translationY &&
y <= child.getBottom() + translationY) {
return child;
}
}
return null;
}

同时我们调用 RecyclerView 的另一个方法 getChildViewHolder(),可以获得该 item 的 ViewHolder,最后再回调我们定义的虚方法 onItemClick() 就ok了,这样我们就可以在外部实现该方法来获得 item 的点击事件了。

ItemDecoration实现item的拖拽,平移等操作

ItemTouchHelper 一个帮助开发人员处理拖拽和滑动删除的实现类,它能够让你非常容易实现侧滑删除、拖拽的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
/**
* Created by wen on 2017/8/8.
* ItemTouchHelper 一个帮助开发人员处理拖拽和滑动删除的实现类,它能够让你非常容易实现侧滑删除、拖拽的功能。
*/
public class MyItemTouchHelper extends ItemTouchHelper.Callback {
RecyclerAdapter adapter;
public MyItemTouchHelper(RecyclerAdapter adapter) {
this.adapter = adapter;
}
//通过返回值来设置是否处理某次拖曳或者滑动事件
//dragFlags 是拖拽标志,
//swipeFlags 是滑动标志,
//swipeFlags 都设置为0,暂时不考虑滑动相关操作。
//getMovementFlags() 用于设置是否处理拖拽事件和滑动事件,以及拖拽和滑动操作的方向,有以下两种情况:
//如果是列表类型的 RecyclerView,拖拽只有 UP、DOWN 两个方向
//如果是网格类型的则有 UP、DOWN、LEFT、RIGHT 四个方向
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
if (recyclerView.getLayoutManager() instanceof GridLayoutManager) {
int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
int swipeFlags = 0;
return makeMovementFlags(dragFlags, swipeFlags);
} else {
int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
//注意:和拖曳的区别就是在这里
int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
return makeMovementFlags(dragFlags, swipeFlags);
}
}
//当长按并进入拖曳状态时,拖曳的过程中不断的回调此方法
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
//拖动的 item 的下标
int fromPosition = viewHolder.getAdapterPosition();
//目标 item 的下标,目标 item 就是当拖曳过程中,不断和拖动的 item 做位置交换的条目。
int toPosition = target.getAdapterPosition();
//对应某些需求,某一个item不能拖拽
if (toPosition == 0) {
return false;
}
if (fromPosition < toPosition) {
for (int i = fromPosition; i < toPosition; i++) {
//通过你传入的adapter得到你的数据 并进行交换
Collections.swap(((RecyclerAdapter) adapter).getDataList(), i, i + 1);
}
} else {
for (int i = fromPosition; i > toPosition; i--) {
Collections.swap(((RecyclerAdapter) adapter).getDataList(), i, i - 1);
}
}
adapter.notifyItemMoved(fromPosition, toPosition);
return true;
}
//滑动删除的回调
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
int adapterPosition = viewHolder.getAdapterPosition();
adapter.notifyItemRemoved(adapterPosition);
((RecyclerAdapter)adapter).getDataList().remove(adapterPosition);
}
//当长按 item 刚开始拖曳的时候调用
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
//给被拖曳的 item 设置一个深颜色背景
viewHolder.itemView.setBackgroundColor(Color.LTGRAY);
}
super.onSelectedChanged(viewHolder, actionState);
}
//当完成拖曳手指松开的时候调用
@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
//给已经完成拖曳的 item 恢复开始的背景。
// 这里我们设置的颜色尽量和你 item 在 xml 中设置的颜色保持一致
viewHolder.itemView.setBackgroundColor(Color.WHITE);
}
//返回 false 让它控制所有的 item 都不能拖曳。
@Override
public boolean isLongPressDragEnabled() {
return false;
}
}

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
itemTouchHelper = new ItemTouchHelper(new MyItemTouchHelper(adapter));
itemTouchHelper.attachToRecyclerView(textRecycler);
textRecycler.addOnItemTouchListener(new OnRecyclerItemClickListener(textRecycler) {
@Override
public void onItemClick(RecyclerView.ViewHolder viewHolder) {
ToastUtils.showToast(viewHolder.getAdapterPosition()+1+"");
}
@Override
public void onLongClick(RecyclerView.ViewHolder viewHolder) {
ToastUtils.showToast(viewHolder.getAdapterPosition()+1+"");
//当 item 被长按且不是第一个时,开始拖曳这个 item(这里只是一个特殊需求)
if (viewHolder.getLayoutPosition() != 0) {
itemTouchHelper.startDrag(viewHolder);
}
}
});