RecyclerView 很强大很好用,直接开整。
一、RecyclerView 准备工作
- Gradle 中引入 dependencies 闭包
dependencies {
implementation 'androidx.recyclerview:recyclerview:1.1.0'
}
- 修改 activity_main.xml,配置按键 和 RecyclerView 控件 layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="添加数据"
android:onClick="onAddDataClick"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="切换布局"
android:onClick="onChangeLayoutClick"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="插入一条数据"
android:onClick="onInsertDataClick"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="删除一条数据"
android:onClick="onRemoveDataClick"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.recyclerview.widget.RecyclerView>
</LinearLayout>
- 新建一个类继承 RecyclerView.Adapter
- 并且创建一个内部类 MyViewHolder 传入 RecyclerView.Adapter 泛型
- 重写 MyRecyclerViewAdapter 抽象方法
public class MyRecyclerViewAdapter extends
RecyclerView.Adapter<MyRecyclerViewAdapter.MyViewHolder> {
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return null;
}
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder,final int position) {
}
@Override
public int getItemCount() {
}
//创建一个MyViewHolder继承RecyclerView.ViewHolder
class MyViewHolder extends RecyclerView.ViewHolder {
public MyViewHolder(@NonNull View itemView) {
super(itemView);
}
}
}
二、配置 Adapter 内部方法
- 新建一个 layout_item.xml 用于作 Adapter 的 item
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal" android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:background="#A4D3EE">
<ImageView
android:id="@+id/image_view"
android:layout_width="88dp"
android:layout_height="88dp"
android:scaleType="fitXY"
/>
<TextView
android:id="@+id/text_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textSize="22sp"
android:textColor="#FFF"
android:gravity="center"
android:layout_marginStart="8dp"/>
</LinearLayout>
- 加载布局,创建一个 ViewHolder
//创建并且返回 ViewHolder
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
//创建layout.layout_item,绑定View,返回ViewHolder给onBindViewHolder
View view = LayoutInflater.from(mContext).inflate(R.layout.layout_item,
parent, false);
MyViewHolder holder = new MyViewHolder(view);
return holder;
}
- 从 ViewHolder 中 findViewById
//创建一个MyViewHolder继承RecyclerView.ViewHolder
class MyViewHolder extends RecyclerView.ViewHolder {
TextView textView;
ImageView imageView;
//这里提出MyViewHolder中的View来做点击事件处理
View itemView;
//onCreateViewHolder中创建MyViewHolder对象,传入View
public MyViewHolder(@NonNull View itemView) {
super(itemView);
imageView = itemView.findViewById(R.id.image_view);
textView = itemView.findViewById(R.id.text_view);
this.itemView = itemView;
}
}
- 绑定数据
//绑定数据
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder,final int position) {
//4.通过onCreateViewHolder传来的ViewHolder来进行数据源设置
holder.textView.setText(dataSource.get(position));
holder.imageView.setImageResource(getIcon(position));
}
- 创建数据源
注意:一定要初始化数据源,否则getItemCount会出现空指针
private final Context mContext;
private List<String> dataSource;
//或者layout_item布局设置为vertical,然后随机textView字符串的长度
public MyRecyclerViewAdapter(Context context) {
//初始化数据源,否则getItemCount会出现空指针
this.dataSource = new ArrayList<>();
this.mContext = context;
}
//设置数据源
public void setDataSource(List<String> dataSource) {
this.dataSource = dataSource;
notifyDataSetChanged();
}
//这里使用自定义的五张图片循环使用
private int getIcon (int position) {
switch (position % 5) {
case 0:
return R.mipmap.a;
case 1:
return R.mipmap.b;
case 2:
return R.mipmap.c;
case 3:
return R.mipmap.d;
case 4:
return R.mipmap.e;
}
return 0;
}
- 返回数据量
//返回数据量
@Override
public int getItemCount() {
//5.返回数据总量
return dataSource.size();
}
三、显示数据
- MainActivity 中给 RecyclerView 配置 Adapter
public class MainActivity extends AppCompatActivity {
private RecyclerView mRecyclerView;
private MyRecyclerViewAdapter mAdapter;
private List<String> mDataSource = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRecyclerView = findViewById(R.id.recycler_view);
//LayoutManager用于指定RecyclerView的布局方式
//LinearLayoutManager为线性布局
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
//设置RecyclerView的布局
mRecyclerView.setLayoutManager(linearLayoutManager);
mAdapter = new MyRecyclerViewAdapter(this);
mRecyclerView.setAdapter(mAdapter);
}
//使用按键创建并设置数据源
public void onAddDataClick(View view) {
for (int i = 0; i < 20; i++) {
String s = "第" + i + "条数据";
mDataSource.add(s);
}
mAdapter.setDataSource(mDataSource);
}
}
- 横向显示数据,需要在加载 Adapter 之前配置 linearLayoutManager
...
//横向排列ItemView,最好修改layout_item为vertical
linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
//数据反向展示(从右向左滑动)
linearLayoutManager.setReverseLayout(true);
...
- 运行结果
四、切换布局
-
RecyclerView 除了 LinearLayout 外,还有 GridLayout 网格布局和 StaggeredGridLayout 瀑布流布局
-
修改 MyRecyclerViewAdapter 构造方法, 新增传入mRecyclerView用来设置textView随机高度
private RecyclerView mRecyclerView;
//参数2 传入mRecyclerView用来设置textView随机高度;
//或者layout_item布局设置为vertical,然后随机textView字符串的长度
public MyRecyclerViewAdapter(Context context, RecyclerView mRecyclerView) {
//初始化数据源,否则getItemCount会出现空指针
this.dataSource = new ArrayList<>();
this.mContext = context;
this.mRecyclerView = mRecyclerView;
}
- 随机控件高度
//获取TextView随机高度
private int getRandomHeight() {
return (int)(Math.random() * 1000);
}
- 在 onBindViewHolder 方法增加条件判断
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder,final int position) {
...
//如果是瀑布流就设置随机高度
if (mRecyclerView.getLayoutManager().getClass() == StaggeredGridLayoutManager.class) {
LinearLayout.LayoutParams params
= new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getRandomHeight());
holder.textView.setLayoutParams(params);
} else {
LinearLayout.LayoutParams params
= new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
holder.textView.setLayoutParams(params);
}
}
- 修改 MainActivity 中的方法
protected void onCreate(Bundle savedInstanceState) {
...
//onCreate方法中修改
mAdapter = new MyRecyclerViewAdapter(this, mRecyclerView);
...
}
//改变mAdapter的布局方式
public void onChangeLayoutClick(View view) {
//从线性布局 --> 网格布局
if (mRecyclerView.getLayoutManager().getClass() == LinearLayoutManager.class) {
GridLayoutManager gridLayoutManager = new GridLayoutManager(this, 2);
mRecyclerView.setLayoutManager(gridLayoutManager);
}
//网格布局 --> 瀑布流布局
else if (mRecyclerView.getLayoutManager().getClass() == GridLayoutManager.class) {
StaggeredGridLayoutManager staggeredGridLayoutManager
= new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
mRecyclerView.setLayoutManager(staggeredGridLayoutManager);
}
//瀑布流布局 --> 线性布局
else if (mRecyclerView.getLayoutManager().getClass() == StaggeredGridLayoutManager.class) {
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(linearLayoutManager);
}
}
五、添加点击事件
- MyRecyclerViewAdapter 中创建 ItemView 点击事件回调接口
public class MyRecyclerViewAdapter extends
RecyclerView.Adapter<MyRecyclerViewAdapter.MyViewHolder> {
private OnItemClickListener onItemClickListener;
private OnImageClickListener onImageClickListener;
...
//ItemView点击事件回调接口
interface OnItemClickListener {
void onItemClick(int position);
}
interface OnImageClickListener {
void onItemClick(int position);
}
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener;
}
public void setOnImageClickListener(OnImageClickListener onImageClickListener) {
this.onImageClickListener = onImageClickListener;
}
}
- 在 onBindViewHolder 中使用回调方法
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder,final int position) {
...
//点击事件处理
holder.itemView.setOnClickListener(v -> {
if (onItemClickListener != null) {
//这里使用回调方法,具体怎么使用还得从MainActivity中重写
onItemClickListener.onItemClick(position);
}
});
holder.imageView.setOnClickListener(v -> {
if (onImageClickListener != null) {
//这里使用回调方法,具体怎么使用还得从MainActivity中重写
onImageClickListener.onItemClick(position);
}
});
}
- 或者在 onCreateViewHolder 中使用回调方法
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
//创建layout.layout_item,绑定View,返回ViewHolder给onBindViewHolder
View view = LayoutInflater.from(mContext).inflate(R.layout.layout_item,
parent, false);
MyViewHolder holder = new MyViewHolder(view);
//onClick方法写在onCreateViewHolder中
holder.imageView.setOnClickListener(v -> {
int position = holder.getAdapterPosition();
if (onItemClickListener != null) {
//这里使用回调方法,具体怎么使用还得从MainActivity中重写
onItemClickListener.onItemClick(position);
}
});
return holder;
}
- MainActivity 重写回调方法
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
...
mAdapter.setOnItemClickListener(position -> {
Toast.makeText(MainActivity.this, "第" + position + "数据被点击", Toast.LENGTH_SHORT).show();
});
mAdapter.setOnImageClickListener(position -> {
Toast.makeText(MainActivity.this, "第" + position + "图片被点击", Toast.LENGTH_SHORT).show();
});
}
六、增加 \ 删除 Item
- MyRecyclerViewAdapter 中增加方法
//添加的数据位置,用来改变新加数据的背景色
private int addDataPosition = -1;
//添加一条数据
public void addData(int position) {
addDataPosition = position;
dataSource.add(position, "添加的数据");
//通知插入数据
notifyItemInserted(position);
//通知数据长度变更
// 参数1:是起始位置,从哪里开始更新,参数2:更新的总数
notifyItemRangeChanged(position, dataSource.size() - position);
//添加数据时滚动到第一行
mRecyclerView.smoothScrollToPosition(position);
}
//删除一条数据
public void deleteData(int position){
addDataPosition = -1;
dataSource.remove(position);
//通知插入数据
notifyItemRemoved(position);
//通知数据长度变更
// 参数1:是起始位置,从哪里开始更新,参数2:更新的总数
notifyItemRangeChanged(position, dataSource.size() - position);
}
- 修改 onBindViewHolder 中的代码
public void onBindViewHolder(@NonNull MyViewHolder holder,final int position) {
...
//改变新添加的颜色
if (addDataPosition == position) {
holder.itemView.setBackgroundColor(Color.RED);
} else {
holder.itemView.setBackgroundColor(Color.parseColor("#A4D3EE"));
}
}
- MainActivity 中增加方法
public void onRemoveDataClick(View view) {
mAdapter.deleteData(0);
}
public void onInsertDataClick(View view) {
mAdapter.addData(0);
}
七、配置动画 setItemAnimator
可以参考学习:
https://www.jianshu.com/p/2a82b0341138
八、所有代码预览
MyRecyclerViewAdapter
/**
* 1、继承RecyclerView.Adapter
* 2、绑定ViewHolder
* 3、实现Adapter的相关方法
*/
public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewAdapter.MyViewHolder> {
private final Context mContext;
private List<String> dataSource;
private RecyclerView mRecyclerView;
private OnItemClickListener onItemClickListener;
private OnImageClickListener onImageClickListener;
//添加的数据位置,用来改变新加数据的背景色
private int addDataPosition = -1;
//参数2 传入mRecyclerView用来设置textView随机高度;
//或者layout_item布局设置为vertical,然后随机textView字符串的长度
public MyRecyclerViewAdapter(Context context, RecyclerView mRecyclerView) {
//初始化数据源,否则getItemCount会出现空指针
this.dataSource = new ArrayList<>();
this.mContext = context;
this.mRecyclerView = mRecyclerView;
}
//设置数据源
public void setDataSource(List<String> dataSource) {
this.dataSource = dataSource;
notifyDataSetChanged();
}
//获取图片数据
private int getIcon (int position) {
switch (position % 5) {
case 0:
return R.mipmap.a;
case 1:
return R.mipmap.b;
case 2:
return R.mipmap.c;
case 3:
return R.mipmap.d;
case 4:
return R.mipmap.e;
}
return 0;
}
//获取TextView随机高度
private int getRandomHeight() {
return (int)(Math.random() * 1000);
}
//创建并且返回 ViewHolder
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
//2.创建layout.layout_item,绑定View,返回ViewHolder给onBindViewHolder
View view = LayoutInflater.from(mContext).inflate(R.layout.layout_item,
parent, false);
MyViewHolder holder = new MyViewHolder(view);
//7.onClick方法写在onCreateViewHolder中
// holder.imageView.setOnClickListener(v -> {
// int position = holder.getAdapterPosition();
// if (onItemClickListener != null) {
// //这里使用回调方法,具体怎么使用还得从MainActivity中重写
// onItemClickListener.onItemClick(position);
// }
// });
return holder;
}
//绑定数据
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder,final int position) {
//4.通过onCreateViewHolder传来的ViewHolder来进行数据源设置
holder.textView.setText(dataSource.get(position));
holder.imageView.setImageResource(getIcon(position));
//8.改变新添加的颜色
if (addDataPosition == position) {
holder.itemView.setBackgroundColor(Color.RED);
} else {
holder.itemView.setBackgroundColor(Color.parseColor("#A4D3EE"));
}
//6.如果是瀑布流就设置随机高度
if (mRecyclerView.getLayoutManager().getClass() == StaggeredGridLayoutManager.class) {
LinearLayout.LayoutParams params
= new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getRandomHeight());
holder.textView.setLayoutParams(params);
} else {
LinearLayout.LayoutParams params
= new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
holder.textView.setLayoutParams(params);
}
//7.点击事件处理
holder.itemView.setOnClickListener(v -> {
if (onItemClickListener != null) {
//这里使用回调方法,具体怎么使用还得从MainActivity中重写
onItemClickListener.onItemClick(position);
}
});
holder.imageView.setOnClickListener(v -> {
if (onImageClickListener != null) {
//这里使用回调方法,具体怎么使用还得从MainActivity中重写
onImageClickListener.onItemClick(position);
}
});
}
//返回数据量
@Override
public int getItemCount() {
//5.返回数据总量
return dataSource.size();
}
//1.创建一个MyViewHolder继承RecyclerView.ViewHolder
class MyViewHolder extends RecyclerView.ViewHolder {
TextView textView;
ImageView imageView;
//7.这里提出MyViewHolder中的View来做点击事件处理
View itemView;
//3.onCreateViewHolder中创建MyViewHolder对象,传入View
public MyViewHolder(@NonNull View itemView) {
super(itemView);
imageView = itemView.findViewById(R.id.image_view);
textView = itemView.findViewById(R.id.text_view);
this.itemView = itemView;
}
}
//7.ItemView点击事件回调接口
interface OnItemClickListener {
void onItemClick(int position);
}
interface OnImageClickListener {
void onItemClick(int position);
}
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener;
}
public void setOnImageClickListener(OnImageClickListener onImageClickListener) {
this.onImageClickListener = onImageClickListener;
}
//8.添加一条数据
public void addData(int position) {
addDataPosition = position;
dataSource.add(position, "添加的数据");
//通知插入数据
notifyItemInserted(position);
//通知数据长度变更
// 参数1:是起始位置,从哪里开始更新,参数2:更新的总数
notifyItemRangeChanged(position, dataSource.size() - position);
mRecyclerView.smoothScrollToPosition(position);
}
//8.删除一条数据
public void deleteData(int position){
addDataPosition = -1;
dataSource.remove(position);
//通知插入数据
notifyItemRemoved(position);
//通知数据长度变更
// 参数1:是起始位置,从哪里开始更新,参数2:更新的总数
notifyItemRangeChanged(position, dataSource.size() - position);
}
}
MainActivity
public class MainActivity extends AppCompatActivity {
private RecyclerView mRecyclerView;
private MyRecyclerViewAdapter mAdapter;
private List<String> mDataSource = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRecyclerView = findViewById(R.id.recycler_view);
//LayoutManager用于指定RecyclerView的布局方式
//LinearLayoutManager为线性布局
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
//横向排列ItemView,最好修改layout_item为vertical
//linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
//数据反向展示
//linearLayoutManager.setReverseLayout(true);
//设置RecyclerView的布局
mRecyclerView.setLayoutManager(linearLayoutManager);
mAdapter = new MyRecyclerViewAdapter(this, mRecyclerView);
mRecyclerView.setAdapter(mAdapter);
//mRecyclerView.setItemAnimator(new DefaultItemAnimator());
// mAdapter.setOnItemClickListener(position -> {
// Toast.makeText(MainActivity.this, "第" + position + "数据被点击", Toast.LENGTH_SHORT).show();
// });
mAdapter.setOnImageClickListener(position -> {
Toast.makeText(MainActivity.this, "第" + position + "图片被点击", Toast.LENGTH_SHORT).show();
});
}
public void onRemoveDataClick(View view) {
mAdapter.deleteData(0);
}
public void onInsertDataClick(View view) {
mAdapter.addData(0);
}
//改变mAdapter的布局方式
public void onChangeLayoutClick(View view) {
//从线性布局 --> 网格布局
if (mRecyclerView.getLayoutManager().getClass() == LinearLayoutManager.class) {
GridLayoutManager gridLayoutManager = new GridLayoutManager(this, 2);
mRecyclerView.setLayoutManager(gridLayoutManager);
}
//网格布局 --> 瀑布流布局
else if (mRecyclerView.getLayoutManager().getClass() == GridLayoutManager.class) {
StaggeredGridLayoutManager staggeredGridLayoutManager
= new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
mRecyclerView.setLayoutManager(staggeredGridLayoutManager);
}
//瀑布流布局 --> 线性布局
else if (mRecyclerView.getLayoutManager().getClass() == StaggeredGridLayoutManager.class) {
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(linearLayoutManager);
}
}
//创建并设置数据源
public void onAddDataClick(View view) {
for (int i = 0; i < 20; i++) {
String s = "第" + i + "条数据";
mDataSource.add(s);
}
mAdapter.setDataSource(mDataSource);
}
}
常见问题:
1. RecyclerViewAdapter 获取数据的时候空指针异常 java.lang.NullPointerException.
@Override
public int getItemCount() {
return mData.getTotal();
}
原因分析:
- 设置数据之前需要初始化数据
- 网络请求的数据需要在完成后加载 Adapter
解决方法:
public MyRecyclerViewAdapter(Context context) {
//初始化数据源,否则getItemCount会出现空指针
this.dataSource = new ArrayList<>();
this.mContext = context;
}
api.getAll().enqueue(new Callback<Result>() {
@Override
public void onResponse(Call<Result> call, Response<Result> response) {
Result result = response.body();
MovieAdapter adapter = new MovieAdapter(result);
mainBinding.rvList.setLayoutManager(new GridLayoutManager(MainActivity.this, 2));
mainBinding.rvList.setAdapter(adapter);
}
@Override
public void onFailure(Call<Result> call, Throwable t) {
Toast.makeText(MainActivity.this, "网络错误", Toast.LENGTH_SHORT).show();
}
});
2. RecyclerViewAdapter 加载图片卡顿严重
RecyclerView 滑动的时候在加载图片卡顿非常明显
public void bindMovieImg(String s) {
Glide.with(doubleItemBinding.getRoot().getContext())
.load(s)
.into(doubleItemBinding.ivMovieImg);
}
原因分析:
- ImageView 不要使用裁切,直接填充。
- glide 缓存的图片问题
解决方法:
android:scaleType 使用填充 ImageView 的大小,免去加载图片的时候还要裁切。
<ImageView
android:id="@+id/iv_movie_img"
android:layout_width="170dp"
android:layout_height="220dp"
android:scaleType="fitXY"
tools:src="@mipmap/p2455261804" />
3. RecyclerView 中的 item 无法居中显示
原因分析:
- RecyclerView 中的 layout_width 没有设置成 wrap_content 或者 match_parent。
解决方法:
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_list"
android:layout_width="wrap_content"
... />