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