用途:将某个类的接口转换为我们想要的接口。
涉及到有三个角色:适配器,被适配对象,目标对象。
举个例子好理解:**看新闻的时候,一般左下角都会有个手语老师,方便听障人员观看新闻。这里面主持人就对应:被适配对象,手语老师就对应:适配器,听障人员就对应:目标对象。**手语老师是为了听障人员能够听懂主持人所讲的内容而来做适配,通俗一点也可以这么说:适配器就是负责翻译的。这里相信大家对第一段话适配器模式的用途应该能理解了吧。
上面的新闻例子用代码解释如下:
//首先定义一个Speaker主持人类:(被适配对象)
public class Speaker{
public String speak(){
return "这是主持人讲的新闻内容";
}
}
//接着定义一个翻译接口 (目标对象)
interface Translator{
String translate();
}
//手语老师 (适配器 )
class Adapter implements Translator{
private Speaker speaker;
public Adapter(Speaker speaker){
this.speaker = speaker;
}
@Override
public String translate() {
String speakContent = speaker.speak();
//这里进行转换成手语的处理
//.....(省略)
return "这是手语老师经过处理之后的手语内容";
}
}
适配器模式有两种:类适配器和对象适配器。上面代码的写法是对象适配器的写法,也就是将被适配对象作为适配器的成员变量,然后通过成员变量来调用方法;而类适配器是适配器去继承被适配对象,以此来获得方法。
类适配器写法:
//首先定义一个Speaker主持人类:(被适配对象)
public class Speaker{
public String speak(){
return "这是主持人讲的新闻内容";
}
}
//接着定义一个翻译接口 (目标对象)
interface Translator{
String translate();
}
//手语老师 (适配器 )
class Adapter extends Speaker implements Translator{
@Override
public String translate() {
String speakContent = speak();
//这里进行转换成手语的处理
//.....(省略)
return "这是手语老师经过处理之后的手语内容";
}
}
适配器模式在android中的应用非常广,最常见的比如Listview、GridView、RecyclerView等的Adapter。
以ListView为例,ListView用于显示列表,每一个item的数据和布局都不一样,但最后都能够看做是一个View,这就需要适配器来适配这些数据和ListView之间的关系。
平时我们写适配器,最基本的就是去继承BaseAdapter。而BaseAdapter实现了ListAdapter接口,ListAdapter接口又继承了Adapter接口,这就是adapter他们之间的关系。
首先看最顶层Adapter接口(将注释去掉):
public interface Adapter {
void registerDataSetObserver(DataSetObserver observer);
void unregisterDataSetObserver(DataSetObserver observer);
int getCount();
Object getItem(int position);
long getItemId(int position);
boolean hasStableIds();
View getView(int position, View convertView, ViewGroup parent);
static final int IGNORE_ITEM_VIEW_TYPE = AdapterView.ITEM_VIEW_TYPE_IGNORE;
int getItemViewType(int position);
int getViewTypeCount();
static final int NO_SELECTION = Integer.MIN_VALUE;
boolean isEmpty();
default @Nullable CharSequence[] getAutofillOptions() {
return null;
}
}
接下来看ListView:
public class ListView extends AbsListView {}
public abstract class AbsListView extends AdapterView<ListAdapter> {}
public abstract class AdapterView<T extends Adapter> extends ViewGroup {}
所以可以知道ListView就是一个ViewGroup,主要代码是在ListView和AbsListView中。
讲一下我们一般写适配器时需要重写的一些方法,它们底层都是咋做的。
例如getCount方法:
ListView的父类AbsListView中的onAttachedToWindow方法,会将我们重写的getCount()方法的值赋给mItemCount,这样就确定了Item的数量。
//关联到window时调用的方法
@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);
mDataChanged = true;
mOldItemCount = mItemCount;
//获取item的数量
mItemCount = mAdapter.getCount();
}
}
还有getView方法:
AbsListView的onLayout方法:
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// ....
layoutChildren();
// ....
}
onLayout方法中会调用layoutChildren方法来布局子控件,layoutChildren方法具体实现是在ListView中:
@Override
protected void layoutChildren() {
// 代码省略
try {
super.layoutChildren();
invalidate();
// 代码省略
// 根据布局模式来布局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 为例:可以看到在方法里面是通过makeAndAddView()方法来创建每一个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;
}
makeAndAddView方法:
private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
boolean selected) {
if (!mDataChanged) {
// Try to use an existing view for this position.
final View activeView = mRecycler.getActiveView(position);
if (activeView != null) {
// Found it. We're reusing an existing child, so it just needs
// to be positioned like a scrap view.
setupChild(activeView, position, y, flow, childrenLeft, selected, true);
return activeView;
}
}
// Make a new view for this position, or convert an unused view if
// possible.
final View child = obtainView(position, mIsScrap);
// This needs to be positioned and measured.
setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
return child;
}
里面又调用了obtainView方法:在obtainView方法中就用到了我们重写的getView方法了:
View obtainView(int position, boolean[] outMetadata) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView");
outMetadata[0] = false;
final View transientView = mRecycler.getTransientStateView(position);
if (transientView != null) {
final LayoutParams params = (LayoutParams) transientView.getLayoutParams();
if (params.viewType == mAdapter.getItemViewType(position)) {
//此处用到了我们重写的getView方法
final View updatedView = mAdapter.getView(position, transientView, this);
if (updatedView != transientView) {
setItemViewLayoutParams(updatedView, position);
mRecycler.addScrapView(updatedView, position);
}
}
outMetadata[0] = true;
// Finish the temporary detach started in addScrapView().
transientView.dispatchFinishTemporaryDetach();
return transientView;
}
//....(省略)
}
ListView中的Adapter就是很好的一个对象适配器示例,很好地满足了我们所需要不同布局和数据却要以某种相同类型的形式进行展现的这些需求。