Android設計模式之適配器(Adapter)模式
本文實例為大家分享了Android適配器模式源碼,供大家參考,具體內(nèi)容如下
1. 模式介紹
1.1模式的定義:
適配器模式把一個類的接口變換成客戶端所期待的另一種接口,從而使原本因接口不匹配而無法在一起工作的兩個類能夠在一起工作。
1.2模式的使用場景:
用電源接口做例子,筆記本電腦的電源一般都是接受5V的電壓,但是我們生活中的電線電壓一般都是220V的輸出。這個時候就出現(xiàn)了不匹配的狀況,在軟件開發(fā)中我們稱之為接口不兼容,此時就需要適配器來進行一個接口轉(zhuǎn)換。在軟件開發(fā)中有一句話正好體現(xiàn)了這點:任何問題都可以加一個中間層來解決。這個層我們可以理解為這里的Adapter層,通過這層來進行一個接口轉(zhuǎn)換就達到了兼容的目的。
2.模式的簡單實現(xiàn)
2.1簡單實現(xiàn)的介紹:
在上述電源接口這個示例中,5V電壓就是Target接口,220v電壓就是Adaptee類,而將電壓從220V轉(zhuǎn)換到5V就是Adapter。
2.2類適配器模式:
/** * Target角色 */ public interface FiveVolt { public int getVolt5(); } /** * Adaptee角色,需要被轉(zhuǎn)換的對象 */ public class Volt220 { public int getVolt220() { return 220; } } // adapter角色 public class ClassAdapter extends Volt220 implements FiveVolt { @Override public int getVolt5() { return 5; } }
Target角色給出了需要的目標接口,而Adaptee類則是需要被轉(zhuǎn)換的對象。Adapter則是將Volt220轉(zhuǎn)換成Target的接口。對應的是Target的目標是要獲取5V的輸出電壓,而Adaptee即正常輸出電壓是220V,此時我們就需要電源適配器類將220V的電壓轉(zhuǎn)換為5V電壓,解決接口不兼容的問題。
public class Test { public static void main(String[] args) { ClassAdapter adapter = new ClassAdapter(); System.out.println("輸出電壓 : " + adapter.getVolt5()); } }
2.3.Android源碼中的模式實現(xiàn)
與類的適配器模式一樣,對象的適配器模式把被適配的類的API轉(zhuǎn)換成為目標類的API,與類的適配器模式不同的是,對象的適配器模式不是使用繼承關系連接到Adaptee類,而是使用代理關系連接到Adaptee類。
從圖2可以看出,Adaptee類 ( Volt220 ) 并沒有getVolt5()方法,而客戶端則期待這個方法。為使客戶端能夠使用Adaptee類,需要提供一個包裝類Adapter。這個包裝類包裝了一個Adaptee的實例,從而此包裝類能夠把Adaptee的API與Target類的API銜接起來。Adapter與Adaptee是委派關系,這決定了適配器模式是對象的。
/** * Target角色 */ public interface FiveVolt { public int getVolt5(); } /** * Adaptee角色,需要被轉(zhuǎn)換的對象 */ public class Volt220 { public int getVolt220() { return 220; } } // 對象適配器模式 public class ObjectAdapter implements FiveVolt { Volt220 mVolt220; public ObjectAdapter(Volt220 adaptee) { mVolt220 = adaptee; } public int getVolt220() { return mVolt220.getVolt220(); } @Override public int getVolt5() { return 5; } }
2.4.類適配器和對象適配器的權衡
*類適配器使用對象繼承的方式,是靜態(tài)的定義方式;而對象適配器使用對象組合的方式,是動態(tài)組合的方式。
*對于類適配器,由于適配器直接繼承了Adaptee,使得適配器不能和Adaptee的子類一起工作,因為繼承是靜態(tài)的關系,當適配器繼承了Adaptee后,就不可能再去處理Adaptee的子類了。對于對象適配器,一個適配器可以把多種不同的源適配到同一個目標。換言之,同一個適配器可以把源類和它的子類都適配到目標接口。因為對象適配器采用的是對象組合的關系,只要對象類型正確,是不是子類都無所謂。
*對于類適配器,適配器可以重定義Adaptee的部分行為,相當于子類覆蓋父類的部分實現(xiàn)方法。對于對象適配器,要重定義Adaptee的行為比較困難,這種情況下,需要定義Adaptee的子類來實現(xiàn)重定義,然后讓適配器組合子類。雖然重定義Adaptee的行為比較困難,但是想要增加一些新的行為則方便的很,而且新增加的行為可同時適用于所有的源。
*對于類適配器,僅僅引入了一個對象,并不需要額外的引用來間接得到Adaptee。對于對象適配器,需要額外的引用來間接得到Adaptee。
建議盡量使用對象適配器的實現(xiàn)方式,多用合成/聚合、少用繼承。當然,具體問題具體分析,根據(jù)需要來選用實現(xiàn)方式,最適合的才是最好的。
3.Android ListView中的Adapter模式
在開發(fā)過程中,ListView的Adapter是我們最為常見的類型之一。一般的用法大致如下:
// 適配器 public class MyAdapter extends BaseAdapter{ private LayoutInflater mInflater; List<String> mDatas ; public MyAdapter(Context context, List<String> datas){ this.mInflater = LayoutInflater.from(context); mDatas = datas ; } @Override public int getCount() { return mDatas.size(); } @Override public String getItem(int pos) { return mDatas.get(pos); } @Override public long getItemId(int pos) { return pos; } // 解析、設置、緩存convertView以及相關內(nèi)容 @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder = null; // Item View的復用 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 { holder = (ViewHolder)convertView.getTag(); } holder.title.setText(mDatas.get(position)); return convertView; } }
這看起來似乎還挺麻煩的,看到這里我們不禁要問,ListView為什么要使用Adapter模式呢?
我們知道,作為最重要的View,ListView需要能夠顯示各式各樣的視圖,每個人需要的顯示效果各不相同,顯示的數(shù)據(jù)類型、數(shù)量等也千變?nèi)f化。那么如何隔離這種變化尤為重要。
Android的做法是增加一個Adapter層來應對變化,將ListView需要的接口抽象到Adapter對象中,這樣只要用戶實現(xiàn)了Adapter的接口,ListView就可以按照用戶設定的顯示效果、數(shù)量、數(shù)據(jù)來顯示特定的Item View。
通過代理數(shù)據(jù)集來告知ListView數(shù)據(jù)的個數(shù)( getCount函數(shù) )以及每個數(shù)據(jù)的類型( getItem函數(shù) ),最重要的是要解決Item View的輸出。Item View千變?nèi)f化,但終究它都是View類型,Adapter統(tǒng)一將Item View輸出為View ( getView函數(shù) ),這樣就很好的應對了Item View的可變性。
那么ListView是如何通過Adapter模式 ( 不止Adapter模式 )來運作的呢 ?我們一起來看一看。
ListView繼承自AbsListView,Adapter定義在AbsListView中,我們看一看這個類。
public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher, ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener, ViewTreeObserver.OnTouchModeChangeListener, RemoteViewsAdapter.RemoteAdapterConnectionCallback { ListAdapter mAdapter ; // 關聯(lián)到Window時調(diào)用的函數(shù) @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(); } mIsAttached = true; } /** * 子類需要覆寫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; if (changed) { int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { getChildAt(i).forceLayout(); } mRecycler.markChildrenDirty(); } if (mFastScroller != null && mItemCount != mOldItemCount) { mFastScroller.onItemCountChanged(mOldItemCount, mItemCount); } // 布局Child View layoutChildren(); mInLayout = false; mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR; } // 獲取一個Item View View obtainView(int position, boolean[] isScrap) { isScrap[0] = false; View scrapView; // 從緩存的Item View中獲取,ListView的復用機制就在這里 scrapView = mRecycler.getScrapView(position); View child; if (scrapView != null) { // 代碼省略 child = mAdapter.getView(position, scrapView, this); // 代碼省略 } else { child = mAdapter.getView(position, null, this); // 代碼省略 } return child; } }
AbsListView定義了集合視圖的框架,比如Adapter模式的應用、復用Item View的邏輯、布局Item View的邏輯等。子類只需要覆寫特定的方法即可實現(xiàn)集合視圖的功能,例如ListView。
ListView中的相關方法。
@Override protected void layoutChildren() { // 代碼省略 try { super.layoutChildren(); invalidate(); // 代碼省略 // 根據(jù)布局模式來布局Item View 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: // 代碼省略 break; } } // 從上到下填充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++; } return selectedView; } // 添加Item View private View makeAndAddView(int position, int y, boolean flow, int childrenLeft, boolean selected) { View child; // 代碼省略 // Make a new view for this position, or convert an unused view if possible child = obtainView(position, mIsScrap); // This needs to be positioned and measured setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]); return child; }
ListView覆寫了AbsListView中的layoutChilden函數(shù),在該函數(shù)中根據(jù)布局模式來布局Item View。Item View的個數(shù)、樣式都通過Adapter對應的方法來獲取,獲取個數(shù)、Item View之后,將這些Item View布局到ListView對應的坐標上,再加上Item View的復用機制,整個ListView就基本運轉(zhuǎn)起來了。
當然這里的Adapter并不是經(jīng)典的適配器模式,但是卻是對象適配器模式的優(yōu)秀示例,也很好的體現(xiàn)了面向?qū)ο蟮囊恍┗驹瓌t。這里的Target角色和Adapter角色融合在一起,Adapter中的方法就是目標方法;而Adaptee角色就是ListView的數(shù)據(jù)集與Item View,Adapter代理數(shù)據(jù)集,從而獲取到數(shù)據(jù)集的個數(shù)、元素。
通過增加Adapter一層來將Item View的操作抽象起來,ListView等集合視圖通過Adapter對象獲得Item的個數(shù)、數(shù)據(jù)元素、Item View等,從而達到適配各種數(shù)據(jù)、各種Item視圖的效果。因為Item View和數(shù)據(jù)類型千變?nèi)f化,Android的架構師們將這些變化的部分交給用戶來處理,通過getCount、getItem、getView等幾個方法抽象出來,也就是將Item View的構造過程交給用戶來處理,靈活地運用了適配器模式,達到了無限適配、擁抱變化的目的。
4.雜談
優(yōu)點與缺點
優(yōu)點
更好的復用性
系統(tǒng)需要使用現(xiàn)有的類,而此類的接口不符合系統(tǒng)的需要。那么通過適配器模式就可以讓這些功能得到更好的復用。
更好的擴展性
在實現(xiàn)適配器功能的時候,可以調(diào)用自己開發(fā)的功能,從而自然地擴展系統(tǒng)的功能。
缺點
過多的使用適配器,會讓系統(tǒng)非常零亂,不易整體進行把握。比如,明明看到調(diào)用的是A接口,其實內(nèi)部被適配成了B接口的實現(xiàn),一個系統(tǒng)如果太多出現(xiàn)這種情況,無異于一場災難。因此如果不是很有必要,可以不使用適配器,而是直接對系統(tǒng)進行重構。
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
- 舉例講解Android應用中SimpleAdapter簡單適配器的使用
- Android中 自定義數(shù)據(jù)綁定適配器BaseAdapter的方法
- Kotlin編寫Android適配器Adapter
- Android SimpleAdapter適配器使用詳解
- Android之自定義實現(xiàn)BaseAdapter(通用適配器一)
- Android ListView和Adapter數(shù)據(jù)適配器的簡單介紹
- Android控件系列之相冊Gallery&Adapter適配器入門&控件縮放動畫入門
- Android ListView適配器(Adapter)優(yōu)化方法詳解
- Android適配器(Adapter)的概念與自定義
相關文章
Android實現(xiàn)一個帶粘連效果的LoadingBar
Loading效果相信大家應該都實現(xiàn)過,最近發(fā)現(xiàn)了一個不錯的效果,決定分享給大家,所以下面這篇文章主要給大家介紹了關于利用Android實現(xiàn)一個帶粘連效果的LoadingBar的相關資料,需要的朋友可以參考借鑒,下面來一起看看吧。2017-12-12Notification消息通知 自定義消息通知內(nèi)容布局
這篇文章主要為大家詳細介紹了Notification消息通知,消息合并且顯示條數(shù),自定義消息通知內(nèi)容布局,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-09-09詳解Android開發(fā)中Activity的四種launchMode
這篇文章主要介紹了Android開發(fā)中Activity的四種launchMode,launchMode主要用于控制多個Activity間的跳轉(zhuǎn),需要的朋友可以參考下2016-03-03Android Studio 4.0新特性及升級異常問題的解決方案
這篇文章主要介紹了Android Studio 4.0新特性及升級異常的相關問題,本文給大家分享解決方案,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-06-06Android使用SQLite數(shù)據(jù)庫的示例
本篇文章主要介紹了Android使用SQLite數(shù)據(jù)庫的示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-01-01