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

仿写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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值