首先说说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

注意上图的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;
} @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是主动变化。而观察者仅仅能依据主题的变化而做出对应的变更。

Adapter数据变化改变现有View的实现原理及案例的更多相关文章

  1. ListView和Adapter数据适配器的简单介绍

    ListView 显示大量相同格式数据 常用属性: listSelector            listView每项在选中.按下等不同状态时的Drawable divider            ...

  2. [Android Pro] 监听内容提供者ContentProvider的数据变化

    转载自:http://blog.csdn.net/woshixuye/article/details/8281385 一.提出需求 有A,B,C三个应用,B中的数据需要被共享,所以B中定义了内容提供者 ...

  3. Android开发14——监听内容提供者ContentProvider的数据变化

    一.提出需求 有A,B,C三个应用,B中的数据需要被共享,所以B中定义了内容提供者ContentProvider:A应用修改了B应用的数据,插入了一条数据.有这样一个需求,此时C应用需要得到数据被修改 ...

  4. 关于微信小程序使用watch监听数据变化的方法

    众所周知,Vue中,可以使用监听属性 watch来观察和响应 Vue 实例上的数据变化,那么小程序能不能实现这一点呢? 监听器的原理,是将data中需监听的数据写在watch对象中,并给其提供一个方法 ...

  5. vue.js之过滤器,自定义指令,自定义键盘信息以及监听数据变化

    一.监听数据变化 1.监听数据变化有两种,深度和浅度,形式如下: vm.$watch(name,fnCb); //浅度 vm.$watch(name,fnCb,{deep:true}); //深度监视 ...

  6. Android ContenObserver 监听联系人数据变化

    一.知识介绍 1.ContentProvider是内容提供者 ContentResolver是内容解决者(对内容提供的数据进行操作) ContentObserver是内容观察者(观察内容提供者提供的数 ...

  7. 【云栖大会】阿里巴巴集团CTO张建锋:用计算和数据去改变整个世界

    摘要: 当浩瀚的数字化信息能够联网在线,在万物互联网的新世界中,所有东西都可能有感知.变智能,想象一下电表.冰箱.心电图监测仪等设备的信息都能数字化并联网,从城市管理到个人生活,都会迎来翻天覆地的变化 ...

  8. python使用mysql connection获取数据感知不到数据变化问题

    在做数据同步校验的时候,需要从mysql fetch数据和hbase的数据进行对比,发现即使mysql数据变化了,类似下面的代码返回的值还是之前的数据.抽取的代码大概如下: import MySQL ...

  9. 当From窗体中数据变化时,使用代码获取数据库中的数据然后加入combobox中并且从数据库中取得最后的结果

    private void FormLug_Load(object sender, EventArgs e) { FieldListLug.Clear();//字段清除 DI = double.Pars ...

随机推荐

  1. streaming优化:并行接收数据

    val numStreams = 5 val kafkaStreams = (1 to numStreams).map { i => KafkaUtils.createStream(...) } ...

  2. Mysql创建用户并授权以及开启远程访问

    Mysql创建用户并授权以及开启远程访问     一.创建用户并授权 1.登录mysql mysql -u root -p 2.创建数据库 create database test;//以创建test ...

  3. fatal error C1060:compiler is out of heap space

    今天svn update了下代码,rebuild工程的时候报错: fatal error C1060:compiler is out of heap space 意思是说编译器堆内存不足 百度结果:V ...

  4. 循序渐进学.Net Core Web Api开发系列【2】:利用Swagger调试WebApi

    系列目录 循序渐进学.Net Core Web Api开发系列目录 本系列涉及到的源码下载地址:https://github.com/seabluescn/Blog_WebApi 一.概述 既然前后端 ...

  5. 将NX模型导入Process Designer的方法

    如何把一个有焊点的零件从nx中输入到process designer 中?   用户在NX中做了一个prt文件, 想把它输入到process designer中, 并且包括焊点信息, 该如何做? 解决 ...

  6. 实现常用的配置文件/初始化文件读取的一个C程序

    在编程中,我们经常会遇到一些配置文件或初始化文件.这些文件通常后缀名为.ini或者.conf,可以直接用记事本打开.里面会存储一些程序参数,在程序中直接读取使用.例如,计算机与服务器通信,服务器的ip ...

  7. 吴恩达-coursera-机器学习-week9

    十五.异常检测(Anomaly Detection) 15.1 问题的动机 15.2 高斯分布 15.3 算法 15.4 开发和评价一个异常检测系统 15.5 异常检测与监督学习对比 15.6 选择特 ...

  8. Eclipse中执行Maven命令时控制台输出乱码

    Maven 默认编码为 GBK: 在 Eclipse 控制台输出乱码: 解决方法:将以下代码添加到 pom.xml 的 <project> 节点下: <project> …… ...

  9. CSS动画简介

    现在,我很少写介绍CSS的文章,因为感觉网站开发的关键还是在服务器端. 但是,CSS动画除外,它实在太有用了. 本文介绍CSS动画的两大组成部分:transition和animation.我不打算给出 ...

  10. stm32f103串口实现映射功能

    在实际开发中,常常遇到串口的默认输出IO口被其它模块占用了,所以我们要用到串口IO口映射功能.是指将原来实现功能的IO口映射到其它指定IO口,其它不变.详细操作例如以下: 先贴出默认下的串口初始化设置 ...