Exciter

生命不息、折腾不止

0%

Android-RecyclerView的使用详解

本文完整源码下载地址:AndroidBasic

1.RecyclerView的使用

1.1 新建一个RecyclerViewActivity

Activity的activity_recycler_view.xml如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".widget.recyclerview.RecyclerViewActivity">

<include
layout="@layout/layout_tool_bar"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tool_bar" />

</androidx.constraintlayout.widget.ConstraintLayout>

1.2 新建一个RecyclerViewAdapter

1.2.1 准备好item的xml

列表都需要一个Adapter,写Adapter之前先把item的布局写好,item的xml比较简单,就一个文本,item_recycler_view.xml如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="15dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
1.2.2 自定义一个ViewHolder

在新建一个RecyclerViewAdapter写一个MyHolder内部类,继承RecyclerView.ViewHolder:

1
2
3
4
5
6
7
8
public static class MyHolder extends RecyclerView.ViewHolder {
private final TextView mTvTitle;

public MyHolder(@NonNull View itemView) {
super(itemView);
mTvTitle = itemView.findViewById(R.id.tv_title);
}
}
1.2.3 定义item点击事件的接口类和方法

列表都会有点击功能,这里我们给item加上点击、长按以及子View的点击事件。

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
public interface OnItemClickListener {
/**
* 点击item
*
* @param view item
* @param position 索引
*/
void onItemClick(View view, int position);

/**
* 长按item
*
* @param view item
* @param position 索引
*/
void onItemLongClick(View view, int position);

/**
* 点击item中的子view
*
* @param view 子view
* @param position 索引
*/
void onItemChildClick(View view, int position);
}
1.2.4 给item加上点击事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
public void onBindViewHolder(@NonNull MyHolder holder, int position) {
if (null != mData) {
//设置数据
holder.mTvTitle.setText(!TextUtils.isEmpty(mData.get(position).getTitle()) ? mData.get(position).getTitle() : "no title");
}
if (null != mOnItemClickListener) {
//item添加点击事件
holder.itemView.setOnClickListener(v -> mOnItemClickListener.onItemClick(holder.itemView, position));
//item添加长按事件
holder.itemView.setOnLongClickListener(v -> {
mOnItemClickListener.onItemLongClick(holder.itemView, position);
return false;
});
//item子view添加点击事件
holder.mTvTitle.setOnClickListener(v -> mOnItemClickListener.onItemChildClick(holder.mTvTitle, position));
}
}

1.3 在Actiivty中初始化RecyclerView

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
private void initView() {
Toolbar toolbar = findViewById(R.id.tool_bar);
toolbar.setTitle("RecyclerView");
toolbar.setNavigationOnClickListener(v -> finish());
RecyclerView recyclerView = findViewById(R.id.recycler_view);
LinearLayoutManager manager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(manager);
mRecyclerViewAdapter = new RecyclerViewAdapter(this, mData);
recyclerView.setAdapter(mRecyclerViewAdapter);
mRecyclerViewAdapter.setOnItemClickListener(new RecyclerViewAdapter.OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
Toast.makeText(RecyclerViewActivity.this, "点击了第" + position + "条数据",
Toast.LENGTH_SHORT).show();
}

@Override
public void onItemLongClick(View view, int position) {
new AlertDialog.Builder(RecyclerViewActivity.this)
.setTitle("确定删除这条数据吗?")
.setNegativeButton("取消", null)
.setPositiveButton("确定",
(dialog, which) -> mRecyclerViewAdapter.removeData(position)).show();
}

@Override
public void onItemChildClick(View view, int position) {
Toast.makeText(RecyclerViewActivity.this, "点击了第" + position + "条数据的标题",
Toast.LENGTH_SHORT).show();
}
});
}

1.4 最终效果

2021042501

1.5 完整代码

1.5.1 Activity完整代码
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
package com.exciter.androidbasic.widget.recyclerview;

import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

import com.exciter.androidbasic.R;

import java.util.ArrayList;
import java.util.List;

public class RecyclerViewActivity extends AppCompatActivity {

private final List<ItemBean> mData = new ArrayList<>();
private RecyclerViewAdapter mRecyclerViewAdapter;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_recycler_view);
initView();
initData();
}

private void initData() {
for (int i = 0; i < 30; i++) {
ItemBean bean = new ItemBean();
bean.setTitle("item" + i);
bean.setHeight((int) (100 + Math.random() * 300));
mData.add(bean);
}
mRecyclerViewAdapter.notifyDataSetChanged();
}

