第一弹
一、CursorAdapter介绍
CursorAdapter这个类是继承于BaseAdapter的它是一个虚类它为Cursor和ListView连接提供了桥梁
二、CursorAdapter详解
从图中可以看出CursorAdapter是继承于BaseAdapter的,它有一个间接的子类SimpleCursorAdapter。
1.CursorAdapter的用法
我们首先看一下CursorAdapter的部分源码:
/**
* @see android.widget.ListAdapter#getCount()
*/
public int getCount() {
if (mDataValid && mCursor != null) {
return mCursor.getCount();
} else {
return 0;
}
}
/**
* @see android.widget.ListAdapter#getItem(int)
*/
public Object getItem(int position) {
if (mDataValid && mCursor != null) {
mCursor.moveToPosition(position);
return mCursor;
} else {
return null;
}
}
/**
* @see android.widget.ListAdapter#getItemId(int)
*/
public long getItemId(int position) {
if (mDataValid && mCursor != null) {
if (mCursor.moveToPosition(position)) {
return mCursor.getLong(mRowIDColumn);
} else {
return 0;
}
} else {
return 0;
}
}
@Override
public boolean hasStableIds() {
return true;
}
/**
* @see android.widget.ListAdapter#getView(int, View, ViewGroup)
*/
public View getView(int position, View convertView, ViewGroup parent) {
if (!mDataValid) {
throw new IllegalStateException("this should only be called when the cursor is valid");
}
if (!mCursor.moveToPosition(position)) {
throw new IllegalStateException("couldn't move cursor to position " + position);
}
View v;
if (convertView == null) {
v = newView(mContext, mCursor, parent);
} else {
v = convertView;
}
bindView(v, mContext, mCursor);
return v;
}
可以看出CursorAdapter是继承了BaseAdapter后覆盖它的getView方法在getView方法中调用了newView和bindView方法,
我们在写CursorAdapter时
必须实现它的两个方法。
/**
* Makes a new view to hold the data pointed to by cursor.
* @param context Interface to application's global information
* @param cursor The cursor from which to get the data. The cursor is already
* moved to the correct position.
* @param parent The parent to which the new view is attached to
* @return the newly created view.
*/
public abstract View newView(Context context, Cursor cursor, ViewGroup parent);
/**
* Bind an existing view to the data pointed to by cursor
* @param view Existing view, returned earlier by newView
* @param context Interface to application's global information
* @param cursor The cursor from which to get the data. The cursor is already
* moved to the correct position.
*/
public abstract void bindView(View view, Context context, Cursor cursor);
从源码的getView( int position, View convertView, ViewGroup parent)方法中我们可以看出:
(1)newView:并不是每次都被调用的,它只在实例化的时候调用,数据增加的时候也会调用,但是在重绘
(比如修改条目里的TextView的内容)的时候不会被调用.
(2)bindView:从代码中可以看出在绘制Item之前一定会调用bindView方法它在重绘的时候也同样被调用。
2.CursorAdapter还有一个重要的方法:public void changeCursor (Cursor cursor)。
/**
* Change the underlying cursor to a new cursor. If there is an existing cursor it will be
* closed.
*
* @param cursor The new cursor to be used
*/
public void changeCursor(Cursor cursor) {
Cursor old = swapCursor(cursor);
if (old != null) {
old.close();
}
}
/**
* Swap in a new Cursor, returning the old Cursor. Unlike
* {@link #changeCursor(Cursor)}, the returned old Cursor is <em>not</em>
* closed.
*
* @param newCursor The new cursor to be used.
* @return Returns the previously set Cursor, or null if there wasa not one.
* If the given new Cursor is the same instance is the previously set
* Cursor, null is also returned.
*/
public Cursor swapCursor(Cursor newCursor) {
if (newCursor == mCursor) {
return null;
}
Cursor oldCursor = mCursor;
if (oldCursor != null) {
if (mChangeObserver != null) oldCursor.unregisterContentObserver(mChangeObserver);
if (mDataSetObserver != null) oldCursor.unregisterDataSetObserver(mDataSetObserver);
}
mCursor = newCursor;
if (newCursor != null) {
if (mChangeObserver != null) newCursor.registerContentObserver(mChangeObserver);
if (mDataSetObserver != null) newCursor.registerDataSetObserver(mDataSetObserver);
mRowIDColumn = newCursor.getColumnIndexOrThrow("_id");
mDataValid = true;
// notify the observers about the new cursor
notifyDataSetChanged();
} else {
mRowIDColumn = -1;
mDataValid = false;
// notify the observers about the lack of a data set
notifyDataSetInvalidated();
}
return oldCursor;
}
从源码中可以看出调用此方法后会把当前的mCursor置为新传过来的cursor把原来的cursor返回去并关掉。
作用:当我们的Cursor变化时调用此方法
adapter.changeCursor(cursor),它的功能类似于adapter.notifyDataSetChanged()方法
3.之前的疑惑
之前我一直对cursor是怎么移动的疑惑,比方说cursor中有40条数据,那么它是怎样一行一行移动cursor把这40条数据显示出来的,看过源码后发现其实很简单,
它在getCount()方法中return mCursor.getCount();然后在getView方法的时候调用了mCursor.moveToPosition(position)其实和BaseAdapter的原理是一样的,这样就可以一条一条的绘制条目了。
4:案例
在EditText中输入姓名和电话,点击保存后会显示在下面的listView中.
package com.test.sqlite.customcusoradapter;
import com.test.sqlite.simplecusoradapter.R;
import android.content.Context;
import android.database.Cursor;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import android.widget.TextView;
public class CustomCursorAdapter extends CursorAdapter {
public CustomCursorAdapter(Context context, Cursor c) {
super(context, c);
}
//调用newView方法实例化条目,然后调用bindView绘制条目,当只绘制时不会调用newView方法。
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
ViewHolder viewHolder = new ViewHolder();
LayoutInflater layoutInflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View item = layoutInflater.inflate(R.layout.list_item, parent, false);
viewHolder.age = (TextView) item.findViewById(R.id.textView2);
viewHolder.name = (TextView) item.findViewById(R.id.textView1);
item.setTag(viewHolder);
return item;
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
ViewHolder viewHolder = (ViewHolder) view.getTag();
// 从cursor中得到姓名字段
String name = cursor.getString(cursor.getColumnIndex("name"));
// 从cursor中得到年龄
int age = cursor.getInt(cursor.getColumnIndex("age"));
viewHolder.age.setText(String.valueOf(age));
viewHolder.name.setText(name);
}
public static class ViewHolder {
TextView name;
TextView age;
}
}
package com.test.sqlite.customcusoradapter;
import com.test.sqlite.cusoradapter.bean.Person;
import com.test.sqlite.cusoradapter.dao.PersonServiceImpl;
import com.test.sqlite.cusoradapter.service.PersonSevice;
import com.test.sqlite.simplecusoradapter.R;
import android.app.Activity;
import android.database.Cursor;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.ListView;
public class CustomCursorAdapterActivity extends Activity {
private ListView mListView;
private EditText mAge;
private EditText mName;
private PersonSevice personSer;
private CustomCursorAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_customcursor);
mListView = (ListView) findViewById(R.id.listView1);
mAge = (EditText) findViewById(R.id.e_age);
mName = (EditText) findViewById(R.id.e_name);
personSer = new PersonServiceImpl(this);
adapter = new CustomCursorAdapter(this, null);
mListView.setAdapter(adapter);
}
public void performClick(View view) {
// 添加数据
Person person = new Person();
person.age = Integer.parseInt(mAge.getText().toString());
person.name = mName.getText().toString();
personSer.addPerson(person);
// 查询数据
Cursor myCursor = personSer.queryTheCursor();
// 手动更新adpater中的数据源
adapter.changeCursor(myCursor);
}
}
布局文件:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".SimpleCursorAdapterActivity" >
<GridLayout
android:id="@+id/layout1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:columnCount="3"
android:orientation="horizontal"
android:rowCount="3" >
<TextView android:text="请输入姓名:" />
<EditText
android:id="@+id/e_name"
android:layout_columnSpan="2"
android:layout_gravity="fill"
android:hint="xxxx" />
<TextView android:text="请输入年龄:" />
<EditText
android:id="@+id/e_age"
android:layout_columnSpan="2"
android:layout_gravity="fill"
android:hint="xxxx" />
<Button
android:id="@+id/buttton1"
android:onClick="performClick"
android:text="button" />
</GridLayout>
<ListView
android:id="@+id/listView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/layout1" >
</ListView>
</RelativeLayout>
运行结果:
第二弹
//contentObserver只是通知数据库中内容变化了
cursor.registerContentObserver(mChangeObserver);
//datasetObserver是调用requery之后通知上层cursor数据内容已经更新
cursor.registerDataSetObserver(mDataSetObserver);
我们的调用流程如下:
当我们使用getContentResolver().query()的时候,我们的resolver会通过uri找到对应的provider,调用相应的query()方法,
该方法中的部分内容:
SQLiteDatabase db = mOpenHelper.getReadableDatabase();
Cursor ret = qb.query(db, projection, selection,selectionArgs, null, null, finalSortOrder);
// TODO: Does this need to be a URI for this provider.
ret.setNotificationUri(getContext().getContentResolver(), uri);
return ret;
Cursor本身只是接口,实际调用的应该是AbstractCursor
public void setNotificationUri(ContentResolver cr, Uri notifyUri) {
synchronized (mSelfObserverLock) {
mNotifyUri = notifyUri;
mContentResolver = cr;
if (mSelfObserver != null) {
mContentResolver.unregisterContentObserver(mSelfObserver);
}
mSelfObserver = new SelfContentObserver(this);
mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver);
mSelfObserverRegistered = true;
}
}
//AbstractCursor的内部类
protected static class SelfContentObserver extends ContentObserver {
WeakReference<AbstractCursor> mCursor;
public SelfContentObserver(AbstractCursor cursor) {
super(null);
mCursor = new WeakReference<AbstractCursor>(cursor);
}
@Override
public boolean deliverSelfNotifications() {
return false;
}
@Override
public void onChange(boolean selfChange) {
AbstractCursor cursor = mCursor.get();
if (cursor != null) {
cursor.onChange(false);
}
}
}
ContentObservable mContentObservable = new ContentObservable(); //AbstractCursor持有的contentObservable
//AbstractCursor的onchange()
protected void onChange(boolean selfChange) {
synchronized (mSelfObserverLock) {
mContentObservable.dispatchChange(selfChange);
if (mNotifyUri != null && selfChange) {
mContentResolver.notifyChange(mNotifyUri, mSelfObserver); //selfChange = false
}
}
}
AbstractCursor中
public void registerContentObserver(ContentObserver observer) {
mContentObservable.registerObserver(observer);
}
在ContentObservable中
public void dispatchChange(boolean selfChange) {
synchronized(mObservers) {
for (ContentObserver observer : mObservers) {
if (!selfChange || observer.deliverSelfNotifications()) {
observer.dispatchChange(selfChange);
}
}
}
}
在CursorAdapter中我们注册了ContentObserver
private class ChangeObserver extends ContentObserver {
public ChangeObserver() {
super(new Handler());
}
@Override
public boolean deliverSelfNotifications() {
return true;
}
@Override
public void onChange(boolean selfChange) {
onContentChanged();
}
}
//父类的方法
public final void dispatchChange(boolean selfChange) {
if (mHandler == null) {
onChange(selfChange);
} else {
mHandler.post(new NotificationRunnable(selfChange));
}
}
//父类的内部类
private final class NotificationRunnable implements Runnable {
private boolean mSelf;
public NotificationRunnable(boolean self) {
mSelf = self;
}
public void run() {
ContentObserver.this.onChange(mSelf);
}
}
在CursorAdapter中
protected void onContentChanged() {
if (mAutoRequery && mCursor != null && !mCursor.isClosed()) {
if (Config.LOGV) Log.v("Cursor", "Auto requerying " + mCursor + " due to update");
mDataValid = mCursor.requery();
}
}
在AbstractCursor中
public boolean requery() {
if (mSelfObserver != null && mSelfObserverRegistered == false) {
mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver);
mSelfObserverRegistered = true;
}
mDataSetObservable.notifyChanged(); //任何继承AbstractCursor的Cursor都将通过调用super.requery()执行此句
return true;
}
如果我们注册了dataset的observer,就会得到相应的通知。
总结:
contentObserver是一个提前通知,这时候只是通知cursor说,我的内容变化了。
datasetObserver是一个后置通知,只有通过requery() deactivate() close()方法的调用才能获得这个通知。
因此,最为重要的还是ContentObserver,它可以告诉你数据库变化了,当然如果你要在更新完Cursor的dataset之后做一些
事情,datasetObserver也是必需的。
第三弹
用一个条件检查来检测当前运行应用程序的设备的版本:
Cursor c;
if (android.os.Build.VERSION.SDK_INT <11) { //---before Honeycomb---
c = managedQuery(allContacts, null, null, null, null);}
else { //---Honeycomb and later---
CursorLoader cursorLoader = new CursorLoader(this, allContacts, null, null,null ,null);
c = cursorLoader.loadInBackground();
}
如果应用程序运行在早于Honeycomb的设备上(android.os.Build.VERSION.SDK_INT变量的值小于11),那么可以使用Activity类的managedQuery()方法来检索一个托管游标。托管游标处理在应用程序暂停时卸载其自身和在应用程序重启时重新查询自身的所有工作。语句:Cursor c = managedQuery(allContacts, null, null, null, null);
等价于:Cursor c=getContentResolver().query(allContacts,null,null,null,null);
//---allows the activity to manage the Cursor 's
// lifecyle based on the activity's lifecycle---
startManagingCursor(c); getContentResolver()方法返回一个ContentResolver对象,它可以使用合适的内容提供者来帮助解析内容URI。
但是,从Android API级别11开始(Honeycomb及更高版本),manageQuery()方法被弃用了(仍然可以使用,但是不建议这么做)。对于运行Honeycomb或更高版本的设备,应该使用CursorLoader类:
CursorLoader cursorLoader = new CursorLoader( this, allContacts, null, null, null , null);
c = cursorLoader.loadInBackground(); CursorLoader类(从Android API级别11才开始可用)在后台线程中执行游标查询,因此不会阻塞应用程序的用户界面。
SimpleCursorAdapter对象将一个游标映射到在XML文件(main.xml)中定义的TextView (或者ImageView),并将数据(由columns表示)映射到视图(由views表示)上:
String[] columns = new String[] { ContactsContract.Contacts.DISPLAY_NAME, ContactsContract.Contacts._ID}; int[] views = new int[] {R.id.contactName, R.id.contactID}; SimpleCursorAdapter adapter; if (android.os.Build.VERSION.SDK_INT <11) { //---before Honeycomb--- adapter = new SimpleCursorAdapter( this, R.layout.main, c, columns, views); } else { //---Honeycomb and later--- adapter = new SimpleCursorAdapter( this, R.layout.main, c, columns, views, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER); } this.setListAdapter(adapter);
与managedQuery()方法类似,SimpleCursorAdapter类的一个构造函数已被弃用。对于Honeycomb及更高版本的设备,需要使用SimpleCursorAdapter类的一个新构造函数,它带有一个额外的参数://---Honeycomb and later--- adapter = new SimpleCursorAdapter( this, R.layout.main, c, columns, views, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER); 这个标志注册了当内容提供者中发生更改时所通知的适配器。
注意,为了使应用程序可以访问Contacts程序,需要在AndroidManifest.xml文件中有READ_CONTACTS权限。
第四弹
CursorAdapter
继承于BaseAdapter是个虚类,它为cursor和ListView提供了连接的桥梁。
public abstract class
CursorAdapter
extends BaseAdapter
直接子类只有ResourceCursorAdapter
Class Overview
Adapter that exposes data from a Cursor to a ListView widget.
The Cursor must include a column named "_id" or this class will not work.
注意cursor的必须要有个命名为"_id"的列。比如Contacts._ID就为"_id".
必须实现以下函数:
abstract View newView(Context context, Cursor cursor, ViewGroup parent)
Makes a new view to hold the data pointed to by cursor.
abstract void bindView(View view, Context context, Cursor cursor)
Bind an existing view to the data pointed to by cursor
注意:
newView该函数第一次回调用后,如果数据增加后也会再调用,但是重绘是不会调用的。
数据增加后,回调用该函数来生成与新增数据相对应的view。
bindView函数第一次回调用后,如果数据更新也会再调用,但重绘会再次调用的。
【总的来说应该是在调用bindView如果发现view为空会先调用newView来生成view】
01.import java.util.List;
02.import android.app.Activity;
03.import android.app.ListActivity;
04.import android.os.Bundle;
05.import android.os.Handler;
06.import android.content.Context;
07.import android.content.ContentValues;
08.import android.database.Cursor;
09.import android.view.LayoutInflater;
10.import android.view.View;
11.import android.widget.ListView;
12.import android.view.ViewGroup;
13.import android.widget.ArrayAdapter;
14.import android.widget.CursorAdapter;
15.import android.widget.TextView;
16.import android.provider.ContactsContract.Contacts;
17.import android.provider.ContactsContract.RawContacts;
18.import android.view.View.OnClickListener;
19.import android.widget.Button;
20.public class HelloCursor extends ListActivity {
21.private static String[] PROJECTION = new String[] { Contacts._ID,Contacts.DISPLAY_NAME };
22.
23.
24. /** Called when the activity is first created. */
25. @Override
26. public void onCreate(Bundle savedInstanceState) {
27. super.onCreate(savedInstanceState);
28. setContentView(R.layout.main);
29. Cursor c = getContentResolver().query(Contacts.CONTENT_URI, PROJECTION,
30. null, null, Contacts.DISPLAY_NAME + " COLLATE NOCASE");
31. startManagingCursor(c);
32. MyCursorAdapter adapter = new MyCursorAdapter(this, R.layout.list_row,
33. c);
34. this.setListAdapter(adapter);
35. Button button = (Button)findViewById(R.id.Button01);
36. OnClickListener listener=new OnClickListener(){
37. @Override
38. public void onClick(View v) {
39. doAction();
40. }
41. };
42. button.setOnClickListener(listener);
43. mHandler = new Handler();
44.
45. }
46.
47. private String[] mStrings = { "hubin", "hudashi", "robin" };
48. int cnt = 0;
49. private Handler mHandler;
50.
51. class AddContactThread implements Runnable {
52. public void run() {
53. int nStringLength = mStrings.length;
54. int randomNumber = 0;
55. ContentValues newValues = new ContentValues();
56. String tempString = null;
57. randomNumber = (int) (Math.random() % 10);
58. for (int i = 0; i < nStringLength; i++) {
59. tempString = mStrings + cnt + randomNumber;
60. newValues.put(Contacts.DISPLAY_NAME, tempString);
61. getContentResolver().insert(RawContacts.CONTENT_URI, newValues);
62. newValues.clear();
63.
64. }
65. cnt++;
66. }
67. }
68. AddContactThread addContact=new AddContactThread();
69. void doAction()
70. {
71. mHandler.post(addContact);
72. }
73.}
74.class MyCursorAdapter extends CursorAdapter {
75. Context context=null;
76. int viewResId;
77. public MyCursorAdapter(Context context, int resource, Cursor cursor) {
78. super(context,cursor);
79. viewResId=resource;
80. }
81. public View newView(Context context, Cursor cursor, ViewGroup parent) {
82.
83. TextView view =null;
84. LayoutInflater vi = null;
85. vi = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
86. view =(TextView)vi.inflate(viewResId, parent, false);
87. //v =(TextView)vi.inflate(textViewResourceId,null);
88. Log.i("hubin","newView"+view);
89. return view;
90. }
91. @Override
92. public void bindView(View view, Context context, Cursor cursor) {
93. Log.i("hubin","bind"+view);
94. TextView nameView = (TextView) view;
95. // Set the name
96. nameView.setText(cursor
97. .getString(cursor.getColumnIndex("DISPLAY_NAME")));
98. }
99.}