一,简介
1. RecyclerView、Adapter和 ViewHolder的关系
我们需要CrimeListFragment向用户展示crime列表,这就要用到RecyclerView类。RecyclerView是ViewGroup的子类,每一个列表项都是作为一个View子对象显示的。这些View子对象既可以是复杂的View对象,也可以是简单的View对象,这取决于我们对列表显示复杂度的需要
RecyclerView的任务仅限于回收和定位屏幕上的TextView。TextView能够显示数据还离不开另外两个类的支持:Adapter子类和ViewHolder子类。ViewHolder要做的事很少,我们首先讨论它。顾名思义,ViewHolder只做一件事:容纳View视图
adapter
图9-6进行了简化,实际上隐藏了一些信息。RecyclerView自己不创建ViewHolder。这个任务实际是由adapter来完成的。adapter是个控制器对象,从模型层获取数据,然后提供给RecyclerView显示,起到了沟通的桥梁作用。
adapter负责:
创建必要的ViewHolder;
绑定ViewHolder至模型层数据。
要创建adapter,首先要定义RecyclerView.Adapter子类。然后由它封装从CrimeLab获取的crime。
RecyclerView需要显示视图对象时,就会去找它的adapter。图9-7展示了一个RecyclerView可能发起的会话。
首先,通过调用adapter的getItemCount()方法,RecyclerView询问数组列表中包含多少个对象。
接 着 , RecyclerView 调 用adapter的 createViewHolder(ViewGroup, int)方 法 创 建ViewHolder以及ViewHolder要显示的视图。
最后, RecyclerView会传入ViewHolder及其位置,调用onBindViewHolder(ViewHolder,int)方法。adapter会找到目标位置的数据并绑定到ViewHolder的视图上。所谓绑定,就是使用模型数据填充视图。
整个过程执行完毕, RecyclerView就能在屏幕上显示crime列表项了。需要注意的是,相对于onBindViewHolder(ViewHolder, int)方法,createViewHolder(ViewGroup, int)方法
的调用并不频繁。一旦创建了够用的ViewHolder,RecyclerView就会停止调用createViewHolder(...)方法。然后,通过回收利用旧的ViewHolder节约时间和内存。
实质上,recycler类似于安卓内联的listview,只是其是支持库而已
添加支持库:
单击File→Project Structure....菜单项切换至项目结构窗口,选择左边的app模块,然后单击Dependencies选项页。单击+按钮弹出依赖库添加窗口。找到并选择recyclerview-v7支持库,单击OK按钮完成依赖库添加,
例程:该例子是基于fragment显示的
1. RecyclerView的XML,Recycler界面用于容乃列表项:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/crime_recycler_view"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v7.widget.RecyclerView>
2. 列表项的布局XML,用于设置每一项的布局:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/crime_list_LabelText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textStyle="bold"
android:padding="4dp"
android:layout_toLeftOf="@+id/crime_list_CheckBox"
android:text="TextView"/>
<TextView
android:id="@+id/crime_list_DateText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toLeftOf="@+id/crime_list_CheckBox"
android:layout_below="@id/crime_list_LabelText"
android:padding="4dp"
android:text="TextView"/>
<CheckBox
android:id="@+id/crime_list_CheckBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:text="CheckBox"
android:padding="4dp"/>
</RelativeLayout>
3. Crime类,自定义类,表示每个元素的数据,模型里的单个数据结构:
//基本元素单位类
public class Crime {
private UUID yID;
private String yTitle;
private Date yDate;
private boolean CrimeFlag;
public Crime() {
yID = UUID.randomUUID();
yDate = new Date();
}
public UUID getyID() {
return yID;
}
public String getyTitle() {
return yTitle;
}
public void setyTitle(String yTitle) {
this.yTitle = yTitle;
}
public Date getyDate() {
return yDate;
}
public void setyDate(Date yDate) {
this.yDate = yDate;
}
public boolean getCrimeFlag() {
return CrimeFlag;
}
public void setCrimeFlag(boolean crimeFlag) {
CrimeFlag = crimeFlag;
}
}
4. List类,模型本身:
//元素组合类
public class CrimeListLab {
private static CrimeListLab yCrimeLab;
private List<Crime>yCrime;
private CrimeListLab(Context context)
{
yCrime = new ArrayList<>();
for(int i=0;i<100;i++){
Crime crime = new Crime();
crime.setyTitle("Crime #" + i);
crime.setCrimeFlag(i%2 == 0);
yCrime.add(crime);
}
}
public static CrimeListLab getyCrimeLab(Context context) {
if(null == yCrimeLab){
yCrimeLab = new CrimeListLab(context);
}
return yCrimeLab;
}
public List<Crime> getyCrime() {
return yCrime;
}
public Crime getCrime(UUID uuid) {
for (Crime crime:yCrime) {
if(crime.getyID().equals(uuid)){
return crime;
}
}
return null;
}
}
5. fragment的内容,也是决定Recycler如何显示:
//控制RecyclerView的Fragment
public class CrimeListFragment extends Fragment{
//RecyclerView对象
private RecyclerView yCrimeRecyclerView;
//Adapter对象
private CrimeAdapter yCrimeAdapter;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
//绑定布局
View view = inflater.inflate(R.layout.fragment_crime_list, container, false);
//绑定并实例化Recycler
yCrimeRecyclerView = (RecyclerView) view
.findViewById(R.id.crime_recycler_view);
yCrimeRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
//自定义的将Adapter与Recycler绑定的函数
updateUI();
return view;
}
//自定义的将Adapter与Recycler绑定的函数
public void updateUI(){
CrimeListLab crimelistlab = CrimeListLab.getyCrimeLab(getActivity());
List<Crime>crimeList = crimelistlab.getyCrime();
yCrimeAdapter = new CrimeAdapter(crimeList);
yCrimeRecyclerView.setAdapter(yCrimeAdapter);
}
//继承Holder的内部类定义,该类实例化布局内部组件资源
//此处还继承监听事件接口
public class CrimeHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
private Crime yCrime;//用于接受Crime元素对象
private TextView yTitleLabel;
private TextView yTitleDate;
private CheckBox yCheckBox;
public CrimeHolder(View itemView) {
super(itemView);
itemView.setOnClickListener(this);//监听
//关联布局内的组件资源实例化
yTitleLabel = (TextView) itemView.findViewById(R.id.crime_list_LabelText);
yTitleDate = (TextView) itemView.findViewById(R.id.crime_list_DateText);
yCheckBox = (CheckBox) itemView.findViewById(R.id.crime_list_CheckBox);
}
//自定义方法,用于在Adapter的视图和数据绑定函数调用以实现数据绑定
public void bindCrime(Crime crime){
yCrime = crime;//通过每个传进来的Crime元素来获取数据
yTitleLabel.setText(yCrime.getyTitle());
yTitleDate.setText(yCrime.getyDate().toString());
yCheckBox.setChecked(yCrime.getCrimeFlag());
}
//监听事件的相应
@Override
public void onClick(View v){
Toast.makeText(getActivity(),
yCrime.getyTitle() + " clicked!", Toast.LENGTH_SHORT)
.show();
}
}
//决定项的布局,项的数目,并将项和数据模型绑定的Adpater类
public class CrimeAdapter extends RecyclerView.Adapter<CrimeHolder>{
//用于接收存放List资源的对象
private List<Crime> yCrimelist;
//构造类,内部用于接受存放List资源
public CrimeAdapter(List<Crime>yCrimelist){//可能有内外部类同名隐患
this.yCrimelist = yCrimelist;
}
//创建项视图,关联项布局
@Override
public CrimeHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
//寻找项的布局文件
View view = layoutInflater.inflate(R.layout.fragment_crime_list_item,parent,false);
return new CrimeHolder(view);
}
//将视图和数据模型绑定
@Override
public void onBindViewHolder(CrimeHolder holder, int position) {
Crime crime = yCrimelist.get(position);//获取每个crime数据
holder.bindCrime(crime);//调用CrimeHolder的方法将数据传递并更新
}
//返回一共有多少项
@Override
public int getItemCount() {
return yCrimelist.size();
}
}
}
6. 托管fragment的Activity
//在Activity建立fragmentmanager来托管xml布局,此时继承的是通用ActivityFragmentManager抽象类
public class CrimeListActivity extends CrimeActivity {
@Override
protected Fragment createFragment(){
return new CrimeListFragment();
}
}
7. 其他,托管fragment的Activity的超类:
//创建通用的ActivityFragmentManager的抽象类,防止重复代码
public abstract class CrimeActivity extends FragmentActivity {
protected abstract Fragment createFragment();//抽象创建Fragment布局接口
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.crime_layout);
FragmentManager fm = getSupportFragmentManager();
Fragment yFragment = fm.findFragmentById(R.id.fragment_container);
if(null == yFragment){
yFragment = createFragment();//用抽象接口创建Fragment布局,可以根据需要创建不同接口
fm.beginTransaction()
.add(R.id.fragment_container,yFragment)
.commit();
}
}
}
深入学习: ListView 和 GridView
Android操作系统核心库包含ListView、 GridView和Adapter类。 Android 5.0之前,创建列表项或网格项都应该优先使用这些类。
这些类的API与RecyclerView非常相似。 ListView和GridView不关心具体的展示项,只负责展示项的滚动。 Adapter负责创建列表项的所有视图。不过,使用ListView和GridView不一定非要使用ViewHolder模式(虽然可以并且应该使用)。
过去传统的实现方式现已被RecyclerView的实现方式取代,因为我们不用再费力地去调整ListView和GridView的工作行为了。举例来说, ListView API不支持创建水平滚动的ListView,我们需要许多额外的定制工作。
使用RecyclerView时,虽然创建定制布局和滚动行为需要额外的工作,但RecyclerView天生支持拓展,所以使用体验还不错。
此外, RecyclerView还有支持列表项动画效果的优点。如果要让ListView和GridView支持添加和删除列表项的动画效果,实施任务既复杂又容易出错;而对于天生支持动画特效的RecyclerView来说,对付这些任务简直是小菜一碟。
口吐莲花,不如直接秀代码。例如,如果crime列表项要从位置0移动到位置5,下面这段代码就可以做到:
mRecyclerView.getAdapter().notifyItemMoved(0, 5);
深入学习:单例
Android开发实践中,经常会用到CrimeLab中使用过的单例模式。然而,单例使用不当的话,会导致应用难以维护,因此它也常遭人诟病。
Android开发常用到单例的一大原因是,它们比fragment或activity活得久。例如,在设备旋转或是在fragment和activity间跳转的场景下,单例不会受到影响,而旧的fragment或activity已经不复存在了。
单例能方便地存储控制模型层对象。假设有个比CriminalIntent更为复杂的CriminalIntent应用,它的许多个activity和fragment会修改crime数据。某个控制单元修改了crime数据之后,怎么保证发送给其他控制单元的是最新数据呢?如果CrimeLab掌控数据对象,所有的修改都由它来处理,是不是数据的一致性控制就容易多了?而且,在控制单元间流转时,我们还可以给每个crime添加ID标识,让控制单元使用ID标识从CrimeLab获取完整的crime数据。
再来谈谈单例的缺点。举个例子,虽然单例能存储数据,活得比控制单元长久,但这并不代表它能永存。在我们切换至其他应用,又逢Android回收内存时,单例连同那些实例变量也就不复存在了。结论很明显:单例无法做到持久存储。(将文件写入磁盘或是发送到Web服务器是不
错的数据持久化存储方案。)
单例还不利于单元测试。例如,如果应用代码直接调用CrimeLab对象的静态方法,测试时以模拟版本的CrimeLab代替实际CrimeLab实例就不太现实。实践中, Android开发人员会使用依赖注入工具解决这个问题。这个工具允许以单例模式使用对象,对象也可以按需替换。
单例使用很方便,因而很容易被滥用。在想用就用,想存就存之前,希望你能深思熟虑:数据究竟用在哪里?用在哪里能真正解决问题?假如不慎重对待这个问题,很可能后来人在查看你的单例代码时,就像打开了一个满是乱糟糟废品的抽屉:废电池、拉链扣、旧照片,等等。它们有什么存在的意义?再强调一次:请确保有充足的理由使用单例模式存储你的共享数据!
使用得当,单例就是拥有优秀架构的Android应用中的关键部件