>[info] **注意**:文章中涉及到的源碼, 是以Android4.0 (API 14)源碼為基礎的。貌似Android4.4 (API19及其以上版本)源碼會有細微變更,所以具體到某一個版本具體分析。
> 另外,RecyclerView 是Android 5.0(API21)才在V7包中引入的。
>[warning] **聲明**:這里的Adapter并不是經典的適配器模式,但是卻是對象適配器模式的優秀示例,也很好的體現了面向對象的一些基本原則。這里的Target角色和Adapter角色融合在一起,Adapter中的方法就是目標方法;而Adaptee角色就是ListView的數據集與Item View,Adapter代理數據集,從而獲取到數據集的個數、元素。
>這里的Target角色就是View, Adapter角色就是將Item View輸出為View抽象的角色, Adaptee就是需要被處理的Item View。
> 通過增加Adapter一層來將Item View的操作抽象起來,ListView等集合視圖通過Adapter對象獲得Item的個數、數據元素、Item View等,從而達到適配各種數據、各種Item視圖的效果。因為Item View和數據類型千變萬化,Android的架構師們將這些變化的部分交給用戶來處理,通過getCount、getItem、getView等幾個方法抽象出來,也就是將Item View的構造過程交給用戶來處理,靈活地運用了適配器模式,達到了無限適配、擁抱變化的目的。
> 雖然有些脫離Adapter模式將不兼容的接口轉換為可用接口的使用場景,但也是Adapter模式的一種變種實現。
[ListView](https://www.androidos.net.cn/android/4.0.2_r1/xref/frameworks/base/core/java/android/widget/ListView.java)作為最重要的控件,它需要能夠顯示各式各樣的視圖( Item View ),每個人需要的顯示效果各不相同,顯示的數據類型、數量等也千變萬化,那么如何應對這種變化成為架構師需要考慮的最重要特性之一。

從上圖可知ListView是AbsListView的子類,AbsListView是AdapterView的子類實現。
Android 的做法是**增加一個Adapter層來隔離變化**,**將ListView需要的關于Item View接口抽象到Adapter對象中,并且在ListView內部調用Adapter這些接口完成布局等操作。這樣只要用戶實現了Adapter的接口,并且將該Adapter設置給ListView, ListView就可以按照用戶設定的Ul 效果、數量、數據來顯示每一項數據**。ListView最重要的問題是要解決每一項Item視圖的輸出, **Item View千變萬化,但終究它都是View類型,Adapter統一將ItemView輸出為View ,這樣就很好地應對了Item View的可變性**。這雖然有些脫離Adapter模式將不兼容的接口轉換為可用接口的使用場景,但也是Adapter模式的一種變種實現。
#### **ListView是如何通過Adapter 模式來實現應對千變萬化的UI效果呢?**
發現在ListView中并沒有Adapter相關的成員變量,其實Adapter在ListView 的父類[AbsListView](https://www.androidos.net.cn/android/4.0.2_r1/xref/frameworks/base/core/java/android/widget/AbsListView.java)中, AbsListView 是一個列表控件的抽象,我們看一看這個類。
**AbsListView.java**
~~~
package android.widget;
public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher,
ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener,
ViewTreeObserver.OnTouchModeChangeListener,
RemoteViewsAdapter.RemoteAdapterConnectionCallback {
@SuppressWarnings("UnusedDeclaration")
private static final String TAG = "AbsListView";
..............
ListAdapter mAdapter;
..............
//子類需要覆寫layoutChildren()函數來布局childview,也就是ItemView
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
mInLayout = true;
final int childCount = getChildCount();
if (changed) {
for (int i = 0; i < childCount; i++) {
getChildAt(i).forceLayout();
}
mRecycler.markChildrenDirty();
}
// 布局 Child View
layoutChildren();
mInLayout = false;
mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;
// TODO: Move somewhere sane. This doesn't belong in onLayout().
if (mFastScroll != null) {
mFastScroll.onItemCountChanged(getChildCount(), mItemCount);
}
}
..............
// 關聯到Window時調用世獲取調用Adapter中的getCount方法等
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
final ViewTreeObserver treeObserver = getViewTreeObserver();
treeObserver.addOnTouchModeChangeListener(this);
if (mTextFilterEnabled && mPopup != null && !mGlobalLayoutListenerAddedFilter) {
treeObserver.addOnGlobalLayoutListener(this);
}
//給適配器注冊一個觀察者
if (mAdapter != null && mDataSetObserver == null) {
mDataSetObserver = new AdapterDataSetObserver();
mAdapter.registerDataSetObserver(mDataSetObserver);
// Data may have changed while we were detached. Refresh.
mDataChanged = true;
mOldItemCount = mItemCount;
// 獲取Item的數量,調用的是mAdapter的getCount方法
mItemCount = mAdapter.getCount();
}
}
................
}
~~~
AbsListView 定義了**集合視圖的邏輯框架**,比如**Adapter模式的應用、復用Item View 的邏輯、布局子視圖的邏輯等**,子類只需要覆寫特定的方法即可實現集合視圖的功能。
首先**在AbsListView類型的View 中添加窗口( onAttachedToWindow 函數)時會調用Adapter 中的getCount函數獲取到元素的個數,然后在onLayout函數中調用layoutChilden函數對所有子元素進行布局**。AbsListView中并沒有實現layoutChilden 這個函數,具體的實現在子類中,這里我們分析ListView 中的實現,具體代碼如下。
**ListView:layoutChildren()**
~~~
@Override
protected void layoutChildren() {
final boolean blockLayoutRequests = mBlockLayoutRequests;
if (blockLayoutRequests) {
return;
}
mBlockLayoutRequests = true;
try {
super.layoutChildren();
invalidate();
...........
//根據布局模式來布局ItemView
switch (mLayoutMode) {
case LAYOUT_SET_SELECTION:
if (newSel != null) {
sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);
} else {
sel = fillFromMiddle(childrenTop, childrenBottom);
}
break;
case LAYOUT_SYNC:
sel = fillSpecific(mSyncPosition, mSpecificTop);
break;
case LAYOUT_FORCE_BOTTOM:
sel = fillUp(mItemCount - 1, childrenBottom);
adjustViewsUpOrDown();
break;
case LAYOUT_FORCE_TOP:
mFirstPosition = 0;
sel = fillFromTop(childrenTop);
adjustViewsUpOrDown();
break;
case LAYOUT_SPECIFIC:
sel = fillSpecific(reconcileSelectedPosition(), mSpecificTop);
break;
case LAYOUT_MOVE_SELECTION:
sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);
break;
default:
if (childCount == 0) {
if (!mStackFromBottom) {
final int position = lookForSelectablePosition(0, true);
setSelectedPositionInt(position);
sel = fillFromTop(childrenTop);
} else {
final int position = lookForSelectablePosition(mItemCount - 1, false);
setSelectedPositionInt(position);
sel = fillUp(mItemCount - 1, childrenBottom);
}
} else {
if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
sel = fillSpecific(mSelectedPosition,
oldSel == null ? childrenTop : oldSel.getTop());
} else if (mFirstPosition < mItemCount) {
sel = fillSpecific(mFirstPosition,
oldFirst == null ? childrenTop : oldFirst.getTop());
} else {
sel = fillSpecific(0, childrenTop);
}
}
break;
}
............
}
}
~~~
ListView 覆寫了AbsListView 中的layoutChilden 函數,在該函數中**根據布局模式來布局Item View**,例如, **默認情況是從上到下開始布局, 但是,也有從下到上開始布局的**,例如QQ 聊天窗口的氣泡布局,最新的消息就會布局到窗口的最底部。我們看看這兩種實現。
**ListView:fillDown()**
~~~
//從上到下填充Item View [只是其中一種填充方式]
private View fillDown(int pos, int nextTop) {
View selectedView = null;
int end = (mBottom - mTop);
if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
end -= mListPadding.bottom;
}
while (nextTop < end && pos < mItemCount) {
boolean selected = pos == mSelectedPosition;
//通過makeAndAddView 獲取Item View
View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);
nextTop = child.getBottom() + mDividerHeight;
if (selected) {
selectedView = child;
}
pos++;
}
setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
return selectedView;
}
~~~
**ListView:fillUp()**
~~~
// 從下到上的布局
private View fillUp(int pos, int nextBottom) {
View selectedView = null;
int end = 0;
if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
end = mListPadding.top;
}
while (nextBottom > end && pos >= 0) {
// is this the selected item?
boolean selected = pos == mSelectedPosition;
View child = makeAndAddView(pos, nextBottom, false, mListPadding.left, selected);
nextBottom = child.getTop() - mDividerHeight;
if (selected) {
selectedView = child;
}
pos--;
}
mFirstPosition = pos + 1;
setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
return selectedView;
}
~~~
**在每一種布局方式的函數中都會從makeAndAddView 函數獲取一個View , 這個View 就是ListView 的每一項的視圖**,這里有一個**pos 參數**,也就是**對應這個View 是ListView 中的第幾項**。我們看看makeAndAddView 中的實現。
**ListView:makeAndAddView()**
~~~
// 添加Item View
private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
boolean selected) {
View child;
if (!mDataChanged) {
// Try to use an existing view for this position
child = mRecycler.getActiveView(position);
if (child != null) {
// Found it -- we're using an existing child
// This just needs to be positioned
setupChild(child, position, y, flow, childrenLeft, selected, true);
return child;
}
}
// ① 獲取一個item View
child = obtainView(position, mIsScrap);
// ② 將item view 設置到對應的地方
setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
return child;
}
~~~
在makeAndAddView 函數中主要分為**兩個步驟**,**第一是根據position 獲取一個item View ,然后將這個View 布局到特定的位置。獲取一個item View 調用的是obatinView 函數**, 這個函數在AbsListView 中。核心代碼如下。
**AbsListView:obtainView()**
~~~
//獲取一個Item View , 該函數在AbsListView中
View obtainView(int position, boolean[] isScrap) {
isScrap[0] = false;
View scrapView;
// l.從緩存的Item View中獲取,ListView的復用機制就在這里
scrapView = mRecycler.getScrapView(position);
View child;
if (scrapView != null) {
if (ViewDebug.TRACE_RECYCLER) {
ViewDebug.trace(scrapView, ViewDebug.RecyclerTraceType.RECYCLE_FROM_SCRAP_HEAP,
position, -1);
}
// 2.注意,這里將scrapView設置給了Adapter的getView 函數
child = mAdapter.getView(position, scrapView, this);
..............
} else {
// 3.沒有緩存View 的情況下getView的第二個參數為null
child = mAdapter.getView(position, null, this);
if (mCacheColorHint != 0) {
child.setDrawingCacheBackgroundColor(mCacheColorHint);
}
if (ViewDebug.TRACE_RECYCLER) {
ViewDebug.trace(child, ViewDebug.RecyclerTraceType.NEW_VIEW,
position, getChildCount());
}
}
return child;
}
~~~
**obatinView 函數定義了列表控件的Item View 的復用邏輯**,**首先會從RecyclerBin 中獲取一個緩存的View,如果有緩存則將這個緩存的View 傳遞到Adapter 的getView 第二個參數中**,這也就是我們**對Adapter 的最常見的優化方式**,即**判斷getView 的convertView 是否為空,如果為空則從xml中創建視圖,否則使用緩存的View。這樣避免了每次都從xml 加載布局的消耗**,能夠顯著提升ListView 等列表控件的效率。通常我們自己的Adapter的實現如下。
~~~
//解析、設置、緩存convertView 以及相關內容
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
//convertView為空,表示沒有緩存,此時需要從xml 加載
if (convertView == null) {
holder= new ViewHolder();
convertView = mInflater.inflate(R.layout.my_listview_item, null);
//獲取title
holder.title= (TextView)convertView.findViewByid(R.id.title);
convertView.setTag(holder);
} else {
// convertView不為空,這里的convertView就是從RecyclerBin取出的緩存視圖
holder= (ViewHolder)convertView.getTag();
//數據綁定
return convertView;
}
~~~
通過這種緩存機制,即使有成千上萬的數據項,ListView也能夠流暢運行,因此,**只有填滿一屏所需的Item View 存在內存中**。**ListView 根據Adapter 設置的數據項數量循環調用getView 方法獲取視圖(Item View),第一次加載填滿屏幕的數據項時(沒有緩存)getView的第二個參數convertView 都為空,此時每次都需要從xml中加載布局文件,填充數據之后返回給ListView 。當整屏的數據項加載完畢之后用戶向下滾動屏幕,此時item1滾出屏幕,并且一個新的項目(ItemView)從屏幕低端上來時, ListView 再請求一個視圖,此時item 1被緩存起來,在下一項數據加載時傳遞給getView 的第二個參數convertView ,因此, convertView此時不是空值,它的值是item1。此時只需設定新的數據然后返回convertView,這樣就避免了每次都從xml加載、初始化視圖,減少了時間、性能上的消耗**。原理如圖所示。
:-: 
ListView緩存機制
了解了它的工作原理后,就可以重復利用ListView 的Item View,只要convertView不為空就直接使用,改變它綁定的數據就行了。當然,由于視圖被緩存了,視圖中的數據也會被緩存,因此,你需要在每次獲取到了Item view時對每個數據項重新賦值,否則會出現數據錯誤的現象。
#### **總結一下這個過程**
ListView 等集合控件通過Adapter 來獲取Item View的數量、布局、數據等,在這里最為**重要的就是getView函數**,這個函數**返回一個View類型的對象,也就是Item View**。 由于它**返回的是一個View 抽象**,而千變萬化的UI視圖都是View 的子類,**通過依賴抽象這個簡單的原則和Adaper模式就將Item View的變化隔離了,保證了AbsListView 類族的高度可定制化。在獲取了View之后,將這些View 通過特定的布局方式設置到對應的位置上,再加上Item View的復用機制,整個ListView就運轉起來了。**
當然,**這里的Adapter并不是經典的適配器模式,卻是對象適配器模式的優秀示例**,也很好地體現了面向對象的一些基本原則。這里的**Target角色就是View, Adapter角色就是將Item View輸出為View抽象的角色, Adaptee就是需要被處理的Item View。通過增加Adapter一層來將Item View的操作抽象起來,ListView 等集合視圖通過Adapter 對象獲得Item的個數、數據、Item View 等,從而達到適配各種數據、各種Item視圖的效果**。
因為Item View和數據類型千變萬化, Android 的架構師們**將這些變化的部分交給用戶來處理,通過getCount、getItem、getView 等幾個方法抽象出來,也就是將Item View的構造過程交給用戶來處理,靈活地運用了適配器模式,達到了無限適配、擁抱變化的目的。**
#### **深度擴展之[RecyclerView](https://www.androidos.net.cn/android/5.0.1_r1/xref/frameworks/support/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java)**
雖然ListView已經足夠強大,但還是有很多問題,比如:每次都需要創建一個ViewHolder、手動判斷是否有緩存View等。對于用戶而言,這些都可改進,因此出現了**RecyclerView**,顧名思義,這個View**代表的就是一個可循環使用的視圖集合控件,它定義了ViewHolder類型標準,封裝了View緩存判斷機制,更強大的是它可以將一個RecyclerView顯示為不同的樣式,例如ListView、GridView形式,瀑布流形式。**
:-: 
RecyclerView列表視圖
除了類似ListView的Adapter之外, RecyclerView還有一個**額外的設置就是設置布局方式**,這也是RecyclerView 能夠在布局上比ListView更為靈活的原因。因為它**將布局通過橋接、組合的形式交由LayoutManager實現**,而不是像ListView 、GridView這些類自己實現,因為這會導致一個類只能有一種布局方式,當用戶需要實現其他布局效果時就需要替換整個實現類。而**通過橋接模式和組合模式,將布局這個職責分離開來,使得一個RecyclerView與不同的LayoutManager搭配就能做成各種布局效果,這也是橋接模式的經典應用。**
~~~
//1.獲取控件
RecyclerView mRecyclerView = (RecyclerView) rootView.findViewById(R.id.recyclerview);
//2.設置布局方式為線性布局
mRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
mRecyclerView.setHasFixedSize(true);
List<Article> articles= new ArrayList<Article>();
//3.設置Adapter
ArticleAdapter mAdapter = r1ew ArticleAdapter (articles);
mRecyclerView.setAdapter(mAdapter);
~~~
上述代碼描述的是一個普通的初始化過程,唯一與ListView 不太相同的是第二步中設置了**LayoutManager**,這個LayoutManager的職責就是**負責RecyclerView元素的布局。默認的實現有LinearLayoutManager 、GridLayoutManager、StaggeredGridLayoutManager,分別是線性布局、網格布局和瀑布流布局**。這些布局不僅可以豎向布局,還支持橫向布局,這幾個布局已經能夠基本滿足我們的需求了,可見RecylerView 的高度定制化能力。
RecyclerView是在Android 5.0(API 21)才引入的V7包中的一個控件,可以查看官方的Android 5.0變更,也可以查看[這里整理的譯文](http://www.hmoore.net/alex_wsc/android_google/488254),從ViewGroup這張層次結構圖,可以看出RecyclerView繼承于ViewGroup。
下面看看文章Adapter 的實現。(**Adapter是指這個`RecyclerView.Adapter<VH extends ViewHolder>`**)
~~~
public class ArticleAdapter extends Adapter<ViewHolder> {
List<Article> mArticles;//文章數據
OnItemClickListener<Article> mClickListener;//每一項的點擊事件
public ArticleAdapter (List<Article> dataset) {
mArticles = dataset;
}
//綁定每一項的數據
@Override
public void onBindViewHolder (ViewHolder viewHolder, int position) {
if(viewHolder instanceof ArticleViewHolder) {
bindViewForArticle (viewHolder,position);
}
}
protected void bindViewForArticle (ViewHolder viewHolder,int position) {
ArticleViewHolder articleViewHolder = (ArticleViewHolder) viewHolder;
fina1 Article article = getItem(position);
articleViewHolder.titleTv.setText(article.title);
articleViewHolcter.publishTimeTv.setText(article.publishTime);
articleViewHolder.authorTv.setText(article.author);
//設置點擊事件
articleViewHolder.itemView.setOnClickListener(new OnClickListener() {
@Override
public void onClick (View v) {
if (mClickListener != null ) {
mClickListener.onClick(article);
}
}
});
}
@Override
public int getitemCount () {
return mArticles.size();
}
//創建ViewHolder
@Override
public RecyclerView.ViewHolder onCreateViewHolder (ViewGroup viewGroup, int viewType) {
return createArticleViewHolder(viewGroup);
}
protected ViewHolder createArticleViewHolder (ViewGroup viewGroup) {
View itemView = Layoutinflater.from(viewGroup.getContext()).inflate(
R.layout.recyclerview_article_item,viewGroup , false );
return new ArticleViewHolder(itemView);
}
protected Article getItem(int position) {
return mArticles.get(position);
}
public void setOnItemClickListener (OnItemClickListener<Article> mClickListener) {
this.mClickListener = mClickListener;
}
//ViewHolder ,負責保持Item View
static class ArticleViewHolder extends RecyclerView.ViewHolder {
public TextView titleTv;
public TextView publishTimeTv;
public TextView authorTv;
public ArticleViewHolder (View itemView) {
super (itemView);
titleTv = (TextView) itemView.findViewById(R.id.article_title_tv);
publishTimeTv = (TextView) itemView.findViewById(R.id.article_time_tv);
authorTv = (TextView) itemView.findViewById(R.id.article_author_tv);
}
}
}
~~~
在RecyclerView 中沒有了getView函數,取而代之的是**onBindViewHolder和onCreateViewHolder,這兩個函數(在RecyclerView的內部類`Adapter<VH extends ViewHolder>`中)就相當于getView功能**。
**RecyclerView.Adapter**
~~~
public class RecyclerView extends ViewGroup {
.........
public static abstract class Adapter<VH extends ViewHolder> {
...........
public abstract VH onCreateViewHolder(ViewGroup parent, int viewType);
public abstract void onBindViewHolder(VH holder, int position);
..........
}
...........
}
~~~
**首先會從onCreateViewHolder中獲取ViewHolder對象,ViewHolder 中又含有Item View 的引用,獲取到ViewHolder 之后又會通過onBindViewHolder 函數來綁定數據**,這兩個過程**實際上是將getView函數分解開來,使得加載視圖和綁定數據分離開來**。在**RecyclerView中的操作單位也不再是View,而是ViewHolder**。
Android中定義了一個[Adapter基類](https://www.androidos.net.cn/android/5.0.1_r1/xref/frameworks/support/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java),該類的一個泛型參數(**VH**)就是ViewHolder,**用戶需要繼承ViewHolder實現自己的ViewHolder,對于item view 的操作都從這個ViewMolder對象進行。通過onBindViewHolder和onCreateViewHolder兩個函數,Android屏蔽了對Item View緩存的手動判斷,將這部分邏輯封裝在RecyclerView中,這樣就不需要像ListView中的Adapter的getView函數那樣對convertView進行判空等操作,簡化的邏輯,隱藏了具體實現**。
**最后將數據設置給Adapter,再將Adapter設置給RecyclerView, RecyclerView再根據用戶設置的LayoutManager 將每個數據項布局到對應的位置,這樣一來整個列表控件就顯示在我們眼前了**。
**總結**:Android通過onBindViewHolder和onCreateViewHolder兩個函數屏蔽了一些固定的邏輯,使得Adapter的使用更為簡單,又通過LayoutManager實現具體的布局,使得RecyclerView具有更強大的定制能力,這就是RecyclerView比ListView更強大的原因所在。
如果我們看膩了上圖所示的單向列表效果,想要換成每行3 篇文章的網格形式的布局,此時只需要將LieanrLayoutManager 替換為一個GridLayouManager 即可。具體代碼如下。
~~~
//1.獲取控件,代碼省略
//2.設置布局方式為網格布局
mRecyclerView.setLayoutManager(new GridLayoutManager(getActivity(), 3));
mRecyclerView.setHasFixedSize(true);
List<Article> articles = new ArrayList<Article>();
//3 . 設置Adapter
~~~
:-: 
RecyclerView網格視圖
又如果你覺得高度一樣的網格布局還是不好看, 你想要高度不規則的瀑布流布局, 那么此時需要做的同樣也很簡單。修改LayoutManager為 StaggeredGridLayoutManager,然后在 Adapter 中為每個Item 設置一個高度即可。具體代碼如下
~~~
//1.獲取控件,代碼省略
//2.設置布局方式為瀑布流布局
mRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(3 ,StaggeredGridLayoutManager.VERTICAL));
mRecyclerView.setHasFixedSize(true);
List<Article> articles = new ArrayList<Article>();
//3 . 設置Adapter
~~~
還需要做的是在Adapter 中設置每個Item 的高度, 例如。
~~~
@Override
public void onBindViewHolder(ViewHolder viewHolder, int position) {
//代碼省略
ArticleViewHolder articleViewHoider = (ArticleViewHolder) viewHolder;
LayoutParams params = articleViewHolder.itemView.getLayoutParams()
//隨機生成一個高度
params.height = new Random().nextint(lO)*50 + 100;
//設置屬性
articleViewHolder.itemView.setLayoutParams(params);
}
~~~
:-: 
RecyclerView瀑布流格局
#### **RecyclerView存在的缺陷**
* **缺陷一:沒有設置處理條目Item 點擊事件的Listener接口**
RecyclerView 也有幾個小問題需要注意, 第一個是它沒有設置處理Item 點擊事件的Listener 接口,例如ListView 可以通過setOnltemClickListener,但是, 在RecyclerView 中并沒有這個接口。如果要給Item設置點擊事件,那么需要通過Adapter 實現,通常的做法是定義一個點擊接口,然后設置給Adapter,最后在這個View 的點擊事件中觸發這個類似于OnltemCiickListener 的回調函數。示例程序如下。
~~~
public class ArticleAdapter extends Adapter<ViewHolder> {
private OnItemClickListener<Article> ClickListener;
@Override
public void onBindViewHolder(ViewHolder viewHolder, int position) {
//代碼省略
ArticleViewHolder articleViewHolder = (ArticleViewHolder) viewHolder;
//設置itemView的點擊事件,并且在里面調用OnItemClickListener的onClick方法
articleViewHolder.itemView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (mClickListener != null) {
mClickListener.onClick(article);
}
}
});
public void setOnitemClickListener (OnItemClick.Listener<Article> listener) {
mClickListener = listener;
}
//定義點擊接口
public static interface OnitemClickListener<T> {
public void onClick (T item);
}
}
}
~~~
* **缺陷二:沒有可以設置Header 和footer 的接口**
第二個問題是, RecyclerView 沒有可以設置Header 和footer 的接口,當需要實現HeaderView時,需要根據viewType 進行分類處理。例如當position 為0 時,你把viewType設置為0 ,其他位置的Item 的viewType 則為1 ,也就是說第一個item 是Header,其他的是普通的Item 。因此,需要在position 為0 時返回HeaderView 的ViewHolder,而在其他位置時返回普通item 的ViewHolder。并且需要注意的是, item count 需要比數據集的數量多1 ,這個1 就是這個Header View ,另外,從
數據集取數據時則需要把position 減1 。因為第一個View 為Header, Header 通常情況下并不使用數據集中的數據,也就是說第一個item 不需要數據,需要數據的是第二個Item View,而此時它顯示的數據應該是數據集中的第一個,因此取數據時需要把索引減1 。示例代碼如下。
~~~
public class ArticleWithHeaderAdapter extends Adapter<ViewHolder> {
private static final int HEADER TYPE = 0 ;
//字段省略
//分類進行設置
@Override
public void onBindViewHolder(ViewHolder viewHolder, int position) {
if (viewHolder instanceof ArticleViewHolder) {
//設置普通的Item View
} else if (viewHolder instanceof HeaderViewHolder) {
//設置Header View
}
}
//根據分類創建對應的ViewHolder
@Override
public RecyclerView.ViewHolder onCreateViewHolder (ViewGroup viewGroup, int viewType){
if (viewType == HEADER_TYPE) {
return createHeaderViewHolder(viewGroup);
}
return createArticleViewHolder(viewGroup) ;
}
private HeaderViewHolder createHeaderViewHolder(ViewGroup viewGroup) {
View headerView = Layoutinflater.from(
viewGroup.getContext()).inflate(R.layout.auto_slider,viewGroup,false);
return new HeaderViewHolder(headerView);
}
/**
*多了一個Header,因此數量要加1
*/
@Override
public int getItemCount() {
return mArticles ==null ? 1 : mArticles.size() + l ;
}
@Override
protected Article getItem (int position) {
//因為第一項視圖是Header ,添加了一個Heade主,因此數據索引要減去1
return mArticles.get(position - l) ;
}
//設置viewType
@Override
public int getItemViewType(int position) {
if (HEADER_TYPE == position) {
return 0 ;
}
return 1 ;
}
static class ArticleViewHolder extends RecyclerView.ViewHolder {
//字段省略
public HeaderViewHolder(View view) {
super (view);
//代碼省略
}
}
static class HeaderViewHolder extends RecyclerView.ViewHolder {
View headerView;
public HeaderViewHolder(View view) {
super(view);
//代碼省略
}
}
}
~~~
上述代碼中有一個**getItemViewType 函數**,在該函數中**把第一個item 的viewType 設置為0,其他位置的設置為1** 。**這樣在onCreateViewHolder 函數中就可以根據不同的viewType ( onCreateViewHolder的第二個參數,不是position ,而是viewType )來創建不同的ViewHolder,不同的ViewHolder 含有不同的View,因此達到了不同顯示效果的目的**。上文中的開發技術前線網站的第一項就是一個Header View,這個Header View是一個自動滾動的ViewPager,其他的都是普通的文本控件的組合視圖。
#### **參考文章:**
[Android源碼之ListView的適配器模式](http://blog.csdn.net/bboyfeiyu/article/details/43950185)