適配器模式在我們開發(fā)中使用率極高,從代碼中隨處可見的Adapter可以判斷出來。從最早的ListView,GridView到現(xiàn)在最新的RecyclerView都需要使用Adapter, 并且在開發(fā)過程中遇到的優(yōu)化問題,出錯概率較大的地方也基本都出自Adapter, 這也是一個讓人又愛又恨的角色.
說到底,適配器是將兩個不兼容的類融合在一起,它有點像粘合劑,將不同的東西通過一種轉(zhuǎn)換使得它們能夠協(xié)作起來。
這個模式的UML類圖如下.

適配器模式應(yīng)用的簡單示例
用電影接口做栗子,筆記本電腦的電源一般在5V電壓,但是在我們生活中的電線電壓一般是220V。這個時候出現(xiàn)了不匹配的狀況,在軟件開發(fā)中稱為接口不兼容,此時就需要適配器來進(jìn)行一個接口轉(zhuǎn)換. 我們可以加一個Adapter層來進(jìn)行接口轉(zhuǎn)換.
類適配器模式
在上述電源接口這個示例中,5V電壓就是Target接口,220V電壓就是Adaptee類,而將電壓從220v轉(zhuǎn)換到5v就是Adapter.
具體程序如下所示.
public interface FiveVolt {
int getVolt5();
}
public class Volt220 {
public int getVolt220(){
return 220;
}
}
public class VoltAdapter extends Volt220 implements FiveVolt {
@Override
public int getVolt5() {
return 5;
}
}
public class Test {
public static void main(String[] args){
VoltAdapter adapter = new VoltAdapter();
System.out.println("輸出電壓 : "+adapter.getVolt5());
}
}
對象適配器模式
public interface FiveVolt {
int getVolt5();
}
public class Volt220 {
public int getVolt220(){
return 220;
}
}
public class VoltAdapter implements FiveVolt{
Volt220 mVolt220;
public VoltAdapter(Volt220 mVolt220) {
this.mVolt220 = mVolt220;
}
public int getVolt220(){
return mVolt220.getVolt220();
}
@Override
public int getVolt5() {
return 5;
}
}
public class Test {
public static void main(String[] args){
VoltAdapter adapter = new VoltAdapter(new Volt220());
System.out.println("輸出電壓 : "+adapter.getVolt5());
}
}
這種實現(xiàn)方式直接將要適配的對象傳遞到Adapter中,使用組合的形式實現(xiàn)接口兼容的效果。這比類適配器方式更為靈活。
Android源碼中的適配器模式-ListView
我們知道ListView作為最重要的控件,它需要顯示各式各樣的視圖,每個人需要顯示的效果不相同,顯示的數(shù)據(jù)類型,數(shù)量等也千變?nèi)f化,那么如何應(yīng)對這種變化成為架構(gòu)師要考慮的最重要特性之一.
Android的做法是增加一個Adapter層來隔離變化,將ListView需要的關(guān)于Item View接口抽象到Adapter對象中,并且在ListView內(nèi)部調(diào)Adapter這些接口完成布局等操作。這樣用戶只要實現(xiàn)了Adapter的接口,并且將Adapter設(shè)置給ListView, ListView就可以按照用戶設(shè)定的UI效果,數(shù)量,數(shù)據(jù)顯示每一項數(shù)據(jù).

我們發(fā)現(xiàn)在ListView中并沒有Adapter相關(guān)的成員變量,其實Adapter在ListView的父類AbsListView中.
public class ListView extends AbsListView {
ListAdapter mAdapter;
}
//關(guān)聯(lián)到Window時調(diào)用,獲取調(diào)用Adapter中的getCount方法等
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
//代碼省略
//給適配器注冊一個觀察者
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的數(shù)量,調(diào)用的是mAdapter的getCount方法
mItemCount = mAdapter.getCount();
}
}
//子類需要復(fù)寫layoutChildren()函數(shù)來布局child view, 也就是item view
@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();
mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;
// TODO: Move somewhere sane. This doesn't belong in onLayout().
if (mFastScroll != null) {
mFastScroll.onItemCountChanged(getChildCount(), mItemCount);
}
mInLayout = false;
}
AbsListView定義了集合視圖的邏輯框架,比如Adapter模式的應(yīng)用,復(fù)用Item View的邏輯,布局子視圖的邏輯等,子類只需要復(fù)寫特定的方法即可實現(xiàn)集合視圖的功能。首先在AbsListView類型的View中添加窗口時會調(diào)用getCount函數(shù)獲取元素的個數(shù),然后在onLayout函數(shù)中調(diào)用layoutChilden函數(shù)對所有子元素進(jìn)行布局。layoutChilden實際是在ListView中實現(xiàn).
@Override
protected void layoutChildren() {
//代碼省略
switch (mLayoutMode) {
//代碼省略
case LAYOUT_FORCE_BOTTOM:
sel = fillUp(mItemCount - 1, childrenBottom);
adjustViewsUpOrDown();
break;
case LAYOUT_FORCE_TOP:
mFirstPosition = 0;
sel = fillFromTop(childrenTop);
adjustViewsUpOrDown();
break;
default:
//代碼省略
break;
}
}
ListView復(fù)寫了AbsListView中的layoutChilden函數(shù),在該函數(shù)中根據(jù)布局模式來布局Item View, 例如,默認(rèn)情況是從上到下開始布局,也有可能從下到上開始布局.
//從下到上填充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) {
// is this the selected item?
boolean selected = pos == mSelectedPosition;
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;
}
//從下到上填充
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函數(shù)獲取一個View, 這個View就是ListView的每一項的視圖,這里有一個pos函數(shù),也就是對應(yīng)這個View是ListView中的第幾項. 我們來看看makeAndAddView中的實現(xiàn)
private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
boolean selected) {
//代碼省略
//獲取一個item View
final View child = obtainView(position, mIsScrap);
//將item View設(shè)置到對應(yīng)的地方
setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
return child;
}
makeAndAddView主要分兩個步驟,第一是根據(jù)position獲取一個item view, 然后將這個view布局到特定的位置。