Cursor,CursorAdapter这两个东西是在Android编程中非常频繁使用的东西。但是由于一直以来只是知其然,不知其所以然。每次想要在这个基础上做一些改变都显得比较困难,需要花很长的时间在网上找案例来学习。顾今天特地抽了一点时间把这两个家伙学习了一遍。目前的理解还谈不上精通,仅比知其然略进了一步,本文只是个人的学习笔记,有错误的地方欢迎指正。
在理解Cursor和CursorAdapter之前,需要具备一定的设计模式的基础,比如应了解观察者模式、适配器模式、工厂模式等等。
Cursor从字面上解释为游标,这听起来有些抽象。我们用一个具体的Cursor子类SQLiteCursor来做解释就容易理解了。SQLiteCursor包含了数据库查询获得结果集以及一个当前行的指针,SQLiteDatabase的查询结果在未指定特定Cursor工厂的情况下就会存入这个Cursor。我们可以进一步把它简单理解成结果集,只不过这个结果集我们很少会直接操作。
CursorAdapter是将Cursor与View适配起来的一个适配器。简单说就是:Cursor给了CursorAdapter,CusrorAdapter给了View。Cursor里面的内容就能显示在View中啦~~~。
我们主要来谈谈CursorAdapter与Cursor的关系,说这个的时候,我们用CursorAdapter的子类SimpleCursorAdapter来举例。使用SimpleCursorAdapter的时候,我们需要做三件事情:
1. 明确这个Adapter的入和出。入就是Cursor,需要具体到结果集里面的哪些列,出就是界面上的元素,这些元素是用来显示结果集每一列里面的内容的。看例子,title对应subject_date_text1,date对应subject_date_text2
// Create an array to specify the fields we want to display in the list
String[] from = new String[]{“title”, “date”};
// and an array of the fields we want to bind those fields to
int[] to = new int[]{R.id.subject_date_text1, R.id.subject_date_text2};
2.初始化这个adapter,这里的几个参数值得我们好好看一下,一个个来说。
第二个参数layout:指定具体描述ListItem的Layout文件,这个参数会被Adapter保存起来在实例化View时使用。这个是适配器模式的一个典型使用方法,View只需要知道适配器的接口,比如CursorAdapter提供给View的接口是newView(Context context, Cursor cursor, ViewGroup parent)。View在需要的时候调用这个接口,而具体newView怎么实现由CursorAdapter的子类来实现。
第三个参数cursor:如果初始化的时候cursor还没有准备好,可以是null。一般都是这样的情况。后面等cursor准备好了用adapter的swapCursor来使数据有效
最后一个参数flag:这个参数是告诉Adapter要不要成为这个cursor的观察者。一般情况下cursor不会随着数据库内容的变化而自动产生变化,这就导致让adapter成为cursor的观察者也没有太大的意义。另一方面,由于Adapter是在主线程中进行的操作,这里使用观察者模式可能会让界面显示出现卡顿。也许有人已经注意到SimpleCursorAdapter之前有一个不带flag参数的构造函数,3.0以后就被废弃了。这个构造函数是默认使用观察者模式来跟踪cursor数据的,废弃这个构造函数可以认为是google不再推荐在这里使用观察者模式,那现在应该用何种方式来更新adapter,可以看我的另一篇文章,使用AsyncTaskLoader动态载入SQLite数据 。
notesAdapter = new SimpleCursorAdapter(getActivity(), R.layout.row_subject_date, null, from, to, 0);
3. 重写View绑定方法,如果仅仅是将第一步中的from一对一映射到to中,且to中的View是TextView或者ImageView。是不需要重写View方法的,SimpleCursorAdapter已经帮我们做好了这一步。
这里为什么要来说这个呢,我自己在学习的时候看到网上很多人写的案例都是不管效果是不是复杂,都是自己写一个CursorAdapter子类,其实很多效果我们用SimpleCursorAdapter也是可以实现的,完全没有必要自己做一个Adapter。
具体方法就是重载SimpleCursorAdapter的内部适配器setViewBuilder,这个适配器接口只有一个方法setViewValue(View view, Cursor cursor, int column)。Adapter的bindView接口方法被调用的时候,SimpleCursorAdapter首先会调用setViewBuilder.setViewValue,如果返回False,那么再使用SimpleCursorAdapter内置的逻辑来映射from到to。看到这里我们应该能够明白了,只要我们重载了setViewValue这个方法,我们就可以任意安排from里的字段怎么来显示。比如,不同的from显示不同的背景;不同的行显示不同的背景;定义一个多行的layout,某些元素可以在from特定值的时候隐藏显示等等。。。
下面给出这个例子的代码:
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main, container, false);
// Create an array to specify the fields we want to display in the list (only TITLE)
String[] from = new String[]{NotesDbAdapter.KEY_TITLE, NotesDbAdapter.KEY_DATETIME};
// and an array of the fields we want to bind those fields to (in this case just text1)
int[] to = new int[]{R.id.subject_date_text1, R.id.subject_date_text2};
notesAdapter =
new SimpleCursorAdapter(getActivity(), R.layout.row_subject_date, null, from, to, 0);
notesAdapter.setViewBinder(new SimpleCursorAdapter.ViewBinder() {
@Override
public boolean setViewValue(View view, Cursor cursor, int column) {
if (cursor.isClosed())
return false;
if( view.getId() == R.id.subject_date_text1){
TextView row = (TextView)view;
String title = cursor.getString(cursor.getColumnIndex(NotesDbAdapter.KEY_TITLE));
row.setText(title);
return true;
}
if( view.getId() == R.id.subject_date_text2){
TextView row = (TextView)view;
Long timeInLong = cursor.getLong(cursor.getColumnIndex(NotesDbAdapter.KEY_DATETIME));
Time time = new Time();
Time now = new Time();
now.setToNow();
time.set(timeInLong);
if (time.format3339(true).equals(now.format3339(true)))
row.setText(time.format("%H:%M:%S"));
else
row.setText(time.format3339(true));
return true;
}
return false;
}
});
setListAdapter(notesAdapter);
return rootView;
}