前言
为了实现我们的本次的项目,也就是 CriminalIntent 应用中显示 crime 列表的功能,我们需要用 RecyclerView 来实现列表显示。
- 应用模型层将新增一个
CrimeLab对象,该对象是一个数据集中存储池,用来存储Crime对象 - 显示
crime列表需要在应用控制器层新增一个activity和一个fragmentCrimeListActivity和CrimeListFragment

8.1 升级 CriminalIntent 应用的模型
我们需要将应用的模型层,从容纳单个 Crime 对象变为可容纳一组 Crime 对象。
单例与数据集中存储
Crime数组对象将存储在一个单例里- 单例是特殊的
Java类,在创建实例时,一个单例类仅允许创建一个实例 - 应用能在内存中存活多久,单例就存活多久
- 单例是特殊的
- 单例
- 要创建单例,需要创建一个带有私有构造方法及
get()方法的类- 如果实例已经存在,
get()方法就直接返回它 - 如果实例不存在,
get()方法就会调用构造方法创建它
- 如果实例已经存在,
- 要创建单例,需要创建一个带有私有构造方法及
CrimeLab.java
1 | public class CrimeLab { |
sCrimeLab变量带有s前缀,这是 Android 开发的命名约定,一看到此前缀,我们就直到sCrimeLab是一个静态变量CrimeLab的构造方法是私有的,其他类无法创建CrimeLab对象,只能通过get()方法get()方法中,我们传入Context对象
接着,我们往 CrimeLab 中存储 Crime 对象:
- 在
CrimeLab的构造方法里,创建一个空List用来保存Crime对象 getCrime()用来返回数组列表getCrime(UUID)返回带指定ID的Crime对象
创建可容纳 Crime 对象的List(CrimeLab.java)
1 | public class CrimeLab { |
解析:
List<E>是一个泛型类,支持存放特定数据类型的有序列对象,拥有获取、新增和删除列表元素的方法mCrimes含有ArrayList:鉴于此,推荐在声明变量的时候使用List接口类型;这样在有需要的时候还可以使用其他List
mCrimes实例化语句使用的<>是在 Java 7 中引入的- 该符号告诉编译器,
List中的元素类型可以基于变量声明传入的抽象参数来确定 - 变量声明语句
private List<Crime> mCrimes中指定了Crime参数,所以编译器可据此推测出ArrayList里可放入Crime对象
- 该符号告诉编译器,
下面先批量存入 100 个 Crime 对象。
1 | private CrimeLab(Context context){ |
8.2 使用抽象 activity 托管 fragment
记得我们需要为 fragment 创建一个 activity 视图来容纳它吗?
创建托管 CrimeListFragment 的CrimeListActivity类之前,首先为 CrimeListActivity 创建视图。
通用型 fragment 托管布局
通用的布局定义文件 activity_fragment.xml 容器视图:
1 | <?xml version="1.0" encoding="utf-8"?> |
- 此处没有特别指定
fragment,任何使用activity托管fragment的场景,都可以使用它
抽象 activity 类
可以复用 CrimeActivity 的代码来创建 CrimeListActivity 类。
近乎通用的 CrimeActivity 类(CrimeActivity.java):
1 | public class CrimeActivity extends AppCompatActivity { |
可以看到这样的代码结构比较简单,而每一次新建一个 activity 都需要创建这样一段代码。所以我们可以将重复的代码封装为抽象类。(后文的 CrimeActivit 和CrimeListActivity都会用到此类代码,所以有复用价值)
创建一个 SingleFragmentActivity 的抽象类。设置超类为 AppCompatActivity 类。
创建一个 Activity抽象类(SingleFragmentActivity.java):
1 | public abstract class SingleFragmentActivity extends AppCompatActivity { |
可以看到上述方法除了在 * 号处将 new Fragment() 改为 createFragment() 抽象类方法外,其余和 CrimeActivity.java 中没什么区别。
我们要做的工作就是让 SingleFragmentActivity 的子类实现该方法,来返回 activity 托管的 fragment 实例。
- 使用抽象类
改动一下 CrimeActivity 类,将它的超类改为 SingleFragmentActivity。然后删除onCreate(Bundle) 方法,再添加createFragment。
清理 CrimeActivity 类(CrimeActivity.java):
1 | public class CrimeActivity extends SingleFragmentActivity { |
- 新建控制类
类似的,使用 SingleFragmentActivity 类来创建控制类。
实现CrimeListActivity(CrimeListActivity.java)
1 | public class CrimeListActivity extends SingleFragmentActivity() { |
当然我们还要实现CrimeListActivity.java,这样才能使用构造器创建。不过现在我们对这个控制类先留空:
CrimeListFragment.java:
1 | public class CrimeListFragment extends Fragment{ |
- 在配置文件中声明
CrimeListActivity
- 创建完
CrimeListActivity,记得要在配置文件中声明它 - 因为本程式启动时的主界面应该是
crime列表,因此还要在AndroidManifest.xml中声明为launch activity
1 | <activity android:name=".CrimeListActivity"> |
8.3 RecyclerView, ViewHolder和Adapter
RecyclerView是ViewGroup的子类- 每一个列表只显示
Crime的标题和日期 View是一个包含两个TextView的LinearLayout
- 每一个列表只显示

仔细看看图片里的 RecyclerView 和View的关系。
当前屏幕只显示了 12 个子View。不在视线中的子View,就不会被创建出来。
你滑动屏幕就会显示更多子项目,然后之前出现过的,而不在视线中的子 View 就会被回收。
RecyclerView所做的事情,就是创建视线中的子项以及回收再利用,循环往复RecyclerView的任务仅限于回收和定位屏幕上的View
ViewHolder
ViewHolder只做一件事:容纳View视图

下面是 典型的 ViewHolder 子类:
1 | public class ListRow extends RecyclerView.ViewHolder { |
我们可以创建 ListRow 来获取自定义的 mThumbnail 和RecyclerView.ViewHolder超类传入的itemView。
ViewHolder为itemView而生:它引用着传给super(view)的整个视图
ViewHolder的使用示例:
1 | ListRow row = new ListRow(inflater.inflate(R.layout.list_row, parent, false)); |
RecyclerView本身不会创建视图,它创建的是ViewHolder,而ViewHolder引用着itemView

Adapter
RecyclerView自己不创建ViewHolder- 这个任务交给了
Adapter
- 这个任务交给了
Adapter是一个控制器对象,从模型层获取数据,然后提供给RecyclerView显示,是沟通的桥梁Adapter负责- 创建必要的
ViewHolder - 绑定
ViewHolder至模型层数据
- 创建必要的
- 要创建
Adapter,首先要定义RecyclerView.Adapter子类- 然后由它封装从
CrimeLab获取的crime
- 然后由它封装从
RecyclerView需要显示视图对象时,就会找他的Adapter

RecyclerView-Adapter对话
- 首先,调用
Adapter的getItemCount()方法,RecyclerView询问数组列表中包含多少个对象 - 接着,
RecyclerView调用Adapter的onCreateViewHolder(ViewGroup, int)方法创建ViewHolder及其要显示的视图 - 最后,
RecyclerView会传入ViewHolder及其位置,调用onBindViewHolder(ViewHolder, int)Adapter会找到目标位置的数据并将其绑定到ViewHolder视图上- 绑定:使用模型数据填充视图
onCreateViewHolder(ViewGroup, int)方法调用并不频繁- 一旦有了够用的
ViewHolder,RecyclerView就会停止调用ViewHolder(...)方法 - 它会回收
ViewHolder以节约时间和内存
- 一旦有了够用的
使用RecyclerView
- 添加依赖
你可以在 build.gradle 文件中直接写入 RecyclerView 的依赖库。不过我觉得更好的方法是在 Project Structure–>app–>Dependencies 添加依赖。这样会直接搜索最新版的依赖项。
- 修改
.xml文件,将根视图改为RecyclerView,并配置 ID 属性
在布局文件中添加 RecyclerView 视图(fragment_crime_list.mxl)
1 | <?xml version="1.0" encoding="utf-8"?> |
- 关联视图和
fragment
修改 CrimeListFragment.java 类文件,使用布局并找到布局中的 RecyclerView 视图。
为 CrimeListFRagment 配置视图(CrimeListFragment.java)
1 | public class CrimeListFragment extends Fragment { |
RecyclerView视图创建完成之后,立即转交给了LayoutManager对象LayoutManager对象负责在屏幕上摆放列表项还负责定义屏幕滚动行为,因此没有了它,RecyclerView没法正常工作,应用可能会崩溃
目前实现了一个 RecyclerView 空视图。要显示出 crime 列表项,还需要完成 Adapter 和ViewHolder的实现。
列表项视图
我们要为 RecyclerView 上的列表项创建视图层级结构。
list_item_crime.xml:
1 | <?xml version="1.0" encoding="utf-8"?> |
实现 ViewHolder 和Adapter
接着我们在 CrimeListFragment 类中定义 ViewHolder 内部类,它会实例化并使用 list_item_crime 布局。
定义 ViewHolder 内部类(CrimeListFragment.java):
1 | public class CrimeListFragment extends Fragment { |
- 在
CrimeHolder的构造方法里,我们首先实例化list_item_crime布局,然后传给super(...)方法,也就是ViewHolder构造方法 - 基类
ViewHolder因而实际上引用这个视图- 我们可以在
itemView变量中找到它
- 我们可以在
接下来实现Adapter。
创建 Adapter 内部类(CrimeListFragment.java)
1 | public class CrimeListFragment extends Fragment { |
- 需要显示新创建的
ViewHolder或让Crime对象和已创建的ViewHolder关联时,RecyclerView会去找Adapter(调用它的方法)RecyclerView不关心也不了解具体的Crime对象,这是Adapter要做的事
我们还需要在 Crimedapter 中实现三个方法:
武装CrimeAdapter(CrimeListFragment.java)
1 | ... |
RecyclerView需要新的ViewHolder来显示列表时,会调用onCreateViewHolder方法- 这个方法内部,我们创建一个
LayoutInflater,然后用它创建CrimeHolder
- 这个方法内部,我们创建一个
- 关联
Adapter和RecyclerView- 实现一个设置
CrimeListFragment用户界面的updateUI方法- 该方法创建
CrimeAdapter,然后设置给RecyclerView
- 该方法创建
- 实现一个设置
设置Adapter(CrimeListFragment.java)
1 | public class CrimeListAdapter extends Fragment { |
现在已经实现了基本的列表内容了。可以编译运行看看啦。
8.4 绑定列表项
- 绑定:让 Java 代码(
Crime里的模型数据,或点击监听器)和组件关联起来- 因为
CrimeHolder会循环使用,分开处理视图创建和绑定会有好处 - 我们把视图绑定工作放入
CrimeHolder类里- 绑定之前,首先实例化相关组件;此项工作是一次性任务,因此直接放在构造方法里处理
- 因为
在构造方法中实例化视图组件(CrimeListFragment.java)
1 | private class CrimeHolder extends RecyclerView.ViewHolder { |
CrimeHolder还需要实现一个bind(Crime)方法- 每次有新的
Crime要在CrimeHolder中显示时,都要调用它一次
- 每次有新的
实现 bind(Crime) 方法(CrimeListFragment.java)
1 | public void bind(Crime crime) { |
现在只要取得一个
Crime,CrimeHolder就会刷新显示TextView标题视图和TextView日期视图最后修改
CrimeAdapter类,使用bind(Crime)方法:每次RecyclerView要求CrimeHolder绑定对应的Crime时,都会调用bind(Crime)方法。
调用bind(Crime)(CrimeListFragment.java)
1 | private class CrimeAdapter extends RecyclerView.Adapter<CrimeHolder> { |
8.5 响应点击
一般来说,RecyclerView只处理列表项相关工作,而触摸事件是需要我们自己实现的。
实现触摸事件常用方案就是设置 OnClickListener 监听器。既然列表项视图都关联着 ViewHolder,就可以让ViewHolder 为它监听用户触摸事件。
检测用户点击事件(CrimeListFragment.java)
1 | private class CrimeHolder extends RecyclerView.ViewHolder |