仿写BaseAdapter而想到的观察者模式-----思考
来源:互联网 发布:淘宝号码购买 编辑:程序博客网 时间:2024/05/17 09:48
仿写BaseAdapter而想到的观察者模式以及思考
最近在读一篇博客的时候,博客地址(强烈建议先阅读该篇博客再看此博客)看到这个哥们写的代码不太像我们平时用的adapter,我想,既然它都定义成adapter了,为什么不按照规范来,按照我们平时的用法,adapter.setData();adapter.notifyDataSetChanged(),就可以直接来显示内容了,这哥们写的不是这样的,看他的代码:
然后在mainActivity里面直接调用setAdapter方法就能显示内容了,这样虽然简单,但是扩展性并不是很好,没有暴露出一个方法来更新显示的内容,需要每次去调用setAdapter,而不是调用notifyDataSetChanged方法。因此,我们来改造它一番。
adapter的改造
- 定义一个adapter基类
public abstract class TabsAdapter{ public abstract View getView(int position); public abstract int getCount(); public abstract Object getItem(int position); public abstract void notifyDataSetChanged();}
可以看到,里面的抽象方法都是我们平时在使用过程中经常见到的,这个很简单,不过多解释。
写一个具体类继承我们的抽象类来实现具体的逻辑
public class myTabsAdapter extends TabsAdapter{ private List<String> mLists = null; private LayoutInflater mLayoutInflater = null; private WindowManager manager; private DisplayMetrics metrics = new DisplayMetrics(); public myTabsAdapter(Context context) { mLayoutInflater = LayoutInflater.from(context); manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); } public void setData(ArrayList<String> list) { mLists = list; notifyDataSetChanged(); } @Override public View getView(int position) { View v = mLayoutInflater.inflate(R.layout.tab_item_view, null); TextView tv = (TextView) v.findViewById(R.id.text); manager.getDefaultDisplay().getMetrics(metrics); tv.setWidth(metrics.widthPixels / 3); tv.setText(mLists.get(position)); return v; } @Override public int getCount() { return mLists == null ? 0 : mLists.size(); } @Override public Object getItem(int position) { return mLists == null ? "" : mLists.get(position); }
不要吐槽getview方法没有复用view,我们现在是仿写adapter而不是listview,这个我现在还没研究~~
接下来就没有什么难度了,继承HorizontalScrollView写我们自己的滚动控件,不多废话,直接上代码
public class ScrollTabViewPager extends HorizontalScrollView implements ViewPager.OnPageChangeListener{ private Context mContext = null; private ViewPager mViewPager = null; private TabsAdapter mTabAdapter = null; private LinearLayout mContainer = null; public ScrollTabViewPager(Context context) { this(context, null); } public ScrollTabViewPager(Context context, AttributeSet attrs) { this(context, attrs, 0); } public ScrollTabViewPager(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mContext = context; mContainer = new LinearLayout(mContext); mContainer.setOrientation(LinearLayout.HORIZONTAL); mContainer.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); } public void setTabAdapter(TabsAdapter adapter) { mTabAdapter = adapter; } private void initTabs() { removeAllViews(); mContainer.removeAllViews(); for (int i = 0; i < mTabAdapter.getCount(); i++) { mContainer.addView(mTabAdapter.getView(i)); } addView(mContainer); } public void setViewPager(ViewPager v) { mViewPager = v; mViewPager.addOnPageChangeListener(this); } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { } @Override public void onPageSelected(int position) { } @Override public void onPageScrollStateChanged(int state) { }}
接下来不同点来了,我们并不想在setAdapter里面去调用initTab()方法,这样不符合我们的初衷,但是不调用的话我们又无法将我们要显示内容显示在view上,(因为没有调用addView方法)。那么接下来我们开始研究解决方法。
1.我们知道调用notifyDataSetChanged()后会重走getView方法,于是,想法1 代码如下:
public abstract class TabsAdapter { public abstract View getView(int position); public abstract int getCount(); public abstract Object getItem(int position); public void notifyDataSetChanged() { for (int i = 0 ;i<getCount();i++) { getView(i); } }
这样,我们在调用notifyDataSetChanged()后就会去刷新页面,但是这个仅仅只是将页面进行了刷新,还是没有将view显示在我们自定义的控件上面,因为HorizontalScrollView只能有一个子view,所以我们必须定义一个viewGroup去承载我们调用getView后返回的子view,否则会报错。
因此,我们在adapter中定义一个viewGroup,代码如下所示:
重点看标红部分,很简单,就是一个linearLayout
然后,我们在抽象类 TabsAdapter中去定义一个getViewGroup方法,别问为什么不再实现类中去定义这个方法,因为我们在setAdapter方法中传的参数是抽象类,抽象类没有办法访问到实现类的具体方法,这样,我们就可以在具体实现类中重写
@Override public ViewGroup getViewGroup(){ return mContainer; }
将我们的viewGroup返回。在自定义的view中写一个updateView方法:
public void updateViews() { removeAllViews(); addView(mTabAdapter.getViewGroup()); }
然后我们在主activity中直接去调用updateView就可以了,代码如下:
实际上,到现在我们的代码已经完成了,要做的工作已经ok,但是,代码质量很差,表现在
1.我们在adapter中创建了view,我们的adapter按照定义来说是view和data沟通的桥梁,我们不应该在adapter中去创建一具体的view。
2. 我们虽然用notifyDataSetChanged方法改变了view中的数据,但是我们需要额外去调用updateView方法去刷新数据,距离我们的初衷还是很远。
3. 我们的ScrollTabViewPager 无法对ViewGroup进行操作,这样,每次进行notifyDataSetChanged()方法后,数据会出现重复。
4. 总结就是,代码很渣~
现在,我们一条一条来进行解决:
问题1 :既然不能定义在adapter里面,那么我们将它定义在外面,我们知道ScrollTabViewPager是一个view,定义在这里简直不能再好~废话不多说,看代码
public class ScrollTabViewPager extends HorizontalScrollView implements ViewPager.OnPageChangeListener{ private Context mContext = null; private ViewPager mViewPager = null; private TabsAdapter mTabAdapter = null; //定义一个viewGroup private LinearLayout mContainer = null; public ScrollTabViewPager(Context context) { this(context, null); } public ScrollTabViewPager(Context context, AttributeSet attrs) { this(context, attrs, 0); } public ScrollTabViewPager(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mContext = context; mContainer = new LinearLayout(mContext); mContainer.setOrientation(LinearLayout.HORIZONTAL); mContainer.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); } public void setTabAdapter(TabsAdapter adapter) { mTabAdapter = adapter; } public void setViewPager(ViewPager v) { mViewPager = v; mViewPager.addOnPageChangeListener(this); } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { } @Override public void onPageSelected(int position) { } @Override public void onPageScrollStateChanged(int state) { }} public void updateView(){ removeAllViews(); mContainer.removeAllViews(); for (int i = 0; i < mTabAdapter.getCount(); i++) { mContainer.addView(mTabAdapter.getView(i)); } addView(mContainer); }
有人看到这里可能会注意到很坑爹的一点,就是这个代码特么的不是和一开始一样吗,就是把initTabs改成了updateView方法,并对外部可见~~直呼坑爹,好吧,我也发现了。但是先不管它,继续往下走
第二条问题 :我们重点解决。
第三条问题,由于之前ScrollTabViewPager 无法对viewGroup进行操作,现在可以了,因为viewGroup是定义在ScrollTabViewPager 中的,我们想对view怎么操作都可以,也不会觉得怪,第三条问题解决。
重点解决第二条问题,解决之前,我们来回顾一下原先改造的代码
首先是抽象Adapter类:
public abstract class TabsAdapter { public abstract View getView(int position); public abstract int getCount(); public abstract Object getItem(int position); public void notifyDataSetChanged() { for (int i = 0 ;i<getCount();i++) { getView(i); } }
我们在notifyDataSetChanged方法中增加了getview方法,以让他刷新数据和页面。但是这个方法很有局限性并且不符合我们的编码规范,对内修改封闭,对外修改开放原则,假设我们在notifyDataSetChanged方法要调用getItem()方法,那么我们就需要在notifyDataSetChanged方法后继续增加getItem()方法,我们对基类进行了修改,这是应该严格避免的。
接下来是我们在ScrollTabViewPager 定义的updateView方法
public void updateView(){ removeAllViews(); mContainer.removeAllViews(); for (int i = 0; i < mTabAdapter.getCount(); i++) { mContainer.addView(mTabAdapter.getView(i)); } addView(mContainer); }
走来走去,我们又回到了原来的地方,只不过是将方法名改了一遍,并对外可见。我们的初衷是调用notifyDataSetChanged后自动更新view,你加个这个方法是什么鬼?我们在自己使用listview的时候什么时候用updateView方法了?
所以,我们任重而道远。
既然知道了问题,那么我们就得想办法解决:在数据更改后必须通知到相应的view来更新,我们adapter只管数据,notifyDataSetChanged只管通知。可能大家已经想到了,就是观察者模式。
观察者模式定义(如果你不想看前面的废话,直接看这里就行了)
观察者模式(有时又被称为发布(publish )-订阅(Subscribe)模式、模型-视图(View)模式、源-收听者(Listener)模式或从属者模式)是软件设计模式的一种。在此种模式中,一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实现事件处理系统。
讲的很抽象,简单来说,就是两个对象,一个观察者,一个被观察者,当被观察者发生数据改变时候,我们就通知观察者进行更新操作。
这样,关系就很明确了,adapter是被观察者,我们的ScrollTabViewPager 是观察者。
首先,我们定义一个被观察者接口
//抽象被观察者public interface IObsevered { void addObservers(IObserver observer); void deleteObservers(IObserver observer);}
我们的被观察者没什么特别的用途,只需要添加观察者和移除观察者即可。
接下来定义观察者接口
//抽象观察者public interface IObserver { void update();}
观察者也没有什么特别的地方,只需要负责更新自己即可。
不知道大家看到这里有什么疑问没有,为什么总是定义接口,我以前在看各种模式的时候经常会有博客写各种接口,各种继承关系,我很不明白,后来看到了软件设计的一个重要原则——-依赖倒置,如果你不是很懂依赖倒置原则,建议上网学习一下,也可以看这篇博客
因为我们的观察者和被观察者不是只有一个,可能有多个的,所以我们设计代码的时候不能基于实现而应该基于抽象~这样扩展性会非常好。
接下来开始改造代码:
让我们的抽象类adapter继承自被观察者接口
public abstract class TabsAdapter implements IObsevered { private ArrayList<IObserver> list = new ArrayList<>(); public abstract View getView(int position); public abstract int getCount(); public abstract Object getItem(int position); public void notifyDataSetChanged(){ for (IObserver observer : list) { observer.update(); } } @Override public void addObservers(IObserver observer) { list.add(observer); } @Override public void deleteObservers(IObserver observer) { list.remove(observer); }}
重写我们的addObservers(IObserver observer) 和deleteObservers(IObserver observer)方法,在notifyDataSetChanged()方法中通知观察者进行更新,至于更新什么,我们不管,由具体实现类进行判定。
具体实现类adapter代码就一下子和我们平时写的一样简洁了
public class myTabsAdapter extends TabsAdapter{ private List<String> mLists = null; private LayoutInflater mLayoutInflater = null; private WindowManager manager; private DisplayMetrics metrics = new DisplayMetrics(); public myTabsAdapter(Context context) { mLayoutInflater = LayoutInflater.from(context); manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); } public void setData(ArrayList<String> list) { mLists = list; notifyDataSetChanged(); } @Override public View getView(int position) { View v = mLayoutInflater.inflate(R.layout.tab_item_view, null); TextView tv = (TextView) v.findViewById(R.id.text); manager.getDefaultDisplay().getMetrics(metrics); tv.setWidth(metrics.widthPixels / 3); tv.setText(mLists.get(position)); return v; } @Override public int getCount() { return mLists == null ? 0 : mLists.size(); } @Override public Object getItem(int position) { return mLists == null ? "" : mLists.get(position); }}
不做任何关于上层view的处理,只用数据来渲染一下我们的view即可,对于view的操作,我们放在我们的自定义view中。将我们的自定义view实现观察者接口
public class ScrollTabViewPager extends HorizontalScrollView implements ViewPager.OnPageChangeListener, IObserver { private Context mContext = null; private ViewPager mViewPager = null; private TabsAdapter mTabAdapter = null; private LinearLayout mContainer = null; public ScrollTabViewPager(Context context) { this(context, null); } public ScrollTabViewPager(Context context, AttributeSet attrs) { this(context, attrs, 0); } public ScrollTabViewPager(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mContext = context; mContainer = new LinearLayout(mContext); mContainer.setOrientation(LinearLayout.HORIZONTAL); mContainer.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); } public void setTabAdapter(TabsAdapter adapter) { mTabAdapter = adapter; mTabAdapter.addObservers(this); } public void setViewPager(ViewPager v) { mViewPager = v; mViewPager.addOnPageChangeListener(this); } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { } @Override public void onPageSelected(int position) { } @Override public void onPageScrollStateChanged(int state) { } @Override public void update() { removeAllViews(); mContainer.removeAllViews(); for (int i = 0; i < mTabAdapter.getCount(); i++) { mContainer.addView(mTabAdapter.getView(i));// //假设我们想更新一下getItem()方法// mTabAdapter.getItem(i); } addView(mContainer); }}
接下来就是我们的主Activity了,代码像我们平时写的一样简单明了:
public class MainActivity extends Activity { private ScrollTabViewPager mPager = null; private ViewPager mViewPager = null; private myTabsAdapter adapter = null; private ArrayList<String> mLists = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); initData(); } private void initView() { mPager = (ScrollTabViewPager) findViewById(R.id.tabs); mViewPager = (ViewPager) findViewById(R.id.viewPager); mPager.setViewPager(mViewPager); adapter = new myTabsAdapter(this); mPager.setTabAdapter(adapter); } private void initData() { mLists = new ArrayList<>(); for (int i = 0; i < 4; i++) { mLists.add("tab" + i); } adapter.setData(mLists); adapter.notifyDataSetChanged(); }}
我们仅仅调用notifyDataSetChanged()方法就可以实现view的更新和数据的更新操作,完成了最终的目标。
效果如下:
写在最后
在安卓源代码中有着大量的设计模式和设计原则,如果你对这些模式和原则都不是很明白的话会看的云里雾里,看完这篇博客你可以看看listview的setAdapter方法,在源码中也应用到了观察者模式,如下图所示:
标红部分就是观察者模式的应用~我们不管它是不是为了实现我们需要的功能,因为我也不是看得很懂源码,太抽象,我们就暂且认为她和我们是一样的。
额,写不下去了,就这样吧,这两天学到了很多
强烈建议:
理解一下依赖倒置原则,接口回调,这样在看很多设计模式的时候就会清晰很多
- 仿写BaseAdapter而想到的观察者模式-----思考
- 观察者模式的思考
- 一道面试题想到的设计模式(观察者模式)
- ListView BaseAdapter中的观察者模式
- Android ListView与BaseAdapter的观察者模式实现
- 观察者模式的解读与思考
- (转)ListView BaseAdapter中的观察者模式
- 在BaseAdapter源码中了解观察者模式
- 设计模式思考----观察者模式
- 由地震而想到的
- 用js写的简单观察者模式
- 写程序想到的
- 写综述想到的
- 写个观察者模式
- 观察者模式学习和思考
- 由从webgrid开发而想到的
- 由失恋而想到的......---飘题万里
- 由凡客而想到的
- 15电气郄慧敏最小的数排在最后
- R语言学习笔记 - 创建数据集
- 减少2D碰撞体的顶点数量——PolygonCollider2D
- 【FastDFS专题】fastdfs使用实战(概念篇)
- phalcon 之 flash组件
- 仿写BaseAdapter而想到的观察者模式-----思考
- iOS每日一记之——————————契丹的cocopods
- 如何用U盘安装CentOS7系统?U盘安装Centos
- IOS中 Block简介与用法
- 另一种做法(15个数中最小的数在最后)
- linux用户进程分析
- lucene4.X教程一:系统配置
- iOS 调用系统通讯录获取姓名电话号码
- 动态数组排列