RecyclerView是Android中一个强大的控件,不仅可以实现和ListView同样的效果,还有各种特殊的样式,比如网格布局、瀑布流布局,同时在列表布局中也可以实现横向翻滚和数据反向显示
RecyclerView的实现需要下面几个类进行辅助:
RecyclerView并没有被默认引用到项目中,所以在使用之前先配置一下build.gradle(:app) 文件
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation 'androidx.appcompat:appcompat:1.1.0'
// 引用RecyclerView
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}
这里的版本号1.1.0
要与上面的appcompat的版本号保持一致
不管是什么布局都要进行以下三步:
// 泛型:自定义ViewHolder,需继承RecyclerView.ViewHolder
public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewAdapter.MyViewHolder> {
private Context context;
private List<String> data;
private RecyclerView recyclerView;
// 这里传入RecyclerView对象是用于瀑布布局,如果是网格布局和线性布局可以不传
public MyRecyclerViewAdapter(Context context, RecyclerView recyclerView) {
this.context = context;
this.data = new ArrayList<>();
this.recyclerView = recyclerView;
}
public void setData(List<String> data) {
this.data = data;
// 刷新Adapter
notifyDataSetChanged();
}
/**
* 创建并返回ViewHolder
*
* @param parent
* @param viewType
* @return
*/
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
// 这里的R.layout.item_layout是单个子项的布局文件
return new MyViewHolder(LayoutInflater.from(context).inflate(R.layout.item_layout, parent, false));
}
/**
* ViewHolder绑定数据
*
* @param holder
* @param position
*/
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, final int position) {
holder.image.setImageResource(getIcon(position));
holder.text.setText(data.get(position));
}
/**
* 返回数据数量
*
* @return
*/
@Override
public int getItemCount() {
return data.size();
}
// 返回对应图片
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;
}
class MyViewHolder extends RecyclerView.ViewHolder {
ImageView image;
TextView text;
public MyViewHolder(@NonNull View itemView) {
super(itemView);
image = itemView.findViewById(R.id.image);
text = itemView.findViewById(R.id.text);
}
}
}
在MainActivity中完成实例化操作
private RecyclerView recyclerView;
private MyRecyclerViewAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = findViewById(R.id.recycler_view);
// 线性布局
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(linearLayoutManager);
adapter = new MyRecyclerViewAdapter(this, recyclerView);
recyclerView.setAdapter(adapter);
}
// 添加数据按钮
public void onAddDataClick(View v) {
List<String> data = new ArrayList<>();
for (int i = 1; i < 21; i++) {
String s = "第" + i + "条数据";
data.add(s);
}
adapter.setData(data);
}
横向和反向数据只需要对LinearLayoutManager的实例化进行设置即可
// 横向排列ItemView
linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
// 数据反向显示
linearLayoutManager.setReverseLayout(true);
横向排列显示
数据可以向右滑动
数据反向显示
从底部往上显示,可以向上滑动
网格布局使用的是GridLayoutManager,在前面的基础上,只需要增加两行代码即可转变为网格布局
// 切换布局按钮
public void onChangeLayoutClick(View v) {
// 从线型布局 切换为 网格布局
if (recyclerView.getLayoutManager().getClass() == LinearLayoutManager.class) {
// 网格布局
// 参数1:上下文 参数2:列数
GridLayoutManager gridLayoutManager = new GridLayoutManager(this, 2);
recyclerView.setLayoutManager(gridLayoutManager);
}
}
瀑布流布局使用的是StaggeredGridLayoutManager。它每个ItemView的宽度相等,但是高度不同,在实际开发中,每个ItemView的高度都是给定的,这里没有给定直接用随机数来实现
首先在MyRecyclerViewAdapter中增加一个随机数方法
// 返回随机数,用于瀑布布局下不同ItemView的高度(最少要等于原始高度)
private int getRandomHeight() {
return Math.max(ViewGroup.LayoutParams.WRAP_CONTENT, (int) (Math.random() * 1000));
}
然后在onBindViewHolder()
方法中设置随机高度
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, final int position) {
holder.image.setImageResource(getIcon(position));
holder.text.setText(data.get(position));
// 在RecyclerView的LayoutManager为瀑布流布局的时候对TextView使用随机高度
if (recyclerView.getLayoutManager().getClass() == StaggeredGridLayoutManager.class) {
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getRandomHeight());
holder.text.setLayoutParams(params);
} else {
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
holder.text.setLayoutParams(params);
}
}
在MainActivity的切换布局按钮点击方法onChangeLayoutClick()
方法中完成瀑布流布局的设置
// 切换布局按钮
public void onChangeLayoutClick(View v) {
// 从线型布局 切换为 网格布局
if (recyclerView.getLayoutManager().getClass() == LinearLayoutManager.class) {
// 网格布局
// 参数1:上下文 参数2:列数
GridLayoutManager gridLayoutManager = new GridLayoutManager(this, 2);
recyclerView.setLayoutManager(gridLayoutManager);
} else if (recyclerView.getLayoutManager().getClass() == GridLayoutManager.class) {
// 瀑布流布局
// 参数1:列数 参数2:排列方向(竖向、横向)
StaggeredGridLayoutManager staggeredGridLayoutManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
recyclerView.setLayoutManager(staggeredGridLayoutManager);
} else {
// 线性布局
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(linearLayoutManager);
}
}
RecyclerView是默认不支持点击事件,可以通过自定义接口回调ItemView的点击事件
在MyRecyclerViewAdapter类中,创建回调接口,并在自定义的MyViewHolder类里实例化一个View
对象
// ItemView点击事件回调接口
interface OnItemClickListener {
void onItemClick(int position);
}
class MyViewHolder extends RecyclerView.ViewHolder {
ImageView image;
TextView text;
View mItemView;
public MyViewHolder(@NonNull View itemView) {
super(itemView);
image = itemView.findViewById(R.id.image);
text = itemView.findViewById(R.id.text);
mItemView = itemView;
}
}
同时在onBindViewHolder()
方法中为ItemView设置点击事件
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, final int position) {
holder.image.setImageResource(getIcon(position));
holder.text.setText(data.get(position));
// 在RecyclerView的LayoutManager为瀑布流布局的时候使用随机高度
if (recyclerView.getLayoutManager().getClass() == StaggeredGridLayoutManager.class) {
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getRandomHeight());
holder.text.setLayoutParams(params);
} else {
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
holder.text.setLayoutParams(params);
}
// 为ItemView设置点击事件
holder.mItemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 调用接口的回调方法
if (onItemClickListener != null) {
onItemClickListener.onItemClick(position);
}
}
});
}
并在MyRecyclerViewAdapter类中创建一个setOnItemClickListener()
的方法
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener;
}
最后在MainActivity中实现接口为ItemView设置监听事件
// ItemView点击事件监听
adapter.setOnItemClickListener(new MyRecyclerViewAdapter.OnItemClickListener() {
@Override
public void onItemClick(int position) {
Toast.makeText(MainActivity.this, "第" + (position + 1) + "条数据被点击", Toast.LENGTH_SHORT).show();
}
});
在MyRecyclerViewAdapter类中增加数据的添加和删除方法
这里还要完成插入之后更新position
值的操作
// 添加一条数据,参数:添加的位置
public void addData(int position) {
addDataPosition = position;
data.add(position, "插入的数据");
// 设置插入动画(RecyclerView自带的动画显示效果,可省略)
notifyItemInserted(position);
// 刷新ItemView(刷新数据的position值)
notifyItemRangeChanged(position,data.size() - position);
}
//删除一条数据,参数:删除的位置
public void removeData(int position) {
addDataPosition = -1;
data.remove(position);
// 设置删除动画(RecyclerView自带的动画显示效果,可省略)
notifyItemRemoved(position);
// 刷新ItemView(刷新数据的position值)
notifyItemRangeChanged(position,data.size() - position);
}
为了清楚显示,在onBindViewHolder()
方法中设置一下插入的数据背景色
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, final int position) {
holder.image.setImageResource(getIcon(position));
holder.text.setText(data.get(position));
// 在RecyclerView的LayoutManager为瀑布流布局的时候使用随机高度
if (recyclerView.getLayoutManager().getClass() == StaggeredGridLayoutManager.class) {
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getRandomHeight());
holder.text.setLayoutParams(params);
} else {
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
holder.text.setLayoutParams(params);
}
// 改变插入的ItemView背景色
if (addDataPosition == position) {
holder.mItemView.setBackgroundColor(Color.RED);
}
// 为ItemView设置点击事件
holder.mItemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 调用接口的回调方法
if (onItemClickListener != null) {
onItemClickListener.onItemClick(position);
}
}
});
}
在MainActivity中增加两个按钮的点击事件,设定为在position = 1
处插入数据
// 添加一条数据,position = 1
public void onInsertDataClick(View v){
adapter.addData(1);
}
// 删除一条数据,position = 1
public void onRemoveDataClick(View v){
adapter.removeData(1);
}
在RecyclerView对象声明处,增加Animato
方法来实现增加或删除动画效果
recyclerView.setItemAnimator(new DefaultItemAnimator());
运行效果
点击插入数据,这时候插入数据的position
值为1,后面的position
值往后顺延
点击删除数据,这时后面的所有数据的position
值自动修正
RecyclerView的作用是在有限的空间里展示大批量的数据,它本身只负责回收以及复用View,需要与其它类进行搭配来完成展示效果, 如通过与Adapter的配合来实现数据和视图的连接,与LayoutManager的配合来确定数据的展示形式
RecyclerView并没有提供Item点击事件响应方法,需要自己实现
如果想要增加或删除数据,可以使用ItemAnimator来设置增加或删除动画效果
最后注意要通过notifyItemRangeChanged()
方法来刷新ItemView(数据的position
值)