适配器模式在我们开发中使用率极高,从代码中随处可见的Adapter可以判断出来。从最早的ListView,GridView到现在最新的RecyclerView都需要使用Adapter, 并且在开发过程中遇到的优化问题,出错概率较大的地方也基本都出自Adapter, 这也是一个让人又爱又恨的角色.
说到底,适配器是将两个不兼容的类融合在一起,它有点像粘合剂,将不同的东西通过一种转换使得它们能够协作起来。
这个模式的UML类图如下.
image.png
适配器模式应用的简单示例
用电影接口做栗子,笔记本电脑的电源一般在5V电压,但是在我们生活中的电线电压一般是220V。这个时候出现了不匹配的状况,在软件开发中称为接口不兼容,此时就需要适配器来进行一个接口转换. 我们可以加一个Adapter层来进行接口转换.
类适配器模式
在上述电源接口这个示例中,5V电压就是Target接口,220V电压就是Adaptee类,而将电压从220v转换到5v就是Adapter.
具体程序如下所示.
public interface FiveVolt {
int getVolt5();
}
public class Volt220 {
public int getVolt220(){
return 220;
}
}
public class VoltAdapter extends Volt220 implements FiveVolt {
@Override
public int getVolt5() {
return 5;
}
}
public class Test {
public static void main(String[] args){
VoltAdapter adapter = new VoltAdapter();
System.out.println("输出电压 : "+adapter.getVolt5());
}
}
对象适配器模式
public interface FiveVolt {
int getVolt5();
}
public class Volt220 {
public int getVolt220(){
return 220;
}
}
public class VoltAdapter implements FiveVolt{
Volt220 mVolt220;
public VoltAdapter(Volt220 mVolt220) {
this.mVolt220 = mVolt220;
}
public int getVolt220(){
return mVolt220.getVolt220();
}
@Override
public int getVolt5() {
return 5;
}
}
public class Test {
public static void main(String[] args){
VoltAdapter adapter = new VoltAdapter(new Volt220());
System.out.println("输出电压 : "+adapter.getVolt5());
}
}
这种实现方式直接将要适配的对象传递到Adapter中,使用组合的形式实现接口兼容的效果。这比类适配器方式更为灵活。
Android源码中的适配器模式-ListView
我们知道ListView作为最重要的控件,它需要显示各式各样的视图,每个人需要显示的效果不相同,显示的数据类型,数量等也千变万化,那么如何应对这种变化成为架构师要考虑的最重要特性之一.
Android的做法是增加一个Adapter层来隔离变化,将ListView需要的关于Item View接口抽象到Adapter对象中,并且在ListView内部调Adapter这些接口完成布局等操作。这样用户只要实现了Adapter的接口,并且将Adapter设置给ListView, ListView就可以按照用户设定的UI效果,数量,数据显示每一项数据.
image.png
我们发现在ListView中并没有Adapter相关的成员变量,其实Adapter在ListView的父类AbsListView中.
public class ListView extends AbsListView {
ListAdapter mAdapter;
}
//关联到Window时调用,获取调用Adapter中的getCount方法等
@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的数量,调用的是mAdapter的getCount方法
mItemCount = mAdapter.getCount();
}
}
//子类需要复写layoutChildren()函数来布局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;
final int childCount = getChildCount();
if (changed) {
for (int i = 0; i < childCount; i++) {
getChildAt(i).forceLayout();
}
mRecycler.markChildrenDirty();
}
//布局Child View
layoutChildren();
mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;
// TODO: Move somewhere sane. This doesn't belong in onLayout().
if (mFastScroll != null) {
mFastScroll.onItemCountChanged(getChildCount(), mItemCount);
}
mInLayout = false;
}
AbsListView定义了集合视图的逻辑框架,比如Adapter模式的应用,复用Item View的逻辑,布局子视图的逻辑等,子类只需要复写特定的方法即可实现集合视图的功能。首先在AbsListView类型的View中添加窗口时会调用getCount函数获取元素的个数,然后在onLayout函数中调用layoutChilden函数对所有子元素进行布局。layoutChilden实际是在ListView中实现.
@Override
protected void layoutChildren() {
//代码省略
switch (mLayoutMode) {
//代码省略
case LAYOUT_FORCE_BOTTOM:
sel = fillUp(mItemCount - 1, childrenBottom);
adjustViewsUpOrDown();
break;
case LAYOUT_FORCE_TOP:
mFirstPosition = 0;
sel = fillFromTop(childrenTop);
adjustViewsUpOrDown();
break;
default:
//代码省略
break;
}
}
ListView复写了AbsListView中的layoutChilden函数,在该函数中根据布局模式来布局Item View, 例如,默认情况是从上到下开始布局,也有可能从下到上开始布局.
//从下到上填充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++;
}
setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
return selectedView;
}
//从下到上填充
private View fillUp(int pos, int nextBottom) {
View selectedView = null;
int end = 0;
if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
end = mListPadding.top;
}
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;
}
在每一种布局方式中都会从makeAndAddView函数获取一个View, 这个View就是ListView的每一项的视图,这里有一个pos函数,也就是对应这个View是ListView中的第几项. 我们来看看makeAndAddView中的实现
private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
boolean selected) {
//代码省略
//获取一个item View
final View child = obtainView(position, mIsScrap);
//将item View设置到对应的地方
setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
return child;
}
makeAndAddView主要分两个步骤,第一是根据position获取一个item view, 然后将这个view布局到特定的位置。
网友评论