Adapter数据变化改变现有View的实现原理及案例

来源:互联网 发布:python股票涨速 编辑:程序博客网 时间:2024/06/05 15:21

首先说说Adapter具体的类的继承关系,如下图

     Adapte为接口它的实现类的对象作为AdapterView和View的桥梁,Adapter是装载了View(比如ListView和girdView要显示的数据)。相关View要显示的数据完全与View解耦。View要显示的数据从Adapter里面获取并展现出来;Adapter负责把真实的数据是配成一个个View(每一条数据对应一个View)让GirdView等类似的组件来显示这些是配好的一个个View,。也就是说View要显示什么数据取决于AdapterView的变化(比如ListView删除一个或者增加一个Item)也取决于Adapter里面的数据的变化。这也就说明当Adapter的里面的数据如果发生变化的时候相应的View(如ListView)也得发生对应的变化。Adapter里面的数据发生变化的时候必须通知View也发生变化,继而重绘View从而展示数据变化过的View.这是典型的观察者模式的应用。

Adapter是被观察的主题,被观察者监视,如果Adapter这个Subject状态发生了变化,主题会告知观察者,观察者通过回调一个函数得到通知,通知关联对象做出相应的更新。

既然是被观察者,用面向对象的思维来说,该主题必须持有观察者的对象用来让观察者观察,所以主题在观察者模式中也具备了注册和销毁观察者对象的责任

 由此可以推出Adapter有以下两个主要的职责:

  1) 把源数据适配成一个个View

  2) 当数据发生变化的时候发送通知(向观察者发送通知,然后由观察者做出相应的响应,观察者模式),让相关组件(GirdView)做出在页面展现上的修改。

 

Adapter的部分代码如下:

public interface Adapter {    /**     * 注册观察者observer,当Adapter里面的数据发生变化时通知 *该观察者,观察者调用onChanged()方法来做出相应的响应     */    void registerDataSetObserver(DataSetObserver observer);    /**     * 取消已经注册过的观察者observer对象     */    void unregisterDataSetObserver(DataSetObserver observer);/** *把adapter里面的数据适配一个个View,每一条数据对应了一个View用来对该条数据做展现 最终让GirdView等相关组件来显示。具体会产生多少个View由getCount()方法来决定     */    View getView(int position, View convertView, ViewGroup parent); }

可以看到adapter这个父接口定义了注册观察者的方法,下面就看看观察者的都做了哪些事情:这里的观察者是DataSetObserver抽象类的扩展类。该抽象类提供了两个方法:

