仿写BaseAdapter而想到的观察者模式-----思考

来源:互联网 发布:淘宝号码购买 编辑:程序博客网 时间:2024/05/17 09:48

仿写BaseAdapter而想到的观察者模式以及思考

最近在读一篇博客的时候,博客地址(强烈建议先阅读该篇博客再看此博客)看到这个哥们写的代码不太像我们平时用的adapter,我想,既然它都定义成adapter了,为什么不按照规范来,按照我们平时的用法,adapter.setData();adapter.notifyDataSetChanged(),就可以直接来显示内容了,这哥们写的不是这样的,看他的代码:

重点看标红部分
然后在mainActivity里面直接调用setAdapter方法就能显示内容了,这样虽然简单,但是扩展性并不是很好,没有暴露出一个方法来更新显示的内容,需要每次去调用setAdapter,而不是调用notifyDataSetChanged方法。因此,我们来改造它一番。

adapter的改造

  1. 定义一个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方法,在源码中也应用到了观察者模式,如下图所示:

这里写图片描述
标红部分就是观察者模式的应用~我们不管它是不是为了实现我们需要的功能,因为我也不是看得很懂源码,太抽象,我们就暂且认为她和我们是一样的。

额,写不下去了,就这样吧,这两天学到了很多
强烈建议:
理解一下依赖倒置原则,接口回调,这样在看很多设计模式的时候就会清晰很多

1 0
原创粉丝点击