Android源码设计模式学习笔记-适配器模式

适配器模式在我们开发中使用率极高,从代码中随处可见的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布局到特定的位置。

你可能感兴趣的:(Android源码设计模式学习笔记-适配器模式)