使用CursorLoader及更新RecyclerView

  RecyclerView目前还处在“heavy development”的阶段,一些功能仍有待完善。例如ListView可以使用CursorAdapter和CursorLoader,很方便地从ContentProvider获取并更新数据,而RecyclerView并不提供类似CursorAdapter的功能。下面的例子通过从系统通讯录中读取并显示联系人信息,展示了使用CursorLoader更新RecyclerView的方法,其实非常简单,只需在CursorLoader获取到Cursor后,将Cursor更新到Adapter即可。

1. 相关布局

  使用RecyclerView显示联系人列表:

<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布局如下:

<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> {
    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" />

3.1. onCreateLoader()

  onCreateLoader()用于生成请求URI并创建CursorLoader:

@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;

由此可以很方便的从Cursor中读取各列数据,如获取姓名,只需:
String name = mCursor.getString(MainActivityFragment.COL_DISPLAY_NAME);

而不必再根据列名获取列的索引,再由索引从Cursor中读取数据。

3.2. onLoadFinished()

  数据获取完成后,onLoadFinished() 会被调用。我们已经在ContactAdapter中实现了swapCursor() 方法,这里就可以像CursorLoader那样来使用了。

@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中的数据无效化:

@Override
public void onLoaderReset(Loader<Cursor> loader) {
    mContactAdapter.swapCursor(null);
}

  完整文件在这里

4. 启动CursorLoader

  使用getLoaderManager().initLoader() 启动CursorLoader, 使用getLoaderManager().restartLoader() 可以重启CursorLoader。

private static final int LOADER = 0;
...
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
    getLoaderManager().initLoader(LOADER, null, this);
}

  完整文件在这里