适配器模式:把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。
场景:
用电源接口做例子,笔记本电脑的电源一般都是接受5V的电压,但是我们生活中的电线电压一般都是220V的输出。 这个时候就出现了不匹配的状况,在软件开发中我们称之为接口不兼容,此时就需要适配器来进行一个接口转换。 在软件开发中有一句话正好体现了这点:任何问题都可以加一个中间层来解决。这个层我们可以理解为这里的Adapter层,通过这层来进行一个接口转换就达到了兼容的目的。
角色介绍:
目标(Target)角色:这就是所期待得到的接口。注意:由于这里讨论的是类适配器模式,因此目标不可以是类。源(Adapee)角色:现在需要适配的接口。
适配器(Adaper)角色:适配器类是本模式的核心。适配器把源接口转换成目标接口。显然,这一角色不可以是接口,而必须是具体类。
在上述电源接口这个示例中,5V电压就是Target接口,220v电压就是Adaptee类,而将电压从220V转换到5V就是Adapter。
//本来是220的电压,现在用户需求转成5v的电压public void testAdapterMode2(){ObjectAdapter adapter=new ObjectAdapter(new Volt220()); adapter.getVolt5();}
public class ObjectAdapter implements FiveVolt {Volt220 mVolt220; public ObjectAdapter(Volt220 adaptee) {mVolt220 = adaptee; }public int getVolt220() {return mVolt220.getVolt220(); }@Override public int getVolt5() {return 0; }}
/*** Created by Administrator on /9/15.* target角色*/public interface FiveVolt {public int getVolt5();}
/*** Created by Administrator on /9/15.* Adaptee角色,需要被转换的对象*/public class Volt220 {public int getVolt220() {return 220; }}
总结:
ListView中的Adapter模式
在开发过程中,ListView的Adapter是我们最为常见的类型之一。一般的用法大致如下:
public class HottestAdapter extends BaseAdapter implements OnClickListener, DownloadManager.DownloadListener, ApkHelper.InstallListener, ApkHelper.UnInstallListener {String TAG="HottestAdapter"; private ArrayList<LaunNearbyHotBean> list; private LayoutInflater inflater; private Context mContext; private DownloadManager downloadManager; private String mDownloadSavePath; private ApkHelper apkHelper2; private ApkInstalledManager installedManager;// private ExpandableListView listView; public HottestAdapter(ArrayList<LaunRecommendGroup> groupDatas, Context mContext) {list = new ArrayList<LaunNearbyHotBean>(); for (LaunRecommendGroup group : groupDatas) {list.addAll(group.getApkList()); }this.mContext = mContext; inflater = LayoutInflater.from(mContext); downloadManager = DownloadManager.getInstance(mContext); mDownloadSavePath = HJStorageUtil.getDirDownload(mContext).getAbsolutePath() + "/"; apkHelper2 = ApkHelper.getInstance(mContext.getApplicationContext()); apkHelper2.registListener(this); apkHelper2.registUninstallListener(this); installedManager = ApkInstalledManager.getInstance(mContext.getApplicationContext()); EventBus.getDefault().register(this); mSwitchInstallPlungi=CXPolicyUtil.isShowUpdateSwitch(mContext, CXPolicyUtil.ON_HOTTEST_INSTALL_SWITCH); mSwitchInstallPlungi=!mSwitchInstallPlungi; if(PermissionUtilsO.isAndroidO()){mSwitchInstallPlungi=false; }// if(CXConfig.DEBUG){// mSwitchInstallPlungi=true;// } }public void destroy(){ EventBus.getDefault().unregister(this);}/**应用装容器还是装在系统 * false,装系统, * True,装容器 * * */ private boolean mSwitchInstallPlungi=false; @Override public int getCount() {return list.size(); }@Override public Object getItem(int position) {return list.get(position); }@Override public long getItemId(int position) {return position; }@Override public View getView(int position, View convertView, ViewGroup parent) {ViewHolder viewHolder = null; if (convertView == null) {viewHolder = new ViewHolder(); convertView = inflater.inflate(R.layout.launcher_recommend_item, null); viewHolder.ivAppIcon = (RoundImageView) convertView.findViewById(R.id.iv_appIc); viewHolder.tvAppName = (TextView) convertView.findViewById(R.id.tv_name); viewHolder.tvDownloadCount = (TextView) convertView.findViewById(R.id.tv_downNumber); viewHolder.tvDescription = (TextView) convertView.findViewById(R.id.tv_description); viewHolder.tvAppSize = (TextView) convertView.findViewById(R.id.tv_appSize); viewHolder.rlDownloadLayout = (RelativeLayout) convertView.findViewById(R.id.down_app); viewHolder.pbDownload = (ProgressBar) convertView.findViewById(R.id.child_right_progress_green); viewHolder.tvAppStatus = (TextView) convertView.findViewById(R.id.child_right_progress_txt); convertView.setTag(viewHolder); } else {viewHolder = (ViewHolder) convertView.getTag(); }LaunNearbyHotBean apkModel = (LaunNearbyHotBean) getItem(position); apkModel.fromSource=5; viewHolder.position = position; viewHolder.key = apkModel.packageName; ImageLoaderUtil.loadAppIcon(viewHolder.ivAppIcon, apkModel.iconUrl); String showTitle=apkModel.title; if((Integer.valueOf(apkModel.serverApkOrg))>YYB_VALUE){showTitle=YYB_FLAG+showTitle; }viewHolder.tvAppName.setText(showTitle); viewHolder.tvDownloadCount.setText(apkModel.getDownloadCount()); viewHolder.tvDescription.setText(apkModel.getAppDescription());// if(apkModel.title.equals("天猫")){// CXLog.e(TAG,"sizeFormat(apkModel.getSize()="+sizeFormat(apkModel.getSize())+"apkModel.getSize()=="+apkModel.getSize());// }//// CXLog.e("FreeInstallAdapter", "model=====" + apkModel.title +//"packageName===" + apkModel.packageName+"apkModel.versionCode=="+apkModel.versionCode);// CXLog.e(TAG,"apkModelUrl=="+apkModel.downloadUrl); viewHolder.tvAppSize.setText(sizeFormat(apkModel.getSize())); setAppStatus(viewHolder, apkModel); viewHolder.rlDownloadLayout.setTag(viewHolder); viewHolder.rlDownloadLayout.setOnClickListener(this); return convertView; }
Android的做法是增加一个Adapter层来应对变化,将ListView需要的接口抽象到Adapter对象中,这样只要用户实现了Adapter的接口,ListView就可以按照用户设定的显示效果、数量、数据来显示特定的Item View。
通过代理数据集来告知ListView数据的个数( getCount函数 )以及每个数据的类型( getItem函数 ),最重要的是要解决Item View的输出。Item View千变万化,但终究它都是View类型,Adapter统一将Item View输出为View( getView函数 ),这样就很好的应对了Item View的可变性
public class ListView extends AbsListView {/** * Used to indicate a no preference for a position type. */ static final intNO_POSITION= -1; /** * When arrow scrolling, ListView will never scroll more than this factor * times the height of the list. */ private static final floatMAX_SCROLL_FACTOR= 0.33f; /** * When arrow scrolling, need a certain amount of pixels to preview next * items. This is usually the fading edge, but if that is small enough, * we want to make sure we preview at least this many pixels. */ private static final intMIN_SCROLL_PREVIEW_PIXELS= 2; /** * A class that represents a fixed view in a list, for example a header at the top * or a footer at the bottom. */ public class FixedViewInfo {/** The view to add to the list */public View view; /** The data backing the view. This is returned from {@linkListAdapter#getItem(int)}. */public Object data; /**<code>true</code>if the fixed view should be selectable in the list */public boolean isSelectable; }private ArrayList<FixedViewInfo> mHeaderViewInfos = Lists.newArrayList(); private ArrayList<FixedViewInfo> mFooterViewInfos = Lists.newArrayList(); Drawable mDivider; int mDividerHeight; Drawable mOverScrollHeader; Drawable mOverScrollFooter; private boolean mIsCacheColorOpaque; private boolean mDividerIsOpaque; private boolean mHeaderDividersEnabled; private boolean mFooterDividersEnabled; private boolean mAreAllItemsSelectable = true; private boolean mItemsCanFocus = false; // used for temporary calculations. private final Rect mTempRect = new Rect(); private Paint mDividerPaint; // the single allocated result per list view; kinda cheesey but avoids // allocating these thingies too often. private final ArrowScrollFocusResult mArrowScrollFocusResult = new ArrowScrollFocusResult(); // Keeps focused children visible through resizes private FocusSelector mFocusSelector; public ListView(Context context) {this(context, null); }public ListView(Context context, AttributeSet attrs) {this(context, attrs, R.attr.listViewStyle); }public ListView(Context context, AttributeSet attrs, int defStyleAttr) {this(context, attrs, defStyleAttr, 0); }
View obtainView(int position, boolean[] isScrap) {Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView"); isScrap[0] = false; // Check whether we have a transient state view. Attempt to re-bind the // data and discard the view if we fail. final View transientView = mRecycler.getTransientStateView(position); if (transientView != null) {final LayoutParams params = (LayoutParams) transientView.getLayoutParams(); // If the view type hasn't changed, attempt to re-bind the data. if (params.viewType == mAdapter.getItemViewType(position)) {final View updatedView = mAdapter.getView(position, transientView, this); // If we failed to re-bind the data, scrap the obtained view. if (updatedView != transientView) {setItemViewLayoutParams(updatedView, position);mRecycler.addScrapView(updatedView, position); }}isScrap[0] = true; // Finish the temporary detach started in addScrapView(). transientView.dispatchFinishTemporaryDetach(); return transientView; }
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; }}// 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;}
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;}
@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; mItemCount = mAdapter.getCount(); }}
ListView覆写了AbsListView中的layoutChilden函数,在该函数中根据布局模式来布局Item View。Item View的个数、样式都通过Adapter对应的方法来获取,获取个数、Item View之后,将这些Item View布局到ListView对应的坐标上,再加上Item View的复用机制,整个ListView就基本运转起来了。
当然这里的Adapter并不是经典的适配器模式,但是却是对象适配器模式的优秀示例,也很好的体现了面向对象的一些基本原则。这里的Target角色和Adapter角色融合在一起,Adapter中的方法就是目标方法;而Adaptee角色就是ListView的数据集与Item View,Adapter代理数据集,从而获取到数据集的个数、元素。
通过增加Adapter一层来将Item View的操作抽象起来,ListView等集合视图通过Adapter对象获得Item的个数、数据元素、Item View等,从而达到适配各种数据、各种Item视图的效果。因为Item View和数据类型千变万化,Android的架构师们将这些变化的部分交给用户来处理,通过getCount、getItem、getView等几个方法抽象出来,也就是将Item View的构造过程交给用户来处理,灵活地运用了适配器模式,达到了无限适配、拥抱变化的目的。
对于android开发者来说起,适配器模式简直太熟悉不过,有很多应用可以说是天天在直接或者间接的用到适配器模式,比如ListView。
ListView用于显示列表数据,但是作为列表数据集合有很多形式,有Array,有Cursor,我们需要对应的适配器作为桥梁,处理相应的数据(并能形成ListView所需要的视图)。
适配器模式,把一个类的接口变换成客户端所期待的另一种接口,从而使原本不匹配而无法在一起工作的两个,类能够在一起工作。
适配器模式分为类适配器模式和对象适配器模式。