本章主要講述了 RecyclerView 的基礎(chǔ)使用,單例設(shè)計(jì)模式以及通過抽象的統(tǒng)一的 activity 來托管 fragment(以減少重復(fù)代碼量)。
GitHub 地址:
完成第九章
1. 單例(SingleInstance)
單例是特殊的 JAVA 類,在創(chuàng)建實(shí)例的時(shí)候绊谭,一個(gè)單例類僅允許創(chuàng)建一個(gè)實(shí)例。應(yīng)用能在內(nèi)存里多久,單例就能存在多久程拭,因此將對象列表保存在單例里的話,就能隨時(shí)獲取到數(shù)據(jù)棍潘,而不用管 activity 和 fragment 的生命周期怎么變化恃鞋。不過當(dāng)應(yīng)用被從內(nèi)存里移除的時(shí)候,單例對象就不復(fù)存在了亦歉。
要?jiǎng)?chuàng)建單例恤浪,需要?jiǎng)?chuàng)建一個(gè)帶有私有構(gòu)造方法及 get() 方法的類,如果實(shí)例已經(jīng)存在了肴楷,get() 方法就直接返回它水由,如果還不存在,就需要調(diào)用構(gòu)造方法創(chuàng)建它赛蔫。書上的代碼是這樣的:
public class CrimeLab {
//下面這個(gè)靜態(tài)對象只會創(chuàng)建一次
private static CrimeLab sCrimeLab;
private List<Crime> mCrimes;
//程序的其他部分需要使用時(shí)砂客,調(diào)用下列方法,當(dāng)?shù)谝淮问褂玫臅r(shí)候創(chuàng)建這個(gè)對象呵恢,如果不是第一次使用的時(shí)候就直接返回靜態(tài)對象鞠值。
public static CrimeLab get(Context context) {
if (sCrimeLab == null) {
sCrimeLab = new CrimeLab(context);
}
return sCrimeLab;
}
//私有的構(gòu)造方法,只在 get 方法中使用
private CrimeLab(Context context) {
mCrimes = new ArrayList<>();
//初始化數(shù)據(jù)的語句
………………
}
//由于對象只創(chuàng)建了一次渗钉,故而數(shù)據(jù)只有一份
public List<Crime> getCrimes() {
return mCrimes;
}
public Crime getCrime(UUID id) {
for (Crime crime : mCrimes) {
if (crime.getId().equals(id)) {
return crime;
}
}
return null;
}
}
單例能方便地控制模型層對象齿诉,由一個(gè)單例類來控制數(shù)據(jù),所有的修改都由它處理,會使數(shù)據(jù)的一致性控制更加簡便粤剧。
但是萬事總有缺點(diǎn)歇竟,
- 首先,單例無法做到持久的存儲抵恋,應(yīng)用的內(nèi)存被回收時(shí)焕议,單例就不復(fù)存在了。
- 其次弧关,單例還不利于單元測試盅安。
- 最后,單例還容易被濫用世囊,需要注意的是有充足的理由時(shí)才使用單例模式存儲共享數(shù)據(jù)别瞭。
2. 使用抽象 activity 托管 fragment
由于書中大部分 FragmentActivity 的是類似的,所以可以直接創(chuàng)建一個(gè)抽象的類用于被繼承株憾,簡化代碼蝙寨。
回憶一下使用 fragment 的步驟:
- 在托管的 activity 的 onCreate() 方法中新建一個(gè) FragmentManager 對象(getSupportFragmentManager() 方法或者 getFragmentManager() 方法)。
- 使用該對象的 findFragmentById() 方法找到放置 fragment 的位置嗤瞎。
- 如果 fragment 沒有建立墙歪,就新建一個(gè) fragment 對象,并使用 FragmentManager 對象的 beginTransaction().add().commit() 的連續(xù)方法將 fragment 事務(wù)提交到隊(duì)列中
在這其中贝奇,只有新建 fragment 對象是與具體 fragment 有關(guān)的虹菲,那么我們可以將其寫成一個(gè)抽象的函數(shù):
protected abstract Fragment createFragment();
3. RecyclerView, Adapter 和 ViewHolder
對于一個(gè)列表,之前有 ListView掉瞳,網(wǎng)格有 GridView毕源,但要實(shí)現(xiàn)更加復(fù)雜的布局和功能,比如瀑布流的時(shí)候陕习,就有些力不從心了脑豹。RecyclerView 是 Google 推出 Android 5.0 時(shí)一并推出的控件,其具有強(qiáng)大的功能和高度的解耦衡查,有助于開發(fā)者實(shí)現(xiàn)更加多變具有拓展能力的布局瘩欺。
3.1 RecyclerView 簡介及工作原理
要使用 RecyclerView 顯示視圖,需要三樣?xùn)|西拌牲,即RecyclerView俱饿,Adapter, ViewHolder塌忽,它們的任務(wù)各不相同:
- RecyclerView 是視圖層對象拍埠,負(fù)責(zé)回收和定位屏幕上的 ViewHolder
- ViewHolder 只負(fù)責(zé)容納 View 視圖
- Adapter 是控制器對象,負(fù)責(zé)創(chuàng)建必要的 ViewHolder土居,從模型層獲取數(shù)據(jù)并與 ViewHolder 綁定枣购,然后提供給 RecyclerView 顯示
RecyclerView 需要顯示視圖對象時(shí)嬉探,就會去找它的 Adapter,然后會有如下調(diào)用棉圈。
- 首先涩堤,調(diào)用 Adapter 的 getItemCount() 方法,RecyclerView 詢問數(shù)組列表中包含多少個(gè)對象分瘾。
- 接著胎围,調(diào)用 Adapter 的 createViewHolder(ViewGroup, int) 方法創(chuàng)建 ViewHolder 以及 ViewHolder 要顯示的視圖。
- 最后德召,RecyclerView 會傳入 ViewHolder 及其位置白魂,調(diào)用 onBindViewHolder(ViewHolder, int) 方法。Adapter 會找到目標(biāo)位置的數(shù)據(jù)并用數(shù)據(jù)填充到 ViewHolder 的視圖上上岗。
過程圖示如下:
需要注意的是福荸,相對于 onBindViewHolder(ViewHolder, int) 方法,createViewHolder(ViewGroup, int) 方法的調(diào)用并不頻繁肴掷。一旦創(chuàng)建了夠用的 ViewHolder敬锐,RecyclerVIew 就會停止調(diào)用 createViewHolder() 方法,然后通過回收舊的 ViewHolder 來節(jié)約時(shí)間和內(nèi)存捆等。
3.2 使用 RecyclerView
介紹了 RecyclerView 的各種細(xì)節(jié),我們來看看它具體怎么使用吧续室。
3.2.1 添加 RecyclerView 依賴庫
在 File - Project Structure 菜單項(xiàng)栋烤,選擇 app 模塊,然后單擊 Dependencies 選項(xiàng)頁挺狰,單擊加號明郭,找到并添加 recyclerview-v7 支持庫。
3.2.2 在布局文件中使用 RecyclerView 并在 JAVA 代碼中聲明
示例 JAVA 代碼如下:
mCrimeRecyclerView = (RecyclerView) view.findViewById(R.id.crime_recycler_view);
mCrimeRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
RecyclerView 視圖創(chuàng)建完成后丰泊,就立即轉(zhuǎn)交給了 LayoutManager 對象薯定。LayoutManager 實(shí)際上負(fù)責(zé)定位列表項(xiàng)和定義屏幕滾動行為,因此如果沒有 LayoutManger 的支持瞳购,不僅 RecyclerView 無法工作话侄,還會導(dǎo)致應(yīng)用崩潰。在示例中使用的 LinearLayoutManager 是以豎直列表的方式展示列表項(xiàng)学赛,內(nèi)置的還有GridLayoutManager 年堆,還有很多第三方的庫可以使用。
3.2.3 實(shí)現(xiàn) Adapter 和 ViewHolder
ViewHolder 需要做的事情很簡單盏浇,就是將自定義的 view 中的組件找出來并綁定在這個(gè) ViewHolder 的成員變量上变丧。
比如定義了一個(gè)有標(biāo)題和圖片的 item,那么這個(gè) Holder 可以這么寫:
class ItemHolder extends RecyclerView.ViewHolder {
public TextView mTitle;
public ImageView mImg;
public ItemHolder(View itemView) {
super(itemView);
mTitle = (TextView) itemView.findViewById(R.id.tv_item_title);
mImg = (ImageView) itemView.findViewById(R.id.iv_item_img);
}
}
如果有監(jiān)聽器的話绢掰,也可以寫在構(gòu)造函數(shù)中
對于 Adapter 來說痒蓬,要做的事就更多了童擎,我來一一梳理:
從模型層獲取數(shù)據(jù)
一般在 Adapter 內(nèi)部聲明一個(gè)數(shù)據(jù)模型的成員變量,在 Adapter 的構(gòu)造函數(shù)中進(jìn)行初始化-
重寫 ViewHolder 這個(gè)父類的三個(gè)方法
-
onCreateViewHolder(ViewGroup parent, int viewType)
每當(dāng) RecyclerView 需要新的 View 視圖來顯示列表項(xiàng)的時(shí)候就會調(diào)用這個(gè)方法攻晒。在這其中顾复,我們創(chuàng)建 View 視圖,然后封裝到 ViewHolder 中炎辨,此時(shí)并不需要向視圖加載數(shù)據(jù)捕透。
//一個(gè)典型的 onCreateViewHolder 方法的內(nèi)部 LayoutInflater layoutInflater = LayoutInflater.from(getActivity()); View view = layoutInflater.inflate(R.layout.list_item, parent, false); return new ItemHolder(view);
-
onBindViewHolder(ItemHolder holder, int position)
這個(gè)方法負(fù)責(zé)將 ViewHolder 的 View 視圖和模型層的數(shù)據(jù)綁定起來。拿到 ViewHolder 和列表項(xiàng)在數(shù)據(jù)集中的索引位置后碴萧,我們通過索引位置找到要顯示的數(shù)據(jù)進(jìn)行綁定乙嘀。綁定完畢后,刷新顯示 View 視圖破喻。
//典型的 onBindViewHolder 方法內(nèi)部
Data data = mDataList.get(position);
// 注意上面的 mDataList 就是在 Adatper 的構(gòu)造函數(shù)中初始化的 Adapter 的成員變量
holder.mTitle.setText(data.getTitle(position));
holder.mImg.setImageResource(data.getImgRes(position));
-
getItemCount()
返回要展示的數(shù)據(jù)的數(shù)量虎谢,一般是數(shù)據(jù)集的 size
到此一個(gè)基本的 Adapter 就創(chuàng)建完了,在主程序中聲明并初始化 Adapter曹质,調(diào)用 RecyclerView 的 setAdapter 方法即可顯示出列表了~
GitHub Page: kniost.github.io
簡書:http://www.reibang.com/u/723da691aa42