private void initView() {
Toolbar toolbar = findViewById(R.id.tool_bar);
toolbar.setTitle("RecyclerView");
toolbar.setNavigationOnClickListener(v -> finish());
RecyclerView recyclerView = findViewById(R.id.recycler_view);
LinearLayoutManager manager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(manager);
mRecyclerViewAdapter = new RecyclerViewAdapter(this, mData);
recyclerView.setAdapter(mRecyclerViewAdapter);
mRecyclerViewAdapter.setOnItemClickListener(new RecyclerViewAdapter.OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
Toast.makeText(RecyclerViewActivity.this, "点击了第" + position + "条数据",
Toast.LENGTH_SHORT).show();
}

@Override
public void onItemLongClick(View view, int position) {
new AlertDialog.Builder(RecyclerViewActivity.this)
.setTitle("确定删除这条数据吗?")
.setNegativeButton("取消", null)
.setPositiveButton("确定",
(dialog, which) -> mRecyclerViewAdapter.removeData(position)).show();
}

@Override
public void onItemChildClick(View view, int position) {
Toast.makeText(RecyclerViewActivity.this, "点击了第" + position + "条数据的标题",
Toast.LENGTH_SHORT).show();
}
});
}
}
1.5.2 Adapter完整代码
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
package com.exciter.androidbasic.widget.recyclerview;

import android.content.Context;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import com.exciter.androidbasic.R;

import java.util.List;

public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.MyHolder> {

private Context mContext;
private List<ItemBean> mData;
private OnItemClickListener mOnItemClickListener;

public RecyclerViewAdapter(Context mContext, List<ItemBean> mData) {
this.mContext = mContext;
this.mData = mData;
}

@NonNull
@Override
public MyHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new MyHolder(LayoutInflater.from(mContext).inflate(R.layout.item_recycler_view, parent, false));
}

@Override
public void onBindViewHolder(@NonNull MyHolder holder, int position) {
if (null != mData) {
//设置数据
holder.mTvTitle.setText(!TextUtils.isEmpty(mData.get(position).getTitle()) ? mData.get(position).getTitle() : "no title");
}
if (null != mOnItemClickListener) {
//item添加点击事件
holder.itemView.setOnClickListener(v -> mOnItemClickListener.onItemClick(holder.itemView, position));
//item添加长按事件
holder.itemView.setOnLongClickListener(v -> {
mOnItemClickListener.onItemLongClick(holder.itemView, position);
return false;
});
//item子view添加点击事件
holder.mTvTitle.setOnClickListener(v -> mOnItemClickListener.onItemChildClick(holder.mTvTitle, position));
}
}

@Override
public int getItemCount() {
return mData != null ? mData.size() : 0;
}

public static class MyHolder extends RecyclerView.ViewHolder {
private final TextView mTvTitle;

public MyHolder(@NonNull View itemView) {
super(itemView);
mTvTitle = itemView.findViewById(R.id.tv_title);
}
}

public void removeData(int position) {
mData.remove(position);
notifyItemRemoved(position);
}

public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.mOnItemClickListener = onItemClickListener;
}

public interface OnItemClickListener {
/**
* 点击item
*
* @param view item
* @param position 索引
*/
void onItemClick(View view, int position);

/**
* 长按item
*
* @param view item
* @param position 索引
*/
void onItemLongClick(View view, int position);

/**
* 点击item中的子view
*
* @param view 子view
* @param position 索引
*/
void onItemChildClick(View view, int position);
}
}

2.给RecyclerView添加分割线

2.1 线性列表下的分割线

2.1.1 绘制分割线

新建CustomLinearItemDecoration,继承RecyclerView.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
package com.exciter.androidbasic.widget.recyclerview;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.view.View;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

public class CustomLinearItemDecoration extends RecyclerView.ItemDecoration {

/**
* 纵向布局
*/
public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
/**
* 横向布局
*/
public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
private final Drawable mDivider;
private int mOrientation;
private final int mIndent;

/**
* 构造方法
*
* @param context 上下文
* @param orientation 列表方向
* @param drawable 分割线样式
* @param indent 缩进值
*/
public CustomLinearItemDecoration(Context context, int orientation, int drawable, int indent) {
mDivider = context.getResources().getDrawable(drawable);
this.mIndent = indent;
setOrientation(orientation);
}

public void setOrientation(int orientation) {
if (orientation != VERTICAL_LIST && orientation != HORIZONTAL_LIST) {
throw new IllegalArgumentException("invalid orientation");
}
mOrientation = orientation;
}

@Override
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
if (mOrientation == VERTICAL_LIST) {
drawVertical(c, parent);
} else {
drawHorizontal(c, parent);
}
}

@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent,
@NonNull RecyclerView.State state) {
if (mOrientation == VERTICAL_LIST) {
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else {
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
}
}

/**
* 绘制纵向列表的分割线
*
* @param c 画布
* @param parent 画笔
*/
private void drawVertical(Canvas c, RecyclerView parent) {
final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
int top = child.getBottom() + params.bottomMargin;
int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(mIndent > 0 ? left + mIndent : left, top, mIndent > 0 ? right - mIndent : right, bottom);
mDivider.draw(c);
}
}

