當咱們使用RecyclerView
時,第一件事就是要繼承於RecyclerView.Adapter
,實現其中的抽象方法,來處理數據的展現邏輯,今天,咱們就來介紹一下Adapter
中的相關方法。android
咱們從一個簡單的線性列表佈局開始,介紹RecyclerView.Adapter
的基礎用法。 首先,須要導入遠程依賴包:git
compile'com.android.support:recyclerview-v7:25.3.1'
複製代碼
接着,繼承於RecyclerView.Adapter
來實現自定義的NormalAdapter
:github
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
時,至少要作四個工做:bash
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
的操做,這個咱們後面再分析。ide
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
下的複用狀況分析下面,咱們來分析一下兩個關鍵方法的調用時機:post
onCreateViewHolder
onBindViewHolder
經過這兩個方法回調的時機,咱們能夠對RecyclerView
複用的機制有一個大概的瞭解。ui
剛開始進入界面的時候,咱們只展現了3
個Item
,此時這兩個方法的調用狀況以下,能夠看到,RecyclerView
只實例化了屏幕內可見的ViewHolder
,而且onBindViewHolder
是在對應的onCreateViewHolder
調用完後當即調用的: this
當咱們手指觸摸到屏幕,並開始向下滑動,咱們會發現,雖然position=3
的Item
尚未展現出來,可是這時候它的onCreateViewHolder
和onBindViewHolder
就被回調了,也就是說,咱們會預加載一個屏幕之外的Item
: spa
當咱們繼續往下滑動,position=3
的Item
一被展現,那麼position=4
的Item
的兩個方法就會被回調。
當postion=6
的Item
被展現以後,按照前面的分析,這時候就應當回調position=7
的onCreateViewHolder
和onBindViewHolder
方法了,可是咱們發現,這時候只回調了onBindViewHolder
方法,而傳入的ViewHolder
實際上是position=0
的ViewHolder
,也就是咱們所說的複用:
Items
的展示狀況爲:
目前不可見的
Item
爲
position=0,1,2
,因此,咱們能夠得出結論:在單一佈局的狀況,
RecyclerView
在複用的時候,會取相反方向中超出顯示範圍的第
3
個
Item
來複用,而並非超出顯示範圍的第一個
Item
進行復用。
當咱們須要在列表當中展現不一樣類型的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);
}
}
}
複製代碼
最終,會獲得下面的界面:
viewType
下的複用狀況分析前面,咱們已經研究過一種viewType
下的複用狀況,如今,咱們再來分析一下多種viewType
時候的複用狀況。
此時,咱們屏幕中展現了postion=0~6
這七個Item
,onCreateViewHolder
和onBindViewHolder
的回調和以前相同,只會生成屏幕內可見的ViewHolder
這兩種狀況都和單個viewType
時相同,會預加載屏幕之外的一個Item
:
關鍵,咱們看一下什麼時候會複用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
進行復用。
當數據源發生變化的時候,咱們通常會經過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);
}
複製代碼
數據的更新分爲兩種:
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
。