单例与数据集中存储
单例是特殊的Java类,在创建实例时,一个单例类仅允许创建一个实例。
应用能在内存里活多久,单例就能活多久。因此将对象列表保存在单例里的话,就能随时获取crime数据,不管activity和fragment周期怎么变化。使用单例时还应该注意一点:Android从内存中清除应用时,单例对象也会随之消失。后文会对单例进一步介绍。
RecyclerView、ViewHolder和Adapter
RecyclerView是ViewGroup的子类,每一个列表项都是作为一个View子对象来显示的。这些View子对象可简单可复杂,这取决于列表项要显示些什么。
一次为所有的列表项创建View很容易搞垮应用。RecyclerView只创建刚好可以充满屏幕的N个View,而不是一次创建所有的。用户滑动切换视图时,上一个视图会回收利用。顾名思义,RecyclerView就是回收再利用,循环往复。
RecyclerView的任务仅限于回收和定位屏幕上的View。列表项View能够显示数据还离不开另外两个类的支持:ViewHolder子类和Adapter子类。
ViewHolder只做一件事:容纳View视图。定义每个列表项中包含的组件并将它们实例化,并定义点击事件等。
RecyclerView不能自己创建ViewHolder,而是通过Adapter来完成的。Adapter是一个控制器对象,从模型层获取数据,然后供给RecyclerView显示,是沟通的桥梁。
Adapter负责:
1.创建必要的ViewHoler;
2.绑定ViewHolder至模型层数据。
RecyclerView需要显示视图对象时,就会去找它的Adapter。
首先,调用Adapter的getItemCount()方法,RecyclerView会询问数组列表中包含多少个对象。
接着,RecyclerView调用Adapter的onCreateViewHolder(ViewGroup, int)方法创建ViewHolder及其要显示的视图。
最后,RecyclerView会传入ViewHolder及其位置,调用onBindViewHolder(ViewHolder, int)方法。Adapter会找到目标位置的数据并将其绑定在ViewHolder视图上。所谓绑定,就是使用模型数据填充视图。
LayoutManager
RecyclerView类不会亲自摆放屏幕上的列表项。实际上,摆放的任务被委托给了LayoutManager。除了在屏幕上的摆放,LayoutManager还负责定义屏幕滚动行为。除了一些Android操作系统内置版实现,LayoutManager还有很过第三方库实现版本。LinearLayoutManager类使用竖直列表的形式展示列表项。后续还会使用GridLayoutManager类,以网格的形式展示列表项。
ListView和GridView
Android操作系统核心库包含ListView、GridView和Adapter这3个类。在Android 5.0之前,创建列表项或网各项都应该优先使用这些类。
这列类的API与RecyclerView的API非常相似。ListView和GridView不关系具体的展示项,只负责展示项的滚动。Adapter负责创建列表项的所有视图。不过,使用ListView和GrisView时不一定非要使用ViewHolder模式(虽然可以并且应该使用)。
现在已经被RecyclerView替代,举例说明替代理由:
1.ListView的API不支持创建水平滚动的ListView,因此需要许多额外的定制工作。使用RecyclerView时,虽然创建定制布局和滚动行为也需要额外的工作,但RecyclerView天生支持拓展,所以使用体验还不错。
2.RecyclerView还有支持列表项动画效果的优点。如果要让ListView和GridView支持添加和删除列表项的动画效果,实现起来既复杂又容易出错;
挑战练习:RecyclerView ViewType
请在RecyclerView中创建两类列表项:一般性crime,以及需警方介入的crime。要完成这个挑战,你需要用到RecyclerView. Adapter的视图类别功能(view type)。在Crime对象里,再添加一个mRequiresPolice实例变量,使用它并借助getltemViewType(int)方法(developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#getItemViewType(int)), 确定该加载哪个视图到CrimeAdapter。
在onCreateViewHolder(ViewGroup, int)方法里,基于getltemViewType(int)方法返回的viewType值,需要返回不同的ViewHolder。如果是一般性crime,就仍然使用原始布局;如果是需警方介人的crime,就使用一个带联系警方按钮的新布局
思路:
模型层需要在Crime对象里添加一个mRequiresPolice实例变量,用于控制是否需要显示特定的视图;视图层需要新定义一个列表项视图,其中包括报警的按钮;然后控制层对应创建新添列表项的ViewHolder,ViewHolder的选择机制在onCreateViewHolder(ViewGroup, int)方法里完成。
更改Crime对象:
Crime.java
……
private int mRequiresPolice;
……
public int getRequiresPolice() {
return mRequiresPolice;
}
public void setRequiresPolice(int requiresPolice) {
mRequiresPolice = requiresPolice;
}
……
还要编辑CrimeLab在构造Crime数组时,设定好哪些Crime对象需要报警:
CrimeLab.java
……
private CrimeLab(Context context) {
// 单例类使用私有的构造方法,使得外部无法直接实例化单例类
mCrimes = new ArrayList<>();
for (int i = 0; i < 100; i++) {
Crime crime = new Crime();
crime.setTitle("Crime #" + i);
crime.setSolved(i % 2 == 0);
// 初始化哪些Crime对象需要报警
crime.setRequiresPolice(i % 2);
mCrimes.add(crime);
}
}
……
新建列表项布局文件:
list_item_crime_requires_police.xml
接下来在CrimeListFragment中改动,新增CrimeRequiresPoliceHolder,修改CrimeAdapter:
CrimeListFragment.java
……
private class CrimeRequiresPoliceHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private TextView mTitleTextView;
private TextView mDateTextView;
private Button mCallPoliceButton;
private Crime mCrime;
public CrimeRequiresPoliceHolder(LayoutInflater inflater, ViewGroup parent) {
super(inflater.inflate(R.layout.list_item_crime_requires_police, parent, false));
itemView.setOnClickListener(this);
mTitleTextView = itemView.findViewById(R.id.crime_title);
mDateTextView = itemView.findViewById(R.id.crime_date);
mCallPoliceButton = itemView.findViewById(R.id.call_police_button);
mCallPoliceButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getActivity(), "我要报警啦!", Toast.LENGTH_SHORT).show();
}
});
}
public void bind(Crime crime) {
mCrime = crime;
mTitleTextView.setText(mCrime.getTitle());
mDateTextView.setText(mCrime.getDate().toString());
}
@Override
public void onClick(View v) {
Toast.makeText(getActivity(), mCrime.getTitle() + "Clicked!", Toast.LENGTH_SHORT).show();
}
}
……
private class CrimeAdapter extends RecyclerView.Adapter {
private List mCrimes;
public CrimeAdapter(List crimes) {
mCrimes = crimes;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
if (viewType != 0) return new CrimeRequiresPoliceHolder(layoutInflater, parent);
return new CrimeHolder(layoutInflater, parent);
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
Crime crime = mCrimes.get(position);
if (crime.getRequiresPolice() != 0) {
CrimeRequiresPoliceHolder crimeRequiresPoliceHolder = (CrimeRequiresPoliceHolder) holder;
crimeRequiresPoliceHolder.bind(crime);
}else {
CrimeHolder crimeHolder = (CrimeHolder) holder;
crimeHolder.bind(crime);
}
}
@Override
public int getItemCount() {
return mCrimes.size();
}
@Override
public int getItemViewType(int position) {
return mCrimes.get(position).getRequiresPolice();
}
}
……