美文网首页
Android源码设计模式学习笔记-适配器模式

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

作者: e小e | 来源:发表于2018-02-22 09:55 被阅读13次

    适配器模式在我们开发中使用率极高,从代码中随处可见的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源码设计模式学习笔记-适配器模式

          本文链接:https://www.haomeiwen.com/subject/pihbtftx.html