一、概述
當我們使用RecyclerView
時,第一件事就是要繼承於RecyclerView.Adapter
,實現其中的抽象方法,來處理資料的展示邏輯,今天,我們就來介紹一下Adapter
中的相關方法。
二、基礎用法
我們從一個簡單的線性列表佈局開始,介紹RecyclerView.Adapter
的基礎用法。
首先,需要匯入遠端依賴包:
compile'com.android.support:recyclerview-v7:25.3.1'
複製程式碼
接著,繼承於RecyclerView.Adapter
來實現自定義的NormalAdapter
:
public class NormalAdapter extends RecyclerView.Adapter<NormalAdapter.NormalViewHolder> {
private List<String> mTitles = new ArrayList<>();
public NormalAdapter(List<String> titles) {
mTitles = titles;
}
@Override
public NormalViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_normal_item, parent, false);
return new NormalViewHolder(itemView);
}
@Override
public void onBindViewHolder(NormalViewHolder holder, int position) {
holder.setTitle(mTitles.get(position));
}
@Override
public int getItemCount() {
return mTitles.size();
}
class NormalViewHolder extends RecyclerView.ViewHolder {
private TextView mTextView;
NormalViewHolder(View itemView) {
super(itemView);
mTextView = (TextView) itemView.findViewById(R.id.tv_title);
}
void setTitle(String title) {
mTextView.setText(title);
}
}
}
複製程式碼
當我們實現自己的Adapter
時,至少要做四個工作:
- 第一:繼承於
RecyclerView.ViewHolder
,編寫自己的ViewHolder
- 這個子類用來描述
RecyclerView
中每個Item
的佈局以及和它關聯的資料,它同時也是RecyclerView.Adapter<VH>
中需要指定的VH
型別。 - 在構造方法中,除了需要呼叫
super(View view)
方法來傳入Item
的跟佈局來給基類中itemView
變數賦值,還應當提前執行findViewById
來獲得其中的子View
以便我們之後對它們進行更新。 - 第二:實現
onCreateViewHolder(ViewGroup parent, int viewType)
- 當
RecyclerView
需要我們提供型別為viewType
的新ViewHolder
時,會回撥這個方法。 - 在這裡,我們例項化出了
Item
的根佈局,並返回一個和它繫結的ViewHolder
。 - 第三:實現
onBindViewHolder(VH viewHolder, int position)
- 當
RecyclerView
需要展示對應position
位置的資料時會回撥這個方法。 - 通過
viewHolder
中持有的對應position
上的View
,我們可以更新檢視。 - 第四:實現
getItemCount()
- 返回
Item
的總數。
在Activity
中,我們給Adapter
傳遞資料,使用方法和ListView
基本相同,只是多了一句在設定LayoutManager
的操作,這個我們後面再分析。
private void init() {
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.rv_content);
mTitles = new ArrayList<>();
for (int i = 0; i < 20; i++) {
mTitles.add("My name is " + i);
}
NormalAdapter normalAdapter = new NormalAdapter(mTitles);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(normalAdapter);
}
複製程式碼
這樣,一個RecyclerView
的例子就完成了:
三、只有一種ViewType
下的複用情況分析
下面,我們來分析一下兩個關鍵方法的呼叫時機:
onCreateViewHolder
onBindViewHolder
通過這兩個方法回撥的時機,我們可以對RecyclerView
複用的機制有一個大概的瞭解。
3.1 初始進入
剛開始進入介面的時候,我們只展示了3
個Item
,此時這兩個方法的呼叫情況如下,可以看到,RecyclerView
只例項化了螢幕內可見的ViewHolder
,並且onBindViewHolder
是在對應的onCreateViewHolder
呼叫完後立即呼叫的:
3.2 開始滑動
當我們手指觸控到螢幕,並開始向下滑動,我們會發現,雖然position=3
的Item
還沒有展示出來,但是這時候它的onCreateViewHolder
和onBindViewHolder
就被回撥了,也就是說,我們會預載入一個螢幕以外的Item
:
3.3 繼續滑動
當我們繼續往下滑動,position=3
的Item
一被展示,那麼position=4
的Item
的兩個方法就會被回撥。
3.4 複用
當postion=6
的Item
被展示之後,按照前面的分析,這時候就應當回撥position=7
的onCreateViewHolder
和onBindViewHolder
方法了,但是我們發現,這時候只回撥了onBindViewHolder
方法,而傳入的ViewHolder
其實是position=0
的ViewHolder
,也就是我們所說的複用:
Items
的展現情況為:
目前不可見的Item
為position=0,1,2
,所以,我們可以得出結論:在單一佈局的情況,RecyclerView
在複用的時候,會取相反方向中超出顯示範圍的第3
個Item
來複用,而並不是超出顯示範圍的第一個Item
進行復用。
四、多種型別的佈局
4.1 基本使用
當我們需要在列表當中展示不同型別的Item
時,我們一般需要重寫下面的方法,告訴RecyclerView
在對應的position
上需要展示什麼型別的Item
。
public int getItemViewType(int position)
RecyclerView
在回撥onCreateViewHolder
的時候,同時也會把viewType
傳遞進來,我們根據viewType
來建立不同的佈局。
下面,我們就來演示一下它的用法,這裡我們返回三種不同型別的item
:
public class NormalAdapter extends RecyclerView.Adapter<NormalAdapter.NormalViewHolder> {
private List<String> mTitles = new ArrayList<>();
public NormalAdapter(List<String> titles) {
mTitles = titles;
}
@Override
public NormalViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = null;
switch (viewType) {
case 0:
itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_normal_item_1, parent, false);
break;
case 1:
itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_normal_item_2, parent, false);
break;
case 2:
itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_normal_item_3, parent, false);
break;
}
NormalViewHolder viewHolder = new NormalViewHolder(itemView);
Log.d("NormalAdapter", "onCreateViewHolder, address=" + viewHolder.toString());
return viewHolder;
}
@Override
public void onBindViewHolder(NormalViewHolder holder, int position) {
Log.d("NormalAdapter", "onBindViewHolder, address=" + holder.toString() + ",position=" + position);
int viewType = getItemViewType(position);
String title = mTitles.get(position);
holder.setTitle1("title=" + title + ",viewType=" + viewType);
}
@Override
public int getItemCount() {
return mTitles.size();
}
@Override
public int getItemViewType(int position) {
return position % 3;
}
class NormalViewHolder extends RecyclerView.ViewHolder {
private TextView mTv1;
NormalViewHolder(View itemView) {
super(itemView);
mTv1 = (TextView) itemView.findViewById(R.id.tv_title_1);
}
void setTitle1(String title) {
mTv1.setText(title);
}
}
}
複製程式碼
最終,會得到下面的介面:
4.2 多種viewType
下的複用情況分析
前面,我們已經研究過一種viewType
下的複用情況,現在,我們再來分析一下多種viewType
時候的複用情況。
4.2.1 初始進入
此時,我們螢幕中展示了postion=0~6
這七個Item
,onCreateViewHolder
和onBindViewHolder
的回撥和之前相同,只會生成螢幕內可見的ViewHolder
4.2.2 開始滑動和繼續滑動
這兩種情況都和單個viewType
時相同,會預載入螢幕以外的一個Item
:
4.2.3 複用
關鍵,我們看一下何時會複用position=0/viewType=1
的Item
:
Item
為position=4/viewType=1
,最下方的Item
為position=11/viewType=2
,按照之前的分析,RecyclerView
會保留相反方向的2
個ViewHolder
,也就是保留postion=2,3
的ViewHolder
,並複用position=1
的ViewHolder
,但是現在position=0
的ViewHolder
的viewType=1
,不可以複用,因此,會繼續往上尋找,這時候就找到了position=0
的ViewHolder
進行復用。
五、資料更新
5.1 更新方式
當資料來源發生變化的時候,我們一般會通過Adatper. notifyDataSetChanged()
來進行介面的重新整理,RecyclerView.Adapter
也提供了相同的方法:
public final void notifyDataSetChanged()
複製程式碼
除此之外,它還提供了下面幾種方法,讓我們進行區域性的重新整理:
//position的資料變化
notifyItemChanged(int postion)
//在position的下方插入了一條資料
notifyItemInserted(int position)
//移除了position的資料
notifyItemRemoved(int postion)
//從position開始,往下n條資料發生了改變
notifyItemRangeChanged(int postion, int n)
//從position開始,插入了n條資料
notifyItemRangeInserted(int position, int n)
//從position開始,移除了n條資料
notifyItemRangeRemoved(int postion, int n)
複製程式碼
下面是一些簡單的使用方法:
//在頭部新增多個資料.
public void addItems() {
mTitles.add(0, "add Items, name=0");
mTitles.add(0, "add Items, name=1");
mNormalAdapter.notifyItemRangeInserted(0, 2);
}
//移除頭部的多個資料.
public void removeItems() {
mTitles.remove(0);
mTitles.remove(0);
mNormalAdapter.notifyItemRangeRemoved(0, 2);
}
//移動資料.
public void moveItems() {
mTitles.remove(1);
mTitles.add(2, "move Items name=0");
mNormalAdapter.notifyItemMoved(1, 2);
}
複製程式碼
5.2 比較
資料的更新分為兩種:
Item changes
:除了Item
所對應的資料被更新外,沒有其它的變化,對應notifyXXXChanged()
方法。Structural changes
:Items
在資料集中被插入、刪除或者移動,對應notifyXXXInsert/Removed/Moved
方法。
notifyDataSetChanged
會把當前所有的Item
和結構都視為已經失效的,因此它會讓LayoutManager
重新繫結Items
,並對他們重新佈局,這在我們知道已經需要更新某個Item
的時候,其實是不必要的,這時候就可以選擇進行區域性更新來提高效率。
六、監聽ViewHolder
的狀態
RecyclerView.Adapter
中還提供了一些回撥,讓我們能夠監聽某個ViewHolder
的變化:
@Override
public void onViewRecycled(NormalViewHolder holder) {
Log.d("NormalAdapter", "onViewRecycled=" + holder);
super.onViewRecycled(holder);
}
@Override
public void onViewDetachedFromWindow(NormalViewHolder holder) {
Log.d("NormalAdapter", "onViewDetachedFromWindow=" + holder);
super.onViewDetachedFromWindow(holder);
}
@Override
public void onViewAttachedToWindow(NormalViewHolder holder) {
Log.d("NormalAdapter", "onViewAttachedToWindow=" + holder);
super.onViewAttachedToWindow(holder);
}
複製程式碼
下面,我們就從例項來講解這幾個方法的呼叫時機,初始時刻,我們的介面為:
- 初始進入時,
position=0~6
的onViewAttachedToWindow
被回撥: - 當滑動到
postion=7
可見時,它的onViewAttachedToWindow
被回撥: - 當
postion=0
被移出螢幕可視範圍內,它的onViewDetachedFromWindow
被回撥: - 而當我們繼續往下滑動,當
position=2
被移出螢幕之後,此時position=0
的onViewRecycled
被回撥: 現在回憶一下之前我們對複用情況的分析,RecyclerView
最多會保留相反方向上的兩個ViewHolder
,此時雖然position=1,2
不可見,但是依然需要保留它們,這時候會回收position=0
的ViewHolder
以備之後被複用。
七、監聽RecyclerView
和RecyclerView.Adapter
的關係
RecyclerView
和Adapter
是通過setAdapter
方法來繫結的,因此在Adapter
中也通過了繫結的監聽:
public void onAttachedToRecyclerView(RecyclerView recyclerView) {}
public void onDetachedFromRecyclerView(RecyclerView recyclerView) {}
複製程式碼
八、小結
這篇文章,主要總結了一些RecyclerView.Adapter
中平時我們不常注意的細節問題,也通過例項瞭解到了關鍵方法的含義,最後,推薦一個Adapter
的開源庫:BaseRecyclerViewAdapterHelper
。
更多文章,歡迎訪問我的 Android 知識梳理系列:
- Android 知識梳理目錄:www.jianshu.com/p/fd82d1899…
- 個人主頁:lizejun.cn
- 個人知識總結目錄:lizejun.cn/categories/