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有以下两个主要的职责:

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 用来标志是否处于编辑状态,当处于非编辑状态的时候运行结果为效果图1,当点击编辑的时候delePoition为编辑状态,此时的页面效果为

效果图2.当点击编辑的时候的响应事件为:

			//设置删除标志
                       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>

初学android,欢迎批评指正

时间: 2024-10-13 11:06:05

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

赵雅智_ProviderContent监听数据变化

当程序A在执行insert.update.delete时,通过getContext().getContentResolver().notifyChange(uri, observer)方法来告诉所有注册在该Uri的监听者数据发生改变 参数1uri:注册的uri 参数2observer:注册的监听者 /** * 插入操作 */ @Override public Uri insert(Uri uri, ContentValues values) { if (uriMatcher.match(uri)

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

ListView 显示大量相同格式数据 常用属性: listSelector            listView每项在选中.按下等不同状态时的Drawable divider                ListView每项间的间隔Drawable dividerHeight        ListView每项间间隔的间隔高度 常用方法: setAdapter()                设置数据适配器 setOnItemClickListener()        设置每项点击事件

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

转载自:http://blog.csdn.net/woshixuye/article/details/8281385 一.提出需求 有A,B,C三个应用,B中的数据需要被共享,所以B中定义了内容提供者ContentProvider:A应用修改了B应用的数据,插入了一条数据.有这样一个需求,此时C应用需要得到数据被修改的通知并处理相应操作. 二.示例代码 A应用 /** * 对内容提供者进行操作 * * @author XY * */ public class MainActivity exten

android 修改listview中adapter数据时抛出异常java.lang.IllegalStateException: The content of the adapter has changed but ListView did not receive a notification问题

近日在做项目时遇到非必现crush,具体异常信息为: // Short Msg: java.lang.IllegalStateException // Long Msg: java.lang.IllegalStateException: The content of the adapter has changed but ListView did not receive a notification. Make sure the content of your adapter is not mo

Angularjs【监听数据的变化】和【如何修改数据】和【数据变化的传播】

一:监听数据的变化: 由于编译仅仅在启动引导时执行一次,这意味着我们的link函数只会被调用一次,那么, 如果数据变化,在界面上将不会有任何反馈,即界面和数据将变得不同步了. 这需要持续监听数据的变化. 好在AngularJS的scope对象可以使用$watch()方法,对建立在其上的变量的变化进行监听: watch(watchExpression,listener,[objectEquality]);watch方法要求传入三个参数: watchExpression - 要监听的表达式,比如:"

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

在做数据同步校验的时候,需要从mysql fetch数据和hbase的数据进行对比,发现即使mysql数据变化了,类似下面的代码返回的值还是之前的数据.抽取的代码大概如下: 1 import MySQL 2 3 conn = MySQL.connect(host = mysql_config['host'], 4 user = mysql_config['username'], 5 password = mysql_config['password'], 6 port = int(mysql_c

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

private void FormLug_Load(object sender, EventArgs e) { FieldListLug.Clear();//字段清除 DI = double.Parse(tbDn.Text);//DI等于tbdn中的值 把值强制转化成DOUBLE型 string TypeName = "Y_SUPPORT_LUG_4712_3_2007_TYPE";//定义查询表名 string where = string.Format("DnX <

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

一.监听数据变化 1.监听数据变化有两种,深度和浅度,形式如下: vm.$watch(name,fnCb); //浅度 vm.$watch(name,fnCb,{deep:true}); //深度监视 2.实例用法 2.1-1浅度监听:当点击页面,弹出发生变化了,a的值变为1,b的值变为101 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8">

Android ContenObserver 监听联系人数据变化

一.知识介绍 1.ContentProvider是内容提供者 ContentResolver是内容解决者(对内容提供的数据进行操作) ContentObserver是内容观察者(观察内容提供者提供的数据变化) 2.ContentObserver需要ContentResolver进行注册. resolver.registerContentObserver(uri,true,observer): ①URI(第一个参数):该监听所监听ContentProvider的Uri ②notifyForDesc