深入RecyclerView二(添加分割线+使用CardView+item点击事件)

添加分割线


本文介绍如何给RecyclerView添加分割线,需要用到RecyclerView的addItemDecoration方法。
关于ItemDecoration官方的介绍是:An ItemDecoration allows the application to add a special drawing and layout offset to specific item views from the adapter’s data set. This can be useful for drawing dividers between items, highlights, visual grouping boundaries and more.
允许应用程序添加偏移从适配器的数据集特定项目视图的特殊的绘图和布局。这可以是用于绘制的项目,突出显示,视觉分组边界和多之间的分隔。
使用此类需要写一个类继承自RecyclerView.ItemDecoration,重写onDraw(),onDrawOver(),getItemOffsets()三个方法。

  • onDraw() : Draw any appropriate decorations into the Canvas supplied to the RecyclerView.绘制任何合适的装饰成供给到RecyclerView画布
  • onDrawOver() : Draw any appropriate decorations into the Canvas supplied to the RecyclerView.绘制任何合适的装饰成供给到RecyclerView画布
  • getItemOffsets() : Retrieve any offsets for the given item.检索给定项的偏移量

onDraw()方法先于drawChildren,onDrawOver()在drawChildren之后,用一个就行

DividerItemDecoration :

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
package com.liompei.app2;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;
/**
* Created by BLM on 2016/7/21.
*/
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
private static final int[] ATTRS = new int[]{android.R.attr.listDivider};
public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL; //0
public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL; //1
private Drawable drawable; //用来存分割线
private int orientation; //记录方向
public DividerItemDecoration(Context context, int orientation) {
TypedArray typedArray = context.obtainStyledAttributes(ATTRS); //作为分割线
drawable = typedArray.getDrawable(0);
typedArray.recycle(); //回收
setOrientation(orientation);
}
public void setOrientation(int orientation) {
if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
//如果传入方向不是水平或者垂直,则抛出参数异常:无效的orientation
throw new IllegalArgumentException("请传入正确参数");
}
this.orientation = orientation;
}
//绘制
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
Log.d("itemDecoration", "onDraw");
//是垂直还是水平
if (orientation == VERTICAL_LIST) {
drawVertical(c, parent);
} else {
drawHorizontal(c, parent);
}
}
//绘制纵向 item 分割线
public void drawVertical(Canvas canvas, RecyclerView recyclerView) {
int left = recyclerView.getPaddingLeft(); //左边的坐标
int right = recyclerView.getWidth() - recyclerView.getPaddingRight(); //右边的坐标
int childCount = recyclerView.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = recyclerView.getChildAt(i);
RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();
int top = child.getBottom() + layoutParams.bottomMargin;
int bottom = top + drawable.getIntrinsicHeight();
drawable.setBounds(left, top, right, bottom); //drawable将要被绘制在canvas内的矩形区域
drawable.draw(canvas); //将drawable画到屏幕上
}
}
//绘制横向 item 分割线
public void drawHorizontal(Canvas canvas, RecyclerView recyclerView) {
int top = recyclerView.getPaddingTop(); //顶部距离
int bottom = recyclerView.getMeasuredHeight() - recyclerView.getPaddingBottom(); //总长度减去离底部距离
int childCount = recyclerView.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = recyclerView.getChildAt(i);
RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();
int left = child.getRight() + layoutParams.rightMargin;
int right = left + drawable.getIntrinsicHeight();
drawable.setBounds(left, top, right, bottom);
drawable.draw(canvas);
}
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
}
//绘制的范围
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
if (orientation == VERTICAL_LIST) {
outRect.set(0, 0, 0, drawable.getIntrinsicHeight());
} else {
outRect.set(0, 0, drawable.getIntrinsicWidth(), 0);
}
}
}

然后我们再改变一下activity中recyclerView的代码

