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 litTile;
public MyAdapter(Context context, List 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 implements TextWatcher,
ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener,
ViewTreeObserver.OnTouchModeChangeListener,
RemoteViewsAdapter.RemoteAdapterConnectionCallback { ... }
public abstract class AdapterView 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。
还是那句话,设计模式主要理解它的精髓。并不是一成不变。多思考是否应该使用这个设计模式才能事半功倍。