/**
* 绘制横向列表的分割线
*
* @param c 画布
* @param parent 画笔
*/
private void drawHorizontal(Canvas c, RecyclerView parent) {
final int top = parent.getPaddingTop();
final int bottom = parent.getHeight() - parent.getPaddingBottom();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
int left = child.getRight() + params.rightMargin;
int right = left + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, mIndent > 0 ? top + mIndent : top, right, mIndent > 0 ? bottom - mIndent : bottom);
mDivider.draw(c);
}
}
}
2.1.2 设置分割线

在initView方法中给RecyclerView添加如下代码即可。

1
2
recyclerView.addItemDecoration(new CustomLinearItemDecoration(this,
CustomLinearItemDecoration.VERTICAL_LIST, R.drawable.divider_custom, 10));

divider_custom.xml代码:

1
2
3
4
5
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#e3e3e3" />
<size android:height="3dp" />
</shape>
2.1.3 效果图
2021042502

2.2 宫格或瀑布流下的分割线

2.2.1 绘制分割线

新建CustomGridItemDecoration,继承RecyclerView.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
package com.exciter.androidbasic.widget.recyclerview;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.view.View;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;

import java.util.Objects;

public class CustomGridItemDecoration extends RecyclerView.ItemDecoration {
private static final String TAG = "exciter";
private final Drawable mDivider;

public CustomGridItemDecoration(Context context, int drawable) {
mDivider = context.getResources().getDrawable(drawable);
}

@Override
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
drawHorizontal(c, parent);
drawVertical(c, parent);
}

private int getSpanCount(RecyclerView parent) {
//列数
int spanCount = -1;
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof GridLayoutManager) {
spanCount = ((GridLayoutManager) layoutManager).getSpanCount();
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
spanCount = ((StaggeredGridLayoutManager) layoutManager).getSpanCount();
}
return spanCount;
}

private void drawHorizontal(Canvas c, RecyclerView parent) {
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 left = child.getLeft() - params.leftMargin;
final int right = child.getRight() + params.rightMargin;
final int top = child.getBottom() + params.bottomMargin;
final int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}

private void drawVertical(Canvas c, RecyclerView parent) {
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.getTop() - params.topMargin;
final int bottom = child.getBottom() + params.bottomMargin + mDivider.getIntrinsicHeight();
final int left = child.getRight() + params.rightMargin;
final int right = left + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}

private boolean isLastColumn(RecyclerView parent, int pos, int spanCount, int childCount) {
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof GridLayoutManager) {
//最后一列,不需要绘制右边
return (pos + 1) % spanCount == 0;
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
int orientation = ((StaggeredGridLayoutManager) layoutManager).getOrientation();
if (orientation == StaggeredGridLayoutManager.VERTICAL) {
//最后一列,不需要绘制右边
return (pos + 1) % spanCount == 0;
} else {
childCount = childCount - childCount % spanCount;
//最后一列,不需要绘制右边
return pos >= childCount;
}
}
return false;
}

private boolean isLastRow(RecyclerView parent, int pos, int spanCount, int childCount) {
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof GridLayoutManager) {
childCount = childCount - childCount % spanCount;
//如果是最后一行,不需要绘制底部
return pos >= childCount;
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
int orientation = ((StaggeredGridLayoutManager) layoutManager).getOrientation();
if (orientation == StaggeredGridLayoutManager.VERTICAL) {
childCount = childCount - childCount % spanCount;
return pos >= childCount;
} else {
return (pos + 1) % spanCount == 0;
}
}
return false;
}

@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
int spanCount = getSpanCount(parent);
int childCount = Objects.requireNonNull(parent.getAdapter()).getItemCount();
if (isLastRow(parent, parent.indexOfChild(view), spanCount, childCount)) {
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
} else if (isLastColumn(parent, parent.indexOfChild(view), spanCount, childCount)) {
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else {
outRect.set(0, 0, mDivider.getIntrinsicWidth(), mDivider.getIntrinsicHeight());
}
}
}
2.2.2 设置分割线

在initView修改代码:

1
2
3
StaggeredGridLayoutManager manager = new StaggeredGridLayoutManager(4, StaggeredGridLayoutManager.VERTICAL);
recyclerView.setLayoutManager(manager);
recyclerView.addItemDecoration(new CustomGridItemDecoration(this, R.drawable.divider_custom));

同时为了瀑布流每个item有随机高度,在RecyclerViewAdapter的onBindViewHolder中添加如下代码,随机设置item高度:

1
2
3
ViewGroup.LayoutParams params = holder.mTvTitle.getLayoutParams();
params.height = mData.get(position).getHeight();
holder.mTvTitle.setLayoutParams(params);
2.2.3 效果图
2021042503