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