使用CursorLoader及更新RecyclerView
Author: nex3z
2016-01-02
RecyclerView目前还处在“heavy development”的阶段,一些功能仍有待完善。例如ListView可以使用CursorAdapter和CursorLoader,很方便地从ContentProvider获取并更新数据,而RecyclerView并不提供类似CursorAdapter的功能。下面的例子通过从系统通讯录中读取并显示联系人信息,展示了使用CursorLoader更新RecyclerView的方法,其实非常简单,只需在CursorLoader获取到Cursor后,将Cursor更新到Adapter即可。
1. 相关布局
使用RecyclerView显示联系人列表:
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/contact_list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/contact_list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/contact_list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
完整文件在这里。
RecyclerView中的item布局如下:
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
android:id="@+id/contact"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:gravity="center_vertical"
tools:text="Barack Obama"
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<TextView
android:id="@+id/contact"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:gravity="center_vertical"
android:textSize="16sp"
tools:text="Barack Obama"
/>
</FrameLayout>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<TextView
android:id="@+id/contact"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:gravity="center_vertical"
android:textSize="16sp"
tools:text="Barack Obama"
/>
</FrameLayout>
里面只有一个TextView。完整文件在这里。
2. 实现swapCursor()
CursorAdapter提供swapCursor() 方法来更新Cursor,我们需要在RecyclerView的Adapter中提供自己的实现:
public class ContactAdapter extends RecyclerView.Adapter<ContactAdapter.ViewHolder> {
public void swapCursor(Cursor newCursor) {
public class ContactAdapter extends RecyclerView.Adapter<ContactAdapter.ViewHolder> {
private Cursor mCursor;
...
public void swapCursor(Cursor newCursor) {
mCursor = newCursor;
notifyDataSetChanged();
}
}
public class ContactAdapter extends RecyclerView.Adapter<ContactAdapter.ViewHolder> {
private Cursor mCursor;
...
public void swapCursor(Cursor newCursor) {
mCursor = newCursor;
notifyDataSetChanged();
}
}
这里ContactAdapter 使用mCursor 作为数据集,swapCursor() 方法将传入的newCursor 更新到本地,并由notifyDataSetChanged() 通知数据更新。
完整文件在这里。
3. 使用CursorLoader
使用CursorLoader只需实现LoaderManager.LoaderCallbacks<Cursor> 接口,重写onCreateLoader() 、onLoadFinished() 和onLoaderReset() 方法。首先要记得在AndroidManifest.xml中声明READ_CONTACTS权限:
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
3.1. onCreateLoader()
onCreateLoader()用于生成请求URI并创建CursorLoader:
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
Uri baseUri = ContactsContract.Contacts.CONTENT_URI;
String select = "((" + ContactsContract.Contacts.DISPLAY_NAME + " NOTNULL) AND ("
+ ContactsContract.Contacts.HAS_PHONE_NUMBER + "=1) AND ("
+ ContactsContract.Contacts.DISPLAY_NAME + " != '' ))";
return new CursorLoader(getActivity(), baseUri,
CONTACTS_SUMMARY_PROJECTION, select, null,
ContactsContract.Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
Uri baseUri = ContactsContract.Contacts.CONTENT_URI;
String select = "((" + ContactsContract.Contacts.DISPLAY_NAME + " NOTNULL) AND ("
+ ContactsContract.Contacts.HAS_PHONE_NUMBER + "=1) AND ("
+ ContactsContract.Contacts.DISPLAY_NAME + " != '' ))";
return new CursorLoader(getActivity(), baseUri,
CONTACTS_SUMMARY_PROJECTION, select, null,
ContactsContract.Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
Uri baseUri = ContactsContract.Contacts.CONTENT_URI;
String select = "((" + ContactsContract.Contacts.DISPLAY_NAME + " NOTNULL) AND ("
+ ContactsContract.Contacts.HAS_PHONE_NUMBER + "=1) AND ("
+ ContactsContract.Contacts.DISPLAY_NAME + " != '' ))";
return new CursorLoader(getActivity(), baseUri,
CONTACTS_SUMMARY_PROJECTION, select, null,
ContactsContract.Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
}
使用ContactsContract.Contacts.CONTENT_URI 来获取联系人数据,构造select 参数选择有实际名字和电话的联系人,由此创建CursorLoader,返回结果按姓名的升序排列。
注意这里CONTACTS_SUMMARY_PROJECTION 以数组的形式保存了所需的数据列,并通过int常量保存对应索引:
static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
ContactsContract.Contacts._ID,
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.Contacts.CONTACT_STATUS,
ContactsContract.Contacts.CONTACT_PRESENCE,
ContactsContract.Contacts.PHOTO_ID,
ContactsContract.Contacts.LOOKUP_KEY,
static final int COL_ID = 0;
static final int COL_DISPLAY_NAME = 1;
static final int COL_CONTACT_STATUS = 2;
static final int COL_CONTACT_PRESENCE = 3;
static final int COL_PHOTO_ID = 4;
static final int COL_LOOKUP_KEY = 5;
static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
ContactsContract.Contacts._ID,
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.Contacts.CONTACT_STATUS,
ContactsContract.Contacts.CONTACT_PRESENCE,
ContactsContract.Contacts.PHOTO_ID,
ContactsContract.Contacts.LOOKUP_KEY,
};
static final int COL_ID = 0;
static final int COL_DISPLAY_NAME = 1;
static final int COL_CONTACT_STATUS = 2;
static final int COL_CONTACT_PRESENCE = 3;
static final int COL_PHOTO_ID = 4;
static final int COL_LOOKUP_KEY = 5;
static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
ContactsContract.Contacts._ID,
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.Contacts.CONTACT_STATUS,
ContactsContract.Contacts.CONTACT_PRESENCE,
ContactsContract.Contacts.PHOTO_ID,
ContactsContract.Contacts.LOOKUP_KEY,
};
static final int COL_ID = 0;
static final int COL_DISPLAY_NAME = 1;
static final int COL_CONTACT_STATUS = 2;
static final int COL_CONTACT_PRESENCE = 3;
static final int COL_PHOTO_ID = 4;
static final int COL_LOOKUP_KEY = 5;
由此可以很方便的从Cursor中读取各列数据,如获取姓名,只需:
String name = mCursor.getString(MainActivityFragment.COL_DISPLAY_NAME);
String name = mCursor.getString(MainActivityFragment.COL_DISPLAY_NAME);
String name = mCursor.getString(MainActivityFragment.COL_DISPLAY_NAME);
而不必再根据列名获取列的索引,再由索引从Cursor中读取数据。
3.2. onLoadFinished()
数据获取完成后,onLoadFinished() 会被调用。我们已经在ContactAdapter中实现了swapCursor() 方法,这里就可以像CursorLoader那样来使用了。
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
mContactAdapter.swapCursor(data);
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
mContactAdapter.swapCursor(data);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
mContactAdapter.swapCursor(data);
}
通过swapCursor() 将获取到的数据data 送入ContactAdapter,ContactAdapter会进一步地通知数据更新。
3.3. onLoaderReset()
onLoaderReset() 在之前所创建的CursorLoader被重置时调用,此时之前获取的数据已经无效了,类似CursorLoader,使用swapCursor() 传入null将Adapter中的数据无效化:
public void onLoaderReset(Loader<Cursor> loader) {
mContactAdapter.swapCursor(null);
@Override
public void onLoaderReset(Loader<Cursor> loader) {
mContactAdapter.swapCursor(null);
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
mContactAdapter.swapCursor(null);
}
完整文件在这里。
4. 启动CursorLoader
使用getLoaderManager().initLoader() 启动CursorLoader, 使用getLoaderManager().restartLoader() 可以重启CursorLoader。
private static final int LOADER = 0;
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
getLoaderManager().initLoader(LOADER, null, this);
private static final int LOADER = 0;
...
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
getLoaderManager().initLoader(LOADER, null, this);
}
private static final int LOADER = 0;
...
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
getLoaderManager().initLoader(LOADER, null, this);
}
完整文件在这里。