将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。
看定义,也是有点难理解的,还是要结合一个列子来进行讲,有助于我们更好的理解。
生活中的手机充电器就是一个适配器的例子,手机一般都是在5V的电压下进行充电,但是外部的电压都是220V,那怎么办,这就需要充电器去适配了,将220V的电压输入转换为5V输出。
类适配器是通过实现Target接口以及继承Adaptee类来实现接口转换。例如,目标接口需要的是operation2,但是Adaptee对象只有一个operation3,因此就出现了不兼容的情况。此时通过Adapter实现一个operation2函数将Adaptee的operation3转换为Target需要的operation2,以此实现兼容。
在类适配器模式中,Target是一个接口,而不能是类。Adapter必须是具体的类,不能是接口。
用电源接口做例子,笔记本电脑的电源一般都是5V电压,但是生活中的电线电压一般都是220V。这个时候就出现了不匹配的状况,此时就需要适配器来进行一个接口转换。在软件开发中有一句话正好体现了这点:任何问题都可以加一个中间层来解决。这个中间层就是Adapter层,通过这层来进行一个接口转换达到兼容的目的。
在电源接口这个示例中,5V电压就是Target接口,220V电压就是Adaptee类,而将电压从220V转换到5V就是Adapter。
// Target接口
public interface FiveVolt {
public int getVolt5();
}
// Adaptee角色,需要被转换的对象
public class Volt220 {
public int getVolt220() {
return 220;
}
}
// Adapter角色,将220V的电压转换成5V的电压
public class VoltAdapter extends Volt220 implements FiveVolt {
@Override
public int getVolt5() {
return getVolt220() * 0 + 5;
}
}
// Target接口
public interface FiveVolt {
public int getVolt5();
}
// Adaptee角色,需要被转换的对象
public class Volt220 {
public int getVolt220() {
return 220;
}
}
// Adapter角色,将220V的电压转换成5V的电压
public class VoltAdapter extends Volt220 implements FiveVolt {
private Volt220 mVolt220 ;
public VoltAdapter (Volt220 mVolt220 ){
this.mVolt220 = mVolt220
}
@Override
public int getVolt5() {
return mVolt220.getVolt220() * 0 + 5;
}
}
接下来把他们做一下对比:
适配器模式在android中的应用非常广,最常见的ListView、GridView、RecyclerView等的Adapter。而,我们经常使用的ListView就是一个典范。
在使用ListView时,每一项的布局和数据都不一样,但是最后输出都可以看作是一个View,这就对应了上面的适配器模式应用场景的第三条:需要一个统一的输出接口,而输入端的接口不可预知。下面我们来看看ListView中的适配器模式。
首先我们来看看一般我们的Adapter类的结构:
class Adapter extends BaseAdapter {
private List<String> mDatas;
public Adapter(List<String> datas) {
mDatas = datas;
}
@Override
public int getCount() {
return mDatas.size();
}
@Override
public long getItemId(int position) { return position; }
@Override
public Object getItem(int position) { return mDatas.get(position);}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
//初始化View
}
//初始化数据
return convertView;
}
}
可以看出Adapter里面的接口主要是getCount()返回子View的数量,以及getView()返回我们填充好数据的View,ListView则通过这些接口来执行具体的布局、缓存等工作。下面我们来简单看看ListView的实现。
首先这些getCount()等接口都在一个接口类Adapter里
public interface Adapter {
//省略其他的接口
int getCount();
Object getItem(int position);
long getItemId(int position);
View getView(int position, View convertView, ViewGroup parent);
//省略其他的接口
}
中间加了一个过渡的接口ListAdapter
public interface ListAdapter extends Adapter {
//接口省略
}
我们在编写我们自己的Adapter时都会继承一个BaseAdapter,我们来看看BaseAdapter
public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
//BaseAdapter里面实现了ListAdapter的接口以及部分Adapter中的接口
//而像getCount()以及getView()这些接口则需要我们自己去实现
}
ListView的父类AbsListView中有ListAdapter接口,通过这个接口来调用getCount()等方法获取View的数量等
public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher,
ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener,
ViewTreeObserver.OnTouchModeChangeListener,
RemoteViewsAdapter.RemoteAdapterConnectionCallback {
/**
* The adapter containing the data to be displayed by this view
*/
ListAdapter mAdapter;
@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;
//通过getCount()获取View元素的个数
mItemCount = mAdapter.getCount();
}
}
}
从上面我们可以看出,AbsListView是一个抽象类,它里面封装了一些固定的逻辑,如Adapter模式的应用逻辑、布局的复用逻辑和布局子元素逻辑等。而具体的实现则是在子类ListView中。下面我们来看看ListView中是怎么处理每一个子元素View的。
@Override
protected void layoutChildren() {
//省略其他代码
case LAYOUT_FORCE_BOTTOM:
sel = fillUp(mItemCount - 1, childrenBottom);
adjustViewsUpOrDown();
break;
case LAYOUT_FORCE_TOP:
mFirstPosition = 0;
sel = fillFromTop(childrenTop);
adjustViewsUpOrDown();
break;
//省略其他代码
}
ListView中会覆写AbsListView中的layoutChildren()函数,在layoutChildren()中会根据不同的情况进行布局,比如从上到下或者是从下往上。下面我们看看具体的布局方法fillUp方法。
private View fillUp(int pos, int nextBottom) {
//省略其他代码
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;
}
这里我们看到fillUp方法里面又会通过makeAndAddView()方法来获取View,下面我们来看看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;
}
不知道大家看到这里想到了什么?
makeAndAddView()方法里面就出现了缓存机制了,这是提升ListView加载效率的关键方法。我们看到,在获取子View时会先从缓存里面找,也就是会从mRecycler中找,mRecycler是AbsListView中的一个用于缓存的RecycleBin类,来,我们看看缓存的实现
class RecycleBin {
private View[] mActiveViews = new View[0];
/**
* Get the view corresponding to the specified position. The view will be removed from
* mActiveViews if it is found.
*
* @param position The position to look up in mActiveViews
* @return The view if it is found, null otherwise
*/
View getActiveView(int position) {
int index = position - mFirstActivePosition;
final View[] activeViews = mActiveViews;
if (index >=0 && index < activeViews.length) {
final View match = activeViews[index];
activeViews[index] = null;
return match;
}
return null;
}
}
由上可见,缓存的View保存在一个View数组里面,然后我们来看看如果没有找到缓存的View,ListView是怎么获取子View的,也就是上面的obtainView()方法。需要注意的是obtainView()方法是在AbsListView里面。
View obtainView(int position, boolean[] outMetadata) {
//省略其他代码
final View scrapView = mRecycler.getScrapView(position);
final View child = mAdapter.getView(position, scrapView, this);
if (scrapView != null) {
if (child != scrapView) {
// Failed to re-bind the data, return scrap to the heap.
mRecycler.addScrapView(scrapView, position);
} else if (child.isTemporarilyDetached()) {
outMetadata[0] = true;
// Finish the temporary detach started in addScrapView().
child.dispatchFinishTemporaryDetach();
}
}
//省略其他代码
return child;
}
可以看到没有缓存的View直接就是从我们编写的Adapter的getView()方法里面获取。
以上我们简单看了ListView中适配器模式的应用,从中我们可以看出ListView通过引入Adapter适配器类把那些多变的布局和数据交给用户处理,然后通过适配器中的接口获取需要的数据来完成自己的功能,从而达到了很好的灵活性。这里面最重要的接口莫过于getView()接口了,该接口返回一个View对象,而千变万化的UI视图都是View的子类,通过这样一种处理就将子View的变化隔离了,保证了AbsListView类族的高度可定制化
这里的getView ()返回值就是Target,Adapter角色就是将多样的itemView同意输出问getView()返回值View
优点:
缺点: