RecyclerView初识
# 1. RecyclerBiew简介
# 1.1 RecyclerView是什么
从Android 5.0开始,谷歌公司推出了一个用于大量数据展示的新控件RecylerView,可以用来代替传统的ListView,更加强大和灵活。可以说,每个APP都可以看到RecyclerView的影子。RecyclerView的官方定义如下:
一种灵活的视图,用于提供进入大型数据集的有限窗口。
从定义可以看出,flexible(可扩展性)是RecyclerView的特点。
RecyclerView 可以让您轻松高效地显示大量数据。您提供数据并定义每个列表项的外观,而 RecyclerView 库会根据需要动态创建元素。
顾名思义,RecyclerView 会回收这些单个的元素。当列表项滚动出屏幕时,RecyclerView 不会销毁其视图。相反,RecyclerView 会对屏幕上滚动的新列表项重用该视图。这种重用可以显著提高性能,改善应用响应能力并降低功耗。
# 1.2 RecyclerView的优点
RecyclerView能够实现ListIVew列表,横向滚动的ListView, 横向滚动的GridView,瀑布流控件等;
比如:有一个需求是屏幕竖着的时候的显示形式是ListView,屏幕横着的时候的显示形式是2列的GridView,此时如果用RecyclerView,则通过设置LayoutManager一行代码实现替换。
RecylerView相对于ListView的优点罗列如下:
1. RecyclerView标准化了ViewHolder
RecyclerView封装了viewholder的回收复用,也就是说,编写Adapter面向的是ViewHolder而不再是View了,复用的逻辑被封装了,写起来更加简单。直接省去了listview中convertView.setTag(holder)
和convertView.getTag()
这些繁琐的步骤。
2. 提供了一种插拔式的体验,高度的解耦,异常的灵活。
针对一个Item的显示RecyclerView专门抽取出了相应的类,来控制Item的显示,使其的扩展性非常强。
3. 设置布局管理器以控制Item的布局方式,横向、竖向以及瀑布流方式
例如:你想控制横向或者纵向滑动列表效果可以通过LinearLayoutManager这个类来进行控制(与GridView效果对应GridLayoutManager,与瀑布流对应的还StaggeredGridLayoutManager等。也就是说RecyclerView不再拘泥于ListView的线性展示方式,它也可以实现GridView的效果等多种效果。
4.可设置Item的间隔样式(可绘制)。
通过继承RecyclerView的ItemDecoration这个类,然后针对自己的业务需求去书写代码。可以控Item增删的动画,可以通过ItemAnimator这个类进行控制,当然针对增删的动画,RecyclerView有其自己默认的实现。
但是关于Item的点击和长按事件,需要用户自己去实现。
# 2. RecyclerView工作原理简介
RecyclerView涉及的内容很多,此处原理只是简单介绍其工作原理,详情部分后面章节详细说明
# 2.1 原理简介
RecyclerView 是一个容器,它用于显示列表形式 (list) 网格形式 (grid) 的数据或者瀑布流的数据
当列表滑动的时候,实际上只有少量邻近的视图会显示在屏幕上。当视图滑出屏幕时,RecyclerView 会复用它并且填充新的数据。由于它是通过回收已有的结构而不是持续创建新的列表项,所以它可以有效提高应用的时间效率和空间效率。
粉红色的方格表示屏幕上正在显示的表项,黄色的方格表示屏幕可视范围之外的表项是如何被回收并转为新的视图。RecyclerView 使用 ViewHolder 模式,这样做可以提高性能,因为它无需频繁调用 findViewById()
方法即可访问表项的视图,ListView也可以做到这一点
# 2.2 Adapter 类
接下来是 RecyclerView
的重头戏了,也就是 ViewHolder (opens new window) 和 Adapter (opens new window) 类。
ViewHolder 负责存储 RecyclerView 中每一个单独的表项所需要显示的信息。RecyclerView 仅需要创建当前所显示的表项数量的 ViewHolder 外加缓存中的几个 ViewHolder 即可。随着用户滑动屏幕,ViewHolder会被回收 (使用新数据进行填充),已有的表项会在一端消失,并且在另一端显示一个新的表项。
ViewHolder与item View是一一对应关系
- onCreateViewHolder方法就是用来创建新View;
- getItemViewType方法是可以根据不同的position可以返回不同的类型;
- onBindViewHolder是将数据与视图绑定;
- getItemCount方法就是获得需要显示数据的总数。
Adapter 类从数据源获得数据,并且将数据传递给正在更新其所持视图的 ViewHolder。
如图显示了 RecyclerView、Adapter、ViewHolder 和数据之间的协作关系。
# 2.3 官方使用文档
# 官方关键类解释
https://developer.android.google.cn/guide/topics/ui/layout/recyclerview?hl=zh-cn (opens new window)
将多个不同的类搭配使用,可构建动态列表。
RecyclerView
(opens new window) 是包含与您的数据对应的视图的ViewGroup
(opens new window)。它本身就是视图,因此,将RecyclerView
添加到布局中的方式与添加任何其他界面元素相同(xml添加RecyclerView与添加其他控件方式一样)- 列表中的每个独立元素都由一个 ViewHolder 对象进行定义。创建 ViewHolder 时,它并没有任何关联的数据。创建 ViewHolder 后,
RecyclerView
会将其绑定到其数据。您可以通过扩展RecyclerView.ViewHolder
(opens new window) 来定义 ViewHolder。 RecyclerView
会请求这些视图,并通过在 Adapter 中调用方法,将视图绑定到其数据。您可以通过扩展RecyclerView.Adapter
(opens new window) 来定义 Adapter。- 布局管理器负责排列列表中的各个元素。您可以使用 RecyclerView 库提供的某个布局管理器,也可以定义自己的布局管理器。布局管理器均基于库的
LayoutManager
(opens new window) 抽象类。
总结来就是,使用RecyclerView时,需要在XML布局中添加控件、再定义Adapter和ViewHolder,在后通过布局管理器来管理显示的元素。
# 3. RecyclerView的使用
# 3.1 规划布局
RecyclerView 中的列表项由 LayoutManager
(opens new window) 类负责排列。RecyclerView 库提供了三种布局管理器,用于处理最常见的布局情况:
LinearLayoutManager
(opens new window) 将各个项排列在一维列表中。GridLayoutManager
将所有项排列在二维网格中:- 如果网格垂直排列,
GridLayoutManager
会尽量使每行中所有元素的宽度和高度相同,但不同的行可以有不同的高度。 - 如果网格水平排列,
GridLayoutManager
会尽量使每列中所有元素的宽度和高度相同,但不同的列可以有不同的宽度。
- 如果网格垂直排列,
StaggeredGridLayoutManager
(opens new window) 与GridLayoutManager
类似,但不要求同一行中的列表项具有相同的高度(垂直网格有此要求)或同一列中的列表项具有相同的宽度(水平网格有此要求)。其结果是,同一行或同一列中的列表项可能会错落不齐。
# 3.2 实现 Adapter 和 ViewHolder
确定布局后,需要实现 Adapter
和 ViewHolder
。这两个类配合使用,共同定义数据的显示方式。
ViewHolder
是包含列表中各列表项的布局的 View
的封装容器。
Adapter
会根据需要创建 ViewHolder
对象,还会为这些视图设置数据。将视图与其数据相关联的过程称为“绑定”。
定义 Adapter 时,您需要替换三个关键方法:
- [
onCreateViewHolder()
](https://developer.android.google.cn/reference/androidx/recyclerview/widget/RecyclerView.Adapter?hl=zh-cn#onCreateViewHolder(android.view.ViewGroup, int)):每当RecyclerView
需要创建新的ViewHolder
时,它都会调用此方法。此方法会创建并初始化ViewHolder
及其关联的View
,但不会填充视图的内容,因为ViewHolder
此时尚未绑定到具体数据。 - [
onBindViewHolder()
](https://developer.android.google.cn/reference/androidx/recyclerview/widget/RecyclerView.Adapter?hl=zh-cn#onBindViewHolder(VH, int)):RecyclerView
调用此方法将ViewHolder
与数据相关联。此方法会提取适当的数据,并使用该数据填充 ViewHolder 的布局。例如,如果RecyclerView
显示的是一个名称列表,该方法可能会在列表中查找适当的名称,并填充 ViewHolder 的TextView
(opens new window) widget。 getItemCount()
(opens new window):RecyclerView 调用此方法来获取数据集的大小。例如,在通讯簿应用中,这可能是地址总数。RecyclerView 使用此方法来确定什么时候没有更多的列表项可以显示。
基本实现如下:
mRecyclerView = (RecyclerView) findViewById(R.id.recyclerView);
LinearLayoutManager layoutManager = new LinearLayoutManager(this );
//设置布局管理器
mRecyclerView.setLayoutManager(layoutManager);
//设置为垂直布局,这也是默认的
mRecyclerView.setOrientation(OrientationHelper. VERTICAL);
//设置Adapter
mRecyclerView.setAdapter(recycleAdapter);
//设置分隔线
mRecyclerView.addItemDecoration( new DividerGridItemDecoration(this ));
//设置增加或删除条目的动画
mRecyclerView.setItemAnimator( new DefaultItemAnimator());
2
3
4
5
6
7
8
9
10
11
12
在使用RecyclerView时候,必须指定一个适配器Adapter和一个布局管理器LayoutManager。适配器继承**RecyclerView.Adapter
**类,具体实现类似ListView的适配器,取决于数据信息以及展示的UI。布局管理器用于确定RecyclerView中Item的展示方式以及决定何时复用已经不可见的Item,避免重复创建以及执行高成本的findViewById()
方法。
以上内容来自官方文档:https://developer.android.google.cn/guide/topics/ui/layout/recyclerview?hl=zh-cn
# 4. 实现基础列表
我们实现如下最基本的Demo,直接使用androidx库,不需要做额外添加依赖
# 4.1 布局内容
主布局文件activity_main
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:orientation="vertical"
tools:context=".MainActivity">
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="10dp"
android:text="RecyclerView"
android:textSize="30sp"
android:textStyle="bold" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ced6e0" />
</LinearLayout>
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
item布局文件item_view
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
android:orientation="vertical">
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="110dp"
android:background="#FFFFFF"
app:cardUseCompatPadding="true">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_gravity="center_vertical">
<ImageView
android:id="@+id/itemImage"
android:layout_width="90dp"
android:layout_height="90dp"
android:layout_centerVertical="true"
android:layout_margin="1dp"
android:scaleType="fitXY"
android:src="@mipmap/ic_launcher" />
<TextView
android:id="@+id/itemContent"
android:layout_width="match_parent"
android:layout_height="120dp"
android:layout_marginLeft="10dp"
android:layout_toRightOf="@id/itemImage"
android:gravity="center_vertical"
android:text="test"
android:textSize="20sp" />
</RelativeLayout>
</androidx.cardview.widget.CardView>
</RelativeLayout>
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
# 4.2 ViewHolder item中的控件
创建一个叫做 CommonViewHolder的类,并且它可以接收一个 itemView 作为参数。在 ViewHolder 中,用来缓存item中的子控件,避免不必要的findViewById
public class CommonViewHolder extends RecyclerView.ViewHolder {
ImageView imageView;
TextView textView;
public CommonViewHolder(@NonNull View itemView) {
super(itemView);
imageView = itemView.findViewById(R.id.itemImage);
textView = itemView.findViewById(R.id.itemContent);
}
}
2
3
4
5
6
7
8
9
10
11
# 4.3 Adapter
Adapter来决定每个item显示什么内容、item相关的逻辑都在Adapter中实现
创建CommonAdapter,继承 RecyclerView.Adapter
,需要重写三个方法 onCreateViewHolder()
、onBindViewHolder()
和 getItemCount()
重写 onCreateViewHolder()
当 ViewHolder
创建的时候会调用该方法。在该方法里进行初始化和填充 RecyclerView
中的表项视图。该视图使用前面我们创建的用于显示文本的布局。
@NonNull
@Override
public CommonViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = inflater.inflate(R.layout.list_item_view, null);
CommonViewHolder holder = new CommonViewHolder(view);
return holder; // 传入的View就是条目界面
}
2
3
4
5
6
7
重写 onBindViewHolder()
onBindViewHolder()
被调用的时候,会传入参数 ViewHolder
和一个位置 (position),它表示在 List 中所绑定的表项的位置。该位置可以用于提取表项所需的数据,并且将数据传递给 ViewHolder 来使数据绑定到对应的 UI。
@Override
public void onBindViewHolder(@NonNull CommonViewHolder holder, int position) {
ItemData itemData = mList.get(position);
holder.imageView.setImageResource(itemData.getImageId());
holder.textView.setText(itemData.getName());
}
2
3
4
5
6
重写 getItemCount()
RecyclerView 显示一个列表,所以它需要知道列表里共有多少项。由于List 就是数据源,所以直接返回它的长度即可。
@Override
public int getItemCount() {
return mList == null ? 0 : mList.size();
}
2
3
4
完整Adapter代码,包含item的点击事件
item点击事件实现参考:🎨
public class CommonAdapter extends RecyclerView.Adapter<CommonViewHolder> {
private Context mContext;
private List<ItemData> mList;
private LayoutInflater inflater;
private OnItemClickListener onClickListener;
// 初始化Data
public CommonAdapter(Context context) {
mContext = context;
inflater = LayoutInflater.from(context);
}
public void setData(List<ItemData> list) {
this.mList = list;
}
public void setOnItemClickListener(OnItemClickListener clickListener) {
this.onClickListener = clickListener;
}
/**
* 创建ViewHolder实例
* @param parent
* @param viewType
* @return
*/
@NonNull
@Override
public CommonViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = inflater.inflate(R.layout.list_item_view, null);
CommonViewHolder holder = new CommonViewHolder(view);
return holder; // 传入的View就是条目界面
}
/**
* 对RecyclerView子项的数据进行赋值的,会在每个子项被滚动到屏幕内的时候执行
* @param holder
* @param position
*/
@Override
public void onBindViewHolder(@NonNull CommonViewHolder holder, int position) {
ItemData itemData = mList.get(position);
holder.imageView.setImageResource(itemData.getImageId());
holder.textView.setText(itemData.getName());
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(mContext, "item click", Toast.LENGTH_SHORT).show();
onClickListener.onItemClick(holder.getAdapterPosition());
}
});
}
/**
* item条目个数
* @return
*/
@Override
public int getItemCount() {
return mList == null ? 0 : mList.size();
}
public interface OnItemClickListener {
void onItemClick(int position);
}
}
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
# 4.4 MainActivity
为item构造数据源
定义一个实体类,来承载数据
public class ItemData {
private String name;
protected int imageId;
public ItemData() { }
public ItemData(String name, int imageId) {
this.name = name;
this.imageId = imageId;
}
public String getName() {
return name;
}
public int getImageId() {
return imageId;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
MainActivity的逻辑
/**
* @Author: iqqcode
* @Date: 2022/3/5
* @Description: RecyclerView上手使用
*/
public class MainActivity extends AppCompatActivity {
private RecyclerView mRecyclerView;
private CommonAdapter mAdapter;
private List<ItemData> dataList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initData();
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(layoutManager);
mAdapter = new CommonAdapter(this);
mAdapter.setData(dataList); // 用set方法或者构造都可以将dataList传入Adapter中
mRecyclerView.setAdapter(mAdapter);
mAdapter.setOnItemClickListener(new CommonAdapter.OnItemClickListener() {
@Override
public void onItemClick(int position) {
// 点击item是,将该item的数据传递到跳转的NewActivity处
Intent intent = new Intent(MainActivity.this, NewActivity.class);
intent.putExtra("itemName", dataList.get(position).getName());
startActivity(intent);
}
});
}
private void initView() {
mRecyclerView = findViewById(R.id.recycler_view);
}
private void initData() {
for (int i = 0; i < 3; i++) {
ItemData apple = new ItemData("Apple", R.mipmap.ic_launcher);
dataList.add(apple);
ItemData banana = new ItemData("Banana", R.mipmap.ic_launcher);
dataList.add(banana);
ItemData orange = new ItemData("Orange", R.mipmap.ic_launcher);
dataList.add(orange);
ItemData watermelon = new ItemData("Watermelon", R.mipmap.ic_launcher);
dataList.add(watermelon);
ItemData pear = new ItemData("Pear", R.mipmap.ic_launcher);
dataList.add(pear);
ItemData grape = new ItemData("Grape", R.mipmap.ic_launcher);
dataList.add(grape);
ItemData pineapple = new ItemData("Pineapple", R.mipmap.ic_launcher);
dataList.add(pineapple);
ItemData strawberry = new ItemData("Strawberry", R.mipmap.ic_launcher);
dataList.add(strawberry);
ItemData cherry = new ItemData("Cherry", R.mipmap.ic_launcher);
dataList.add(cherry);
ItemData mango = new ItemData("Mango", R.mipmap.ic_launcher);
dataList.add(mango);
}
}
}
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
NewActivity跳转到的新Activity
public class NewActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_new);
Intent intent = getIntent();
String itemName = intent.getStringExtra("itemName");
TextView textView = findViewById(R.id.activityText);
textView.setText(itemName);
}
}
2
3
4
5
6
7
8
9
10
11
12
13