Android設(shè)計(jì)模式之適配器(Adapter)模式
本文實(shí)例為大家分享了Android適配器模式源碼,供大家參考,具體內(nèi)容如下
1. 模式介紹
1.1模式的定義:
適配器模式把一個(gè)類的接口變換成客戶端所期待的另一種接口,從而使原本因接口不匹配而無法在一起工作的兩個(gè)類能夠在一起工作。
1.2模式的使用場(chǎng)景:
用電源接口做例子,筆記本電腦的電源一般都是接受5V的電壓,但是我們生活中的電線電壓一般都是220V的輸出。這個(gè)時(shí)候就出現(xiàn)了不匹配的狀況,在軟件開發(fā)中我們稱之為接口不兼容,此時(shí)就需要適配器來進(jìn)行一個(gè)接口轉(zhuǎn)換。在軟件開發(fā)中有一句話正好體現(xiàn)了這點(diǎn):任何問題都可以加一個(gè)中間層來解決。這個(gè)層我們可以理解為這里的Adapter層,通過這層來進(jìn)行一個(gè)接口轉(zhuǎn)換就達(dá)到了兼容的目的。
2.模式的簡(jiǎn)單實(shí)現(xiàn)
2.1簡(jiǎn)單實(shí)現(xiàn)的介紹:
在上述電源接口這個(gè)示例中,5V電壓就是Target接口,220v電壓就是Adaptee類,而將電壓從220V轉(zhuǎn)換到5V就是Adapter。
2.2類適配器模式:
/**
* Target角色
*/
public interface FiveVolt {
public int getVolt5();
}
/**
* Adaptee角色,需要被轉(zhuǎn)換的對(duì)象
*/
public class Volt220 {
public int getVolt220() {
return 220;
}
}
// adapter角色
public class ClassAdapter extends Volt220 implements FiveVolt {
@Override
public int getVolt5() {
return 5;
}
}
Target角色給出了需要的目標(biāo)接口,而Adaptee類則是需要被轉(zhuǎn)換的對(duì)象。Adapter則是將Volt220轉(zhuǎn)換成Target的接口。對(duì)應(yīng)的是Target的目標(biāo)是要獲取5V的輸出電壓,而Adaptee即正常輸出電壓是220V,此時(shí)我們就需要電源適配器類將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源碼中的模式實(shí)現(xiàn)
與類的適配器模式一樣,對(duì)象的適配器模式把被適配的類的API轉(zhuǎn)換成為目標(biāo)類的API,與類的適配器模式不同的是,對(duì)象的適配器模式不是使用繼承關(guān)系連接到Adaptee類,而是使用代理關(guān)系連接到Adaptee類。
從圖2可以看出,Adaptee類 ( Volt220 ) 并沒有g(shù)etVolt5()方法,而客戶端則期待這個(gè)方法。為使客戶端能夠使用Adaptee類,需要提供一個(gè)包裝類Adapter。這個(gè)包裝類包裝了一個(gè)Adaptee的實(shí)例,從而此包裝類能夠把Adaptee的API與Target類的API銜接起來。Adapter與Adaptee是委派關(guān)系,這決定了適配器模式是對(duì)象的。
/**
* Target角色
*/
public interface FiveVolt {
public int getVolt5();
}
/**
* Adaptee角色,需要被轉(zhuǎn)換的對(duì)象
*/
public class Volt220 {
public int getVolt220() {
return 220;
}
}
// 對(duì)象適配器模式
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.類適配器和對(duì)象適配器的權(quán)衡
*類適配器使用對(duì)象繼承的方式,是靜態(tài)的定義方式;而對(duì)象適配器使用對(duì)象組合的方式,是動(dòng)態(tài)組合的方式。
*對(duì)于類適配器,由于適配器直接繼承了Adaptee,使得適配器不能和Adaptee的子類一起工作,因?yàn)槔^承是靜態(tài)的關(guān)系,當(dāng)適配器繼承了Adaptee后,就不可能再去處理Adaptee的子類了。對(duì)于對(duì)象適配器,一個(gè)適配器可以把多種不同的源適配到同一個(gè)目標(biāo)。換言之,同一個(gè)適配器可以把源類和它的子類都適配到目標(biāo)接口。因?yàn)閷?duì)象適配器采用的是對(duì)象組合的關(guān)系,只要對(duì)象類型正確,是不是子類都無所謂。
*對(duì)于類適配器,適配器可以重定義Adaptee的部分行為,相當(dāng)于子類覆蓋父類的部分實(shí)現(xiàn)方法。對(duì)于對(duì)象適配器,要重定義Adaptee的行為比較困難,這種情況下,需要定義Adaptee的子類來實(shí)現(xiàn)重定義,然后讓適配器組合子類。雖然重定義Adaptee的行為比較困難,但是想要增加一些新的行為則方便的很,而且新增加的行為可同時(shí)適用于所有的源。
*對(duì)于類適配器,僅僅引入了一個(gè)對(duì)象,并不需要額外的引用來間接得到Adaptee。對(duì)于對(duì)象適配器,需要額外的引用來間接得到Adaptee。
建議盡量使用對(duì)象適配器的實(shí)現(xiàn)方式,多用合成/聚合、少用繼承。當(dāng)然,具體問題具體分析,根據(jù)需要來選用實(shí)現(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;
}
// 解析、設(shè)置、緩存convertView以及相關(guān)內(nèi)容
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
// Item View的復(fù)用
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需要能夠顯示各式各樣的視圖,每個(gè)人需要的顯示效果各不相同,顯示的數(shù)據(jù)類型、數(shù)量等也千變?nèi)f化。那么如何隔離這種變化尤為重要。
Android的做法是增加一個(gè)Adapter層來應(yīng)對(duì)變化,將ListView需要的接口抽象到Adapter對(duì)象中,這樣只要用戶實(shí)現(xiàn)了Adapter的接口,ListView就可以按照用戶設(shè)定的顯示效果、數(shù)量、數(shù)據(jù)來顯示特定的Item View。
通過代理數(shù)據(jù)集來告知ListView數(shù)據(jù)的個(gè)數(shù)( getCount函數(shù) )以及每個(gè)數(shù)據(jù)的類型( getItem函數(shù) ),最重要的是要解決Item View的輸出。Item View千變?nèi)f化,但終究它都是View類型,Adapter統(tǒng)一將Item View輸出為View ( getView函數(shù) ),這樣就很好的應(yīng)對(duì)了Item View的可變性。
那么ListView是如何通過Adapter模式 ( 不止Adapter模式 )來運(yùn)作的呢 ?我們一起來看一看。
ListView繼承自AbsListView,Adapter定義在AbsListView中,我們看一看這個(gè)類。
public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher,
ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener,
ViewTreeObserver.OnTouchModeChangeListener,
RemoteViewsAdapter.RemoteAdapterConnectionCallback {
ListAdapter mAdapter ;
// 關(guān)聯(lián)到Window時(shí)調(diào)用的函數(shù)
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
// 代碼省略
// 給適配器注冊(cè)一個(gè)觀察者,該模式下一篇介紹。
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;
}
// 獲取一個(gè)Item View
View obtainView(int position, boolean[] isScrap) {
isScrap[0] = false;
View scrapView;
// 從緩存的Item View中獲取,ListView的復(fù)用機(jī)制就在這里
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模式的應(yīng)用、復(fù)用Item View的邏輯、布局Item View的邏輯等。子類只需要覆寫特定的方法即可實(shí)現(xiàn)集合視圖的功能,例如ListView。
ListView中的相關(guān)方法。
@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的個(gè)數(shù)、樣式都通過Adapter對(duì)應(yīng)的方法來獲取,獲取個(gè)數(shù)、Item View之后,將這些Item View布局到ListView對(duì)應(yīng)的坐標(biāo)上,再加上Item View的復(fù)用機(jī)制,整個(gè)ListView就基本運(yùn)轉(zhuǎn)起來了。
當(dāng)然這里的Adapter并不是經(jīng)典的適配器模式,但是卻是對(duì)象適配器模式的優(yōu)秀示例,也很好的體現(xiàn)了面向?qū)ο蟮囊恍┗驹瓌t。這里的Target角色和Adapter角色融合在一起,Adapter中的方法就是目標(biāo)方法;而Adaptee角色就是ListView的數(shù)據(jù)集與Item View,Adapter代理數(shù)據(jù)集,從而獲取到數(shù)據(jù)集的個(gè)數(shù)、元素。
通過增加Adapter一層來將Item View的操作抽象起來,ListView等集合視圖通過Adapter對(duì)象獲得Item的個(gè)數(shù)、數(shù)據(jù)元素、Item View等,從而達(dá)到適配各種數(shù)據(jù)、各種Item視圖的效果。因?yàn)镮tem View和數(shù)據(jù)類型千變?nèi)f化,Android的架構(gòu)師們將這些變化的部分交給用戶來處理,通過getCount、getItem、getView等幾個(gè)方法抽象出來,也就是將Item View的構(gòu)造過程交給用戶來處理,靈活地運(yùn)用了適配器模式,達(dá)到了無限適配、擁抱變化的目的。
4.雜談
優(yōu)點(diǎn)與缺點(diǎn)
優(yōu)點(diǎn)
更好的復(fù)用性
系統(tǒng)需要使用現(xiàn)有的類,而此類的接口不符合系統(tǒng)的需要。那么通過適配器模式就可以讓這些功能得到更好的復(fù)用。
更好的擴(kuò)展性
在實(shí)現(xiàn)適配器功能的時(shí)候,可以調(diào)用自己開發(fā)的功能,從而自然地?cái)U(kuò)展系統(tǒng)的功能。
缺點(diǎn)
過多的使用適配器,會(huì)讓系統(tǒng)非常零亂,不易整體進(jìn)行把握。比如,明明看到調(diào)用的是A接口,其實(shí)內(nèi)部被適配成了B接口的實(shí)現(xiàn),一個(gè)系統(tǒng)如果太多出現(xiàn)這種情況,無異于一場(chǎng)災(zāi)難。因此如果不是很有必要,可以不使用適配器,而是直接對(duì)系統(tǒng)進(jìn)行重構(gòu)。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- 舉例講解Android應(yīng)用中SimpleAdapter簡(jiǎn)單適配器的使用
- Android中 自定義數(shù)據(jù)綁定適配器BaseAdapter的方法
- Kotlin編寫Android適配器Adapter
- Android SimpleAdapter適配器使用詳解
- Android之自定義實(shí)現(xiàn)BaseAdapter(通用適配器一)
- Android ListView和Adapter數(shù)據(jù)適配器的簡(jiǎn)單介紹
- Android控件系列之相冊(cè)Gallery&Adapter適配器入門&控件縮放動(dòng)畫入門
- Android ListView適配器(Adapter)優(yōu)化方法詳解
- Android適配器(Adapter)的概念與自定義
相關(guān)文章
Android實(shí)現(xiàn)一個(gè)帶粘連效果的LoadingBar
Loading效果相信大家應(yīng)該都實(shí)現(xiàn)過,最近發(fā)現(xiàn)了一個(gè)不錯(cuò)的效果,決定分享給大家,所以下面這篇文章主要給大家介紹了關(guān)于利用Android實(shí)現(xiàn)一個(gè)帶粘連效果的LoadingBar的相關(guān)資料,需要的朋友可以參考借鑒,下面來一起看看吧。2017-12-12
Notification消息通知 自定義消息通知內(nèi)容布局
這篇文章主要為大家詳細(xì)介紹了Notification消息通知,消息合并且顯示條數(shù),自定義消息通知內(nèi)容布局,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-09-09
android 自定義TabActivity的實(shí)例方法
系統(tǒng)自帶的TabActivity的效果不甚理想。開發(fā)中對(duì)TabActivity自定義可能有兩種:第一種:改變TAB行的位置,如放到頁面下方。第二種:對(duì)TabHost圖片的自定義2013-11-11
詳解Android開發(fā)中Activity的四種launchMode
這篇文章主要介紹了Android開發(fā)中Activity的四種launchMode,launchMode主要用于控制多個(gè)Activity間的跳轉(zhuǎn),需要的朋友可以參考下2016-03-03
Android Studio 4.0新特性及升級(jí)異常問題的解決方案
這篇文章主要介紹了Android Studio 4.0新特性及升級(jí)異常的相關(guān)問題,本文給大家分享解決方案,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06
Android使用SQLite數(shù)據(jù)庫(kù)的示例
本篇文章主要介紹了Android使用SQLite數(shù)據(jù)庫(kù)的示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-01-01

