Adapter数据变化改变现有View的实现原理及案例
首先说说Adapter详细的类的继承关系。例如以下图
Adapte为接口它的实现类的对象作为AdapterView和View的桥梁,Adapter是装载了View(比方ListView和girdView要显示的数据)。相关View要显示的数据全然与View解耦。View要显示的数据从Adapter里面获取并展现出来;Adapter负责把真实的数据是配成一个个View(每一条数据相应一个View)让GirdView等类似的组件来显示这些是配好的一个个View,。也就是说View要显示什么数据取决于Adapter。View的变化(比方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
效果图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; } @Override public int getCount() { if(collections != null){ return collections.size(); } // TODO Auto-generated method stub return 0; } @Override public Collection getItem(int position) { if(collections !=null){ return collections.get(position); } // TODO Auto-generated method stub return null; } @Override public long getItemId(int position) { // TODO Auto-generated method stub return position; } @Override public 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是主动变化。而观察者仅仅能依据主题的变化而做出对应的变更。