1
2
3
4
5
6
7
8
recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false); //context,布局走向,是否反转
recyclerView.setLayoutManager(linearLayoutManager); //必备
recyclerView.addItemDecoration(new DividerItemDecoration(this, linearLayoutManager.getOrientation()));
adapter = new RvAdapter(this, getData());
recyclerView.setAdapter(adapter); //必备

这里我们实例化了LinearLayoutManager,在使用addItemDecoration()方法时传入了LinearLayoutManager的走向,根据是垂直还是水平来更改分割线样式.

跑起来是这样的

如果想自定义这个分割线,在style当前Theme中加入

1
<item name="android:listDivider">@drawable/divider_bg</item>

divider_bg.xml

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<!--颜色-->
<solid android:color="@color/colorAccent" />
<!--高度-->
<size android:height="1dp" />
</shape>

这里可以自行定义.

如果用的是GridLayoutManager,这个就不适用了,需要自行绘制.实在觉得麻烦可以搜一下ItemDecoration万能分割线或者去Github上看看学学别人怎么绘制的,Github上面好东西太多了.

CardView


CardView是有圆角的背景和阴影的FrameLayout
使用时需要导入类库

CardView的常用属性有:

  • app:cardBackgroundColor 卡片的背景色
  • app:cardCornerRadius 卡片的圆角大小
  • app:cardElevation 阴影的大小
  • app:cardMaxElevation 阴影最大高度
  • app:cardPreventCornerOverlap 设置内边距,适用于V21+版本
  • app:cardUseCompatPadding 设置内边距V20-版本
  • app:contentPadding 卡片内容于边距的间隔

此外,给CardView添加点击效果使用的是

1
android:foreground="?attr/selectableItemBackground"

这是一个点击水波纹的效果,当然也可以自己定义

下面我们修改布局文件

recycler_item.xml

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
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/rec_linear"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:foreground="?attr/selectableItemBackground"
app:cardBackgroundColor="#fff"
app:cardCornerRadius="2dp"
app:cardElevation="2dp"
app:cardMaxElevation="4dp"
app:cardPreventCornerOverlap="true"
android:stateListAnimator="@animator/touch_item"
app:cardUseCompatPadding="true"
app:contentPadding="5dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/textName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp" />
<TextView
android:id="@+id/textNum"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="15sp" />
</LinearLayout>
</android.support.v7.widget.CardView>

ok,看下成果

是不是连ItemDecoration都省了?

再添加些东西,让点击效果更佳贴近Material Design

在res/animator下新建touch_item.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_enabled="true"
android:state_pressed="true">
<objectAnimator
android:duration="@android:integer/config_shortAnimTime"
android:propertyName="translationZ"
android:valueTo="5dp"
android:valueType="floatType" />
</item>
<item>
<objectAnimator
android:duration="@android:integer/config_shortAnimTime"
android:propertyName="translationZ"
android:valueTo="0dp"
android:valueType="floatType" />
</item>
</selector>

CardView中加入属性

1
2
3
android:stateListAnimator="@animator/touch_item"

这是一种Z轴位移,类似浮起的效果.非常酷

注意事项请参考这里: 传送门

添加item点击事件


在RvAdapter中添加接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface OnItemClickListener {
void onItemClick(View view, int position);
void onItemLongClick(View view, int position);
}
private OnItemClickListener onItemClickListener;
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener;
}

在onBinderViewHolder中设置点击后的回调点击事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//如果设置了回调,则设置点击事件
if (onItemClickListener != null) {
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//点击事件
onItemClickListener.onItemClick(holder.itemView, holder.getLayoutPosition());
}
});
holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
//长按事件
onItemClickListener.onItemLongClick(holder.itemView, holder.getLayoutPosition());
return false;
}
});
}

在activity中使用

1
2
3
4
5
6
7
8
9
10
11
12
13
adapter.setOnItemClickListener(new RvAdapter.OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
}
@Override
public void onItemLongClick(View view, int position) {
}
});

Demo: https://github.com/liompei/RecyclerViewDemo/tree/master/app2