public abstract class DataSetObserver {//数据源发生变化的时候调用 public void onChanged() {        // Do nothing    }    public void onInvalidated() {        // Do nothing    }}

那么,这个观察者是怎么和Adapter进行关联的呢?其实观察者对象是放在一个ArrayList集合里面去的。该集合封装在Observable<T>这个抽象类。看看这个类的源码就可以明白:

public abstract class Observable<T> {//保存观察者对象的集合 protected final ArrayList<T> mObservers = new ArrayList<T>();//注册观察者 public void registerObserver(T observer) {        if (observer == null) {            throw new IllegalArgumentException("The observer is null.");        }        synchronized(mObservers) {            if (mObservers.contains(observer)) {                throw new IllegalStateException("Observer " + observer + " is already registered.");            }            mObservers.add(observer);        }    }//删除已经注册的观察者    public void unregisterObserver(T observer) {        if (observer == null) {            throw new IllegalArgumentException("The observer is null.");        }        synchronized(mObservers) {            int index = mObservers.indexOf(observer);            if (index == -1) {                throw new IllegalStateException("Observer " + observer + " was not registered.");            }            mObservers.remove(index);        }    }//清空所有观察者 public void unregisterAll() {        synchronized(mObservers) {            mObservers.clear();        }    }}
通过源码可以知道Observervable<T>的主要职责是添加观察者以及删除已经添加过的观察者!该抽象类还有一个子类DataSetObservable:该类在继承父类功能的基础上又提供了两个方法,这两个方法的作用就是向一系列观察者发送通知,以便让该类包含的所有观察者执行onChanged()或者onInvalidated()来执行特定的行为。源码如下:

public class DataSetObservable extends Observable<DataSetObserver> {    //当数据源发生变化的时候调用此方法来让View进行响应     public void notifyChanged() {        synchronized(mObservers) {                    for (int i = mObservers.size() - 1; i >= 0; i--) {                mObservers.get(i).onChanged();            }        }    }    public void notifyInvalidated() {        synchronized (mObservers) {            for (int i = mObservers.size() - 1; i >= 0; i--) {                mObservers.get(i).onInvalidated();            }        }    }}

通过上面的说明我们知道观察者对象的工作是由DataSetObservable来间接发出告知并执行观察者自己的onChange方法的。读到这可以发现,现在观察者还是没有和相应Adapter进行关联以及在数据发生变化的时候Adapter是怎么发送通知的,下面就以BaseAdapter进行说明,其部分源码如下:


public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {//该对象用来注册观察者 private final DataSetObservable mDataSetObservable = new DataSetObservable();    public boolean hasStableIds() {        return false;    }    //注册观察者    public void registerDataSetObserver(DataSetObserver observer) {        mDataSetObservable.registerObserver(observer);    }    //删除已经注册过的观察者    public void unregisterDataSetObserver(DataSetObserver observer) {        mDataSetObservable.unregisterObserver(observer);    }       //当数据源发生变化的时候调用此方法    public void notifyDataSetChanged() {        mDataSetObservable.notifyChanged();    }    public void notifyDataSetInvalidated() {        mDataSetObservable.notifyInvalidated();    }

很显然,BaseAdapter包含了一个DataSetObservable类型的引用mDataSetObservable,通过前面的说明可知该引用所代表的对象里面封装了若干个观察者,具体注册观察者的方法就是BaseAdapter registerDataSetObserver方法。通过读该源码发现该类提供了一个notifyDataSetChanged()方法,当数据源或者Adapter里面的数据发生变化的时候要手动调用此方法来发起通知!!!!到此为止就找到了是什么方法来为观察者发送通知的,正是notifyDataSetChanged()方法

  以上只是沿着程序的脉络来说明当数据发生变化的时候是怎么通知观察者的。具体观察者都做了的onChange方法都做了什么并没有说明,这些由观察者不同的子类来实现的,这里先不做讨论。下面说说怎样让adapter里面的数据在view里面显示出来。

  上面已经说明了Adapter的一个职责之一就是把数据源组织成一个个view并返回一个view的对象,具体怎么组织的是由Adapter的方法getView来实现的,该方法实在onMeasure()方法执行的时候被调用的,再具体的是在obtainView方法中调用。搞android开发的程序员都少不了和这个方法打交道,这里就不做赘述。

  当把数据放入Adapter之后,通过GirdView(或者ListView这篇文章以GirdView为例)的setAdapter()方法把数据最终展现出来。或许细心的读者会发现在自己开发的过程中并没有在自己的Adapter添加观察者啊?只是简单的setAdapter()之后就什么也不用管了?其实不然,看看setAdapter都做些了什么就会知道


public void setAdapter(ListAdapter adapter) {        //清空之前绑定的mDataSetObserver对象        if (mAdapter != null && mDataSetObserver != null) {            mAdapter.unregisterDataSetObserver(mDataSetObserver);        }//清空之前的一切数据,初始化一些必要的参数        resetList();        mRecycler.clear();          //重置adapter        mAdapter = adapter;//初始化上次选中item的位置        mOldSelectedPosition = INVALID_POSITION;//初始化上次选中行的位置,即:当初选中的行的索引        mOldSelectedRowId = INVALID_ROW_ID;        // AbsListView#setAdapter will update choice mode states.        super.setAdapter(adapter);        if (mAdapter != null) {    //记录之前girdView里面item的数目            mOldItemCount = mItemCount;//当前girdView里面item的数据            mItemCount = mAdapter.getCount();//数据已经变化            mDataChanged = true;//检测焦点            checkFocus();            //注册观察者            mDataSetObserver = new AdapterDataSetObserver();            mAdapter.registerDataSetObserver(mDataSetObserver);            mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());            int position;//判断是否从最后来查找Selectable的的位置//lookForSelectablePosition从方法实现上来看是第二个参数是没有用到的            if (mStackFromBottom) {                  position = lookForSelectablePosition(mItemCount - 1, false);            } else {                position = lookForSelectablePosition(0, true);            }//选中第几个,记录了行和当前girdview的id            setSelectedPositionInt(position);//选中下一个            setNextSelectedPositionInt(position);//检测是否选中的位置改变            checkSelectionChanged();        } else {            checkFocus();                        // Nothing selected            checkSelectionChanged();        }        //充值布局        requestLayout();    }

通过上面的源码可以发现,每次调用setAdapter的时候都会注册AdapterDataSetObserver对象(上面代码33行),这样就可以在adapter发生变化的时候进行响应的处理。

那么看看这个具体的观察者到底都做了些什么:

class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {        @Override        public void onChanged() {            //注意主要的逻辑就在super.onChanged()方法里面            super.onChanged();            if (mFastScroller != null) {                mFastScroller.onSectionsChanged();            }        }        @Override        public void onInvalidated() {            super.onInvalidated();            if (mFastScroller != null) {                mFastScroller.onSectionsChanged();            }        }    }

看看onChange()方法里面调用了父类的方法onChange()方法,主要的响应数据变化的逻辑就在父类的onChange()方法里面,先买看看父类的该方法的具体实现:

class AdapterDataSetObserver extends DataSetObserver {        private Parcelable mInstanceState = null;        @Override        public void onChanged() {            mDataChanged = true;            mOldItemCount = mItemCount;            mItemCount = getAdapter().getCount();            if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null                    && mOldItemCount == 0 && mItemCount > 0) {                AdapterView.this.onRestoreInstanceState(mInstanceState);                mInstanceState = null;            } else {                rememberSyncState();            }            checkFocus();            requestLayout();        }}

最终走到会执行requestLayout()来重新布局页面达到响应数据变化的目的。至此,已经完成了对adapter数据变化来改变当前View的变化的说明。


下面说说使用案例:

有如下效果图

                                                                                      效果图1

注意上图的12个数据时保存在GirdView里面的,当我点击编辑的时候页面变化成如下图所示的情况:

                                                       效果图2

下面具体说说这种效果的具体实现。

1)Adapter的代码如下:

public class CollectionItemAdapter extends BaseAdapter { private Vector<Collection> collections;public static final  int EDIT_STATUS = 0;//为零时为编辑状态public  static final int UNEDIT_STATUS = -1;//为非编辑状态 private int delePosition = UNEDIT_STATUS;//删除标志public int getDelePosition() {return delePosition;}public void setDelePosition(int delePosition) {this.delePosition = delePosition;}public Vector<Collection> getCollections() {return collections;}public void setCollections(Vector<Collection> collections) {this.collections = collections;}@Overridepublic int getCount() {if(collections != null){return collections.size();}// TODO Auto-generated method stubreturn 0;}@Overridepublic Collection getItem(int position) {if(collections !=null){   return collections.get(position);}// TODO Auto-generated method stubreturn null;}@Overridepublic long getItemId(int position) {// TODO Auto-generated method stubreturn position;}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {ViewItem viewItem = null;if(convertView==null){viewItem = new ViewItem();convertView = App.getLayoutInflater().inflate(R.layout.collection_item, null);viewItem.img = (ImageView)convertView.findViewById(R.id.item_img);viewItem.name = (TextView)convertView.findViewById(R.id.item_name);viewItem.editBg = (ImageView)convertView.findViewById(R.id.collection_edit_bg);convertView.setTag(viewItem);}else {viewItem = (ViewItem) convertView.getTag();}viewItem.img.setImageResource(R.drawable.no_pic_small);Collection collection = this.getItem(position);viewItem.name.setText(collection.getName());ImageLoader.getInstance().displayImage(collection.getPicUrl(), viewItem.img, App.getOptionsSmall());viewItem.img.setVisibility(View.VISIBLE);if(delePosition==EDIT_STATUS){//表示为编辑状态//显示删除背景图viewItem.editBg.setVisibility(View.VISIBLE);}else{//隐藏删除背景图viewItem.editBg.setVisibility(View.GONE);}return convertView;}private static class ViewItem {ImageView img;TextView name;ImageView editBg;public String toString(){return name.getText().toString();}}}
注意该Adapter有一个delePosition 用来标志是否处于编辑状态,同时在getView方法里面对该字段进行了判断:当处于非编辑状态的时候运行结果为效果图1,当点击编辑的时候delePoition为编辑状态,此时的页面效果为效果图2.也就是说如果想是实现这个功能只需要改变Adapter对象的这个字段进行设置然后调用notifyDataSetChanged()方法通知观察者就行了,所以当点击编辑的时候的响应事件为:


//设置删除标志                       collectionAdapter.setDelePosition(CollectionItemAdapter.UNEDIT_STATUS);                      //向观察者发生通知collectionAdapter.notifyDataSetChanged();


2)collection_item.xml配置文件如下

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="fill_parent"    android:layout_height="fill_parent" >    <RelativeLayout        android:layout_width="@dimen/wiki_item_w"        android:layout_height="@dimen/wiki_item_h" >        <!-- 海报 -->        <RelativeLayout            android:layout_width="match_parent"            android:layout_height="match_parent"            android:padding="@dimen/pading_17" >            <ImageView                android:id="@+id/item_img"                android:layout_width="fill_parent"                android:layout_height="fill_parent"                android:focusable="false"                android:src="@drawable/test" />        </RelativeLayout>        <!-- 节目名称的背景底图 -->        <RelativeLayout            android:layout_width="match_parent"            android:layout_height="match_parent"            android:padding="@dimen/pading_15" >            <ImageView                android:id="@+id/item_bg_img"                android:layout_width="fill_parent"                android:layout_height="fill_parent"                android:focusable="false"                android:src="@drawable/item_txt_bg" />        </RelativeLayout>       <!--焦点框图片 -->        <ImageView            android:id="@+id/collection_focus"            android:layout_width="match_parent"            android:layout_height="match_parent"            android:background="@drawable/item_focus_selecter" />               <!-- 处于编辑状态的背景图片,开始visibility为gone,当点击编辑是为visible -->        <RelativeLayout            android:layout_width="match_parent"            android:layout_height="match_parent"            android:padding="@dimen/pading_19" >            <ImageView                android:id="@+id/collection_edit_bg"                android:layout_width="match_parent"                android:layout_height="match_parent"                android:focusable="false"                android:background="@drawable/record_collection_edit_selecter"                android:visibility="gone" />        </RelativeLayout>         <!-- 节目名称 -->        <tv.huan.epg.vod.qclt.ui.widget.ScrollForeverTextView            android:id="@+id/item_name"            android:layout_width="fill_parent"            android:layout_height="wrap_content"            android:layout_alignParentBottom="true"            android:layout_marginBottom="@dimen/collection_item_name_margin_left"            android:layout_marginLeft="@dimen/collection_item_name_margin_right"            android:layout_marginRight="@dimen/collection_item_name_margin_bottom"            android:ellipsize="marquee"            android:gravity="center"            android:marqueeRepeatLimit="marquee_forever"            android:singleLine="true"            android:textColor="@drawable/font_color_selector"            android:textSize="@dimen/font_20" />    </RelativeLayout></RelativeLayout>
另外在编辑的时候点击某一个item可以删除,也就是删除Adapter类里面的Vector里面的数据,删除过后同样调用notifyDatasetChanged()方法进行通知即可


略作总结:因为数据都在Adapter里面,所以说Adapter是观察者要观察的主题(SubJect)或者说被观察的对象,Adapter负责注册和销毁观察者,当数据发生变化的时候通知观察者更新ListView.观察者的作用就是当一个对象的状态发生变化的时候,能够自动通知其他对象,以便其他对象做出对应的更新操作。

其中在观察者模式中,Subject是主动变化,而观察者只能根据主题的变化而做出相应的变更。

5 0