当前位置: 首页 > news >正文

适配器模式

一、前言

适配器模式就是将两个不兼容的类融合在一起。通过转换使他们可以兼容的工作。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();@Overridepublic 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();@Overridepublic 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();}@Overridepublic 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;@Overridepublic 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() {@Overridepublic void middleOutlet() {Log.d(TAG,"middleOutlet");}@Overridepublic 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;}@Overridepublic 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方法

@Overridepublic 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。
还是那句话,设计模式主要理解它的精髓。并不是一成不变。多思考是否应该使用这个设计模式才能事半功倍。

相关文章:

  • 《 C++ 点滴漫谈: 三十六 》lambda表达式
  • Kotlin中 StateFlow 或 SharedFlow 或 LiveData的区别
  • 算力经济模型推演:从中心化到去中心化算力市场的转变(区块链+智能合约的算力交易原型设计)
  • Level DB --- MergingIterator
  • 数据结构之二叉树(4)
  • 【AI大模型】SpringBoot整合Spring AI 核心组件使用详解
  • PHP数组排序深度解析:sort()、rsort()、asort()、arsort()、ksort()、krsort() 的适用场景与性能对比
  • C++负载均衡远程调用学习之负载均衡算法与实现
  • 从零开始学习RAG
  • 《算法导论(第4版)》阅读笔记:p7-p8
  • FISCO BCOS【初体验笔记】
  • 嵌入式学习笔记 - STM32 SRAM控制器FSMC
  • RocketMQ与Kafka的区别
  • Nginx正反向代理与正则表达式
  • 从OpenMP中的不兼容,窥探AI应用开发中的并行编程
  • GStreamer开发笔记(三):测试gstreamer/v4l2+sdl2/v4l2+QtOpengl打摄像头延迟和内存
  • 《深入理解 Java 虚拟机》笔记
  • 手表关于MPU6050中的功能实现
  • 架构思维:构建高并发读服务_基于流量回放实现读服务的自动化测试回归方案
  • Kubernetes控制平面组件:Controller Manager 之 NamespaceController 全方位讲解
  • 专家:家长要以身作则,孩子是模仿者学习者有时也是评判者
  • 中国人民银行等四部门联合召开科技金融工作交流推进会
  • 最高法、证监会:常态化开展证券纠纷代表人诉讼,降低投资者维权成本
  • 深圳中院回应“退休夫妻月入1.2万负债1.2亿”:其自述因经营不善负债
  • 香港根据《维护国家安全条例》订立附属法例
  • 重庆一高校75万采购市价299元产品?工作人员:正在处理