Android 设计模式之适配器模式

作者: AntDream | 来源:发表于2017-10-27 17:40 被阅读1698次

    在日常开发过程中时常需要用到设计模式,但是设计模式有23种,如何将这些设计模式了然于胸并且能在实际开发过程中应用得得心应手呢?和我一起跟着《Android源码设计模式解析与实战》一书边学边应用吧!

    设计模式系列文章

    今天我们要讲的是适配器模式(Adapter模式)


    定义

    适配器模式把一种接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作

    使用场景

    • 系统需要使用现有的类,而此类的接口不符合系统的需要,即接口不兼容
    • 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的一些类一起工作
    • 需要一个统一的输出接口,而输入端的接口不可预知

    使用例子

    • 最常见的ListView、GridView、RecyclerView等的Adapter

    实现

    3大角色

    • 目标角色,也就是所期待得到的接口。
    • 需要适配的接口
    • 适配器角色,是适配器模式的核心。适配器将源接口转换成目标接口

    实现的要点

    • 适配器模式分2种,类适配器模式和对象适配器模式。2种模式的区别在于实现适配的方法不同,类适配器模式通过继承需要适配的接口,而对象适配器模式则是通过组合的形式实现接口兼容的效果。因而对象适配器模式更加灵活也使用得更多,我们这里主要就介绍对象适配器模式
    • 对象适配器模式的实现关键在于直接将要被适配的对象传递到适配器类里面,并且适配器类实现目标的接口,从而在内部进行接口的转换

    实现方式

    我们给手机充电需要5V电压,而我们家里的电压都是220V的,下面我们通过日常生活中的电源电压适配的问题来简单应用下适配器模式。

    • 首先是5V电压接口,也就是我们适配器模式中的目标接口
    public interface FiveVolt {
    
        /**
         * 返回5V电压
         * @return 电压值
         */
        public int getVolt5();
    }
    
    • 然后是我们日常的220V电压,也就是适配器模式中需要被适配的接口
    public class Volt220 {
        public int getVolt220() {
            return 220;
        }
    }
    
    
    • 最后是我们的适配器类
    public class VoltAdapter implements FiveVolt {
    
        Volt220 mVolt220;
    
        public VoltAdapter(Volt220 volt220) {
            mVolt220 = volt220;
        }
    
        @Override
        public int getVolt5() {
            return 5;
        }
    
        public int getVolt220() {
            return mVolt220.getVolt220();
        }
    }
    
    • 通过以上的简单代码我们就实现了接口的适配,当然代码简化了很多,主要还是演示一下思路

    我们在使用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类族的高度可定制化。

    当然这里的Adapter并不是经典的适配器模式,却是对象适配器模式的优秀示例,有兴趣的小伙伴可以好好研究一下。需要注意的是文中的源码是Android7.1的,不同的版本可能稍有变化。

    总结

    • 适配器模式的经典实现在于把原本不兼容的接口融合在了一起,使之能更好的合作。但在实际开发中也可以有一些灵活的实现,比如ListView。
    • 当然过多的使用适配器会让系统显得过于凌乱。如果不是很有必要,可以不适用适配器而是直接对系统进行重构

                  欢迎关注我的微信公众号,期待与你一起学习,一起交流,一起成长!
    
    AntDream

    相关文章

      网友评论

        本文标题:Android 设计模式之适配器模式

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