美文网首页Android精选Android技术知识Android开发
Android 适配器模式(ListView与Adapter)

Android 适配器模式(ListView与Adapter)

作者: Yink_Liu | 来源:发表于2019-01-25 17:27 被阅读6次

    Android 设计模式系列文章 Android 23种设计模式

    一、前言

    适配器模式就是将两个不兼容的类融合在一起。通过转换使他们可以兼容的工作。Android代码中最常见的适配器就是Adapter了。ListView、GridView、RecyclerView都使用Adapter,Adapter的作用都一样,把高度定制化的item view和ListView分开。item view通过一个Adapter和ListView联系到一起。解耦而不失高度可定制。

    二、适配器模式定义

    将一个类的接口转换成客户希望的另一个接口。适配器模式让那些接口不兼容的类可以一起工作

    三、例子

    我们先来看下适配器模式的例子。学习到底什么是适配器模式。

    3.1、我们举一个出水口出水量的例子。出水量有大有小,于是先定义两个接口

    public interface BigOutlet {
        public void bigOutlet();
    }
    
    public interface SmallOutlet {
        public void smallOutlet();
    }
    

    3.2、然后有一个出水口water tap。出水量大。

    public class BigWaterTap implements BigOutlet {
        private static final String TAG = WaterTap.class.getSimpleName();
    
        @Override
        public void bigOutlet() {
            Log.d(TAG,"bigOutlet");
        }
    }
    

    3.3、定义适配器

    现在需求来了,我要出水口既能大量出水,也可以小量出水。而我们不能去更改BigWaterTap,因为通常很多时候一个类拟定好了过后,我们无法再去修改了。也没有源码。给它再继承SmallOutlet这个接口。我们需要的是另外的办法来添加出水量小的方法。这个时候适配器模式就派上用场了。
    适配器模式写法有两种,这里先看第一种写法叫类适配器模式
    类适配器模式

    public class ClassWaterTapAdapter extends BigWaterTap implements SmallOutlet {
        private static final String TAG = ClassWaterTapAdapter.class.getSimpleName();
    
        @Override
        public void smallOutlet() {
            Log.d(TAG,"smallOutlet");
        }
    }
    

    调用

    ClassWaterTapAdapter classWaterTapAdapter = new ClassWaterTapAdapter();
    classWaterTapAdapter.bigOutlet();
    classWaterTapAdapter.smallOutlet();
    

    输出这里就省略了。我们可以看到适配器模式就是把两个不兼容的类结合到了一起,即可以出水量大,也可以出水量小了。达到了融合的作用。而不用去改变原来的类。然后看下另一种写法。对象适配器模式,其实就是代理模式的写法。
    对象适配器模式

    public class ProxyWaterTapAdapter implements SmallOutlet {
        private static final String TAG = ProxyWaterTapAdapter.class.getSimpleName();
        private BigWaterTap bigWaterTap;
    
        public ProxyWaterTapAdapter(BigWaterTap bigWaterTap) {
            this.bigWaterTap = bigWaterTap;
        }
    
        public void adapterBigOutlet() {
            bigWaterTap.bigOutlet();
        }
    
        @Override
        public void smallOutlet() {
            Log.d(TAG,"smallOutlet");
        }
    }
    

    调用

            ProxyWaterTapAdapter proxyWaterTapAdapter = new ProxyWaterTapAdapter(new BigWaterTap());
            proxyWaterTapAdapter.adapterBigOutlet();
            proxyWaterTapAdapter.smallOutlet();
    

    一目了然,就是用代理的方式,拥有BigWaterTap来调用了bigOutlet。ProxyWaterTapAdapter就达到了兼容的目的。

    4、小结

    1、现在我们队适配器模式有个清晰的认识了。适配器就是不改变原有类的基础上,让它兼容别的接口方法,以实现新的功能,达到兼容的目的。
    2、在我们开发过程中,笔者强烈建议最好还是使用对象适配器模式这种写法。对象适配器模式比类对象适配模式好处就是更加灵活,且不会暴露被适配者。因为继承了过后Adapter类中也有了一样的方法。

    四、ListView与适配器模式

    4.1、为了避免读者混淆,我先简单用上面的例子模拟一下Listview和适配器ListAdapter是如何工作的。

    先写适配器ListWaterTapAdapter,等价于ListAdapter

    public abstract class ListWaterTapAdapter implements SmallOutlet{
        public abstract void middleOutlet();
    }
    

    ListWaterTapAdapter抽象类,需要我们客户使用的时候具体去实现。然后重新写一下BigWaterTap,因为它本身有的功能就是bigOutlet()。等价于listview

    public class ListViewWaterTap implements BigOutlet{
        private static final String TAG = ListViewWaterTap.class.getSimpleName();
        private ListWaterTapAdapter listWaterTapAdapter;
    
        @Override
        public void bigOutlet() {
            Log.d(TAG,"bigOutlet");
        }
    
        public void setAdapter(ListWaterTapAdapter listWaterTapAdapter) {
            this.listWaterTapAdapter = listWaterTapAdapter;
        }
    
        public void smallToBigOutlet() {
            listWaterTapAdapter.smallOutlet();
            int i = 100;
            while (i-- > 0);
            listWaterTapAdapter.middleOutlet();
        }
    }
    

    这里的ListViewWaterTap并非适配器。我们的适配器就是ListWaterTapAdapter。setAdapter来拥有适配器抽象类,调用抽象方法,让客户自己去实现smallOutlet和middleOutlet这两个方法。接着我们看下调用

            ListViewWaterTap listViewWaterTap = new ListViewWaterTap();
            ListWaterTapAdapter listWaterTapAdapter = new ListWaterTapAdapter() {
                @Override
                public void middleOutlet() {
                    Log.d(TAG,"middleOutlet");
                }
    
                @Override
                public void smallOutlet() {
                    Log.d(TAG,"smallOutlet");
                }
            };
            listViewWaterTap.setAdapter(listWaterTapAdapter);
    
            listViewWaterTap.bigOutlet();
            listViewWaterTap.smallToBigOutlet();
    

    这里就是和listview与adapter一样的用法了。并不是标准的适配器模式写法。但确实最经典的适配器模式应用。下面我们来分析一下listview和adapter的关系。

    4.2、ListView和ListAdapter

    首先说下ListView用适配器模式的目的就是让listview的每个item可以客户自己高度定制化。你自己去实现就行了。无论你如何定制item使用就是一个view。listview用适配器模式就完美的达到了这个效果。
    以下有射猎源码的地方,来自Android P。"..."代表省略代码

    4.2.1、首先来一个普通示例

        listView = (ListView) findViewById(R.id.listView);
        MyAdapter myAdapter = new MyAdapter(this,mListTile);
    
    public class MyAdapter extends BaseAdapter {
        private LayoutInflater layoutInflater;
        List<String> litTile;
    
        public MyAdapter(Context context, List<String> listTile) {
            layoutInflater = LayoutInflater.from(context);
            this.litTile = listTile;
        }
    
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder;
            if (convertView == null) {
                holder = new ViewHolder();
                convertView = layoutInflater.inflate(R.layout.layout_item,null);
                holder.title = (TextView) convertView.findViewById(R.id.title);
                convertView.setTag(holder);
            } else {
                holder = (ViewHolder)convertView.getTag();
            }
            holder.title.setText(litTile.get(position));
            return convertView;
        }
    
        class ViewHolder{
            public TextView title;
        }
    // 篇幅原因,省略getCount、getItem和getItemId
    

    这是我们最普通的运用listview的写法了。然后我们从适配器讲起,先看adapter是个啥
    listview.java的setAdapter方法

    @Override
        public void setAdapter(ListAdapter adapter) {
        ...
    }
    

    看看BaseAdapter的集成关系,然后开始讲适配器BaseAdapter

    public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter { ... }
    
    public interface ListAdapter extends Adapter { ... }
    
    public interface Adapter { ... }
    

    4.2.2、适配器

    为了节约篇幅我把注释删了。看Adapater代码如下:

    public interface Adapter {
        void registerDataSetObserver(DataSetObserver observer);
        void unregisterDataSetObserver(DataSetObserver observer);
        int getCount();   
        Object getItem(int position);
        long getItemId(int position);
        boolean hasStableIds();
        View getView(int position, View convertView, ViewGroup parent);
        static final int IGNORE_ITEM_VIEW_TYPE = AdapterView.ITEM_VIEW_TYPE_IGNORE;
        int getItemViewType(int position);
        int getViewTypeCount();
        static final int NO_SELECTION = Integer.MIN_VALUE;
        boolean isEmpty();
        default @Nullable CharSequence[] getAutofillOptions() {
            return null;
        }
    }
    

    1>根据上面的继承关系,BaseAdapter跟开始举例一样,抽象类来定义适配器,要实现的接口是Adapter
    2> 可以看到Adapter就是接口,接口的这些方法是要提供给ListView内部使用的。我们自己实现Adapter,完成这些接口,或者抽象方法。

    4.2.3、listview如何使用adapter

    首先看下listView的继承关系

    public class ListView extends AbsListView { ... }
    
    public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher,
            ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener,
            ViewTreeObserver.OnTouchModeChangeListener,
            RemoteViewsAdapter.RemoteAdapterConnectionCallback { ... }
    
    public abstract class AdapterView<T extends Adapter> extends ViewGroup { ... }
    

    ListView就是一个ViewGroup,然后通过主要的逻辑实现代码就在ListView.java和AbsListView.java了

    先讲一个getCount
    在AbsListView.java的onAttachedToWindow方法

        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;
                mItemCount = mAdapter.getCount();
            }
        }
    

    把view关联到window的时候,mAdapter.getCount(),这个getConut是我们继承时写的,就确定了我们这个listView有多少个item了。

    再梳理一下getView是如何把item view加载出来的
    大致流程如下,这里就只列出简化的代码了,本文主要理解适配器模式的思想
    1>AbsListView:onlayout

     protected void onLayout(boolean changed, int l, int t, int r, int b) {
            super.onLayout(changed, l, t, r, b);
            ...
            layoutChildren();
            ....
        }
    

    ViewGroup是组合模式,它在调用onlayout的时候调用layoutChildren来布局子控件,layoutChildren在AbsListView是一个空实现,实现代码在ListView
    2>ListView:layoutChildren

    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;
    

    调用到layoutChildren后来布局item view.
    3>ListView:fillDown

    private View fillDown(int pos, int nextTop) {
      ...
       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++;
            }
    

    每个子view都是调用makeAndAddView然后调用AbsListView的obtainView方法

    private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
                boolean selected) {
        ...
         final View child = obtainView(position, mIsScrap);
    

    4>AbsListView:obtainView

     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();
                }
            }
        ...
    }
    

    这里用到了mAdapter.getView。从ListView布局每个item view的过程来看,最后布局使用view的时候就用到了我们去实现的getView方法返回的view。

    我们对listview优化的时候,为啥写法是判断if (convertView == null)
    接着分析上面第四步的代码
    mRecycler.getScrapView是获得可复用的view,然后带入mAdapter.getView(position, scrapView, this);
    如果还没有被加入到缓存list则

    if (child != scrapView) {
        // Failed to re-bind the data, return scrap to the heap.
        mRecycler.addScrapView(scrapView, position);
    } 
    

    所以我们写代码的时候则这样来优化判断,当然获得缓存后数据也是原来的。所以我们要重新设置title

         if (convertView == null) {
                holder = new ViewHolder();
                convertView = layoutInflater.inflate(R.layout.layout_item,null);
                holder.title = (TextView) convertView.findViewById(R.id.title);
                convertView.setTag(holder);
            } else {
                holder = (ViewHolder)convertView.getTag();
            }
            holder.title.setText(litTile.get(position));
    

    这里ListView和ListAdapter的关系就梳理到这里了。有兴趣的同学还可以去看看GridView,RecyleView喔。比如RecycleView就是ListView的一个升级版,RecycleView定义了ViewHolder的机制。更加巧妙。

    五、总结

    适配器模式就是将原本不兼容的接口融合在一起,以便更好的协同合作。当然设计模式不是一成不变的,litview的adapter就是很好的一个变化,让UI更加高度可定制化而不失自身实现。
    优点:
    1、把接口和类结合,通过适配器可以让接口定义的功能更好的复用。
    2、扩展性好,不光可调用自己开发的功能,还自然的扩展了接口定义的其它功能。
    缺点:
    不易滥用,如果你的代码有n多个适配器,你想想那场面,调用十分凌乱,还不如直接修改源码设计更好的public api。
    还是那句话,设计模式主要理解它的精髓。并不是一成不变。多思考是否应该使用这个设计模式才能事半功倍。

    相关文章

      网友评论

        本文标题:Android 适配器模式(ListView与Adapter)

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