自从v7包中的recyclerview出来以后很多之前要用listview实现的效果用recyclerview很简单就实现了,而且性能上也会有所提升,但是RecyclerView也有相比ListView不太方便的地方,比如ListView作为AdapterView的子类,有setEmptyView方法来设置列表为空时显示的view,这个功能一般都会用到,但是官方的RecyclerView并没有提供这个方法,另一方面,之前我们在涉及到数据库列表展示的时候会经常使用CursorAdapter,因为它能够在cursor数据变化时自动更新数据并刷新界面,不用我们手动再去调用,setAdapter之后什么都不用管了,而和RecyclerView配套的RecyclerView.Adapter并没有相关的子类提供类似的功能,那么本文就是要提供一种思路来实现扩展RecyclerView支持setEmptyView(View)和RecyclerView.Adapter支持cursor自动管理数据。
项目地址:https://github.com/mingyangShang/SupportRecyclerDemo
if ((flags & FLAG_REGISTER_CONTENT_OBSERVER) == FLAG_REGISTER_CONTENT_OBSERVER) {
mChangeObserver = new ChangeObserver();
mDataSetObserver = new MyDataSetObserver();
} else {
mChangeObserver = null;
mDataSetObserver = null;
}
if (cursorPresent) {
if (mChangeObserver != null) c.registerContentObserver(mChangeObserver);
if (mDataSetObserver != null) c.registerDataSetObserver(mDataSetObserver);
}
这里判断如果设置的flag中有FLAG_REGISTER_CONTENT_OBSERVER标记,那么就会创建一个ContentObserver监听某些内容的变化和一个DataSetObserver监听整个数据集合的变化,然后为Cursor注册这两个观察者,这样的话就能感知到cursor指向数据的变化,在相应的回调中对新数据进行处理。
2. ChangeObserver对数据内容监听:
private class ChangeObserver extends ContentObserver {
public ChangeObserver() {
super(new Handler());
}
@Override
public boolean deliverSelfNotifications() {
return true;
}
@Override
public void onChange(boolean selfChange) {
onContentChanged();
}
}
它是ContentObserver的子类,是对数据内容进行监听的观察者,当它的观察对象的数据发生改变时就会回调onChange方法,在该类的实现中调用了onContentChanged:
protected void onContentChanged() {
if (mAutoRequery && mCursor != null && !mCursor.isClosed()) {
if (false) Log.v("Cursor", "Auto requerying " + mCursor + " due to update");
mDataValid = mCursor.requery();
}
}
也就是当数据发生变化时,默认实现会去按照之前的查找条件重新去查询数据,这样就保证了数据永远是最新的,既然数据已经是最新的了,那么剩下的就是保证页面上展示的数据也要是最新的,下一个重要的点就是实现界面的刷新
3. MyDataSetObserver对整个数据结合监听
private class MyDataSetObserver extends DataSetObserver {
//当整个数据集合变化的时候调用,比如调用Cusor#requery
@Override
public void onChanged() {
mDataValid = true;
notifyDataSetChanged();
}
//当数据不可用时调用,比如调用Cursor#deactive()或者Cursor#close()
@Override
public void onInvalidated() {
mDataValid = false;
notifyDataSetInvalidated();
}
}
上面的注释已经解释了这两个方法被回调的时机,再来看它们的实现,这里我们可以简单地认为这两个方法都是来通知adapter数据已经改变需要刷新UI了,那么在CursorAdapter中就是重新去调用getCount,getItem(),getView()等方法刷新列表
@Override
public void setAdapter(ListAdapter adapter) {
/*****************看这里,看这里********************/
if (mAdapter != null && mDataSetObserver != null) {
mAdapter.unregisterDataSetObserver(mDataSetObserver);
}
/************************************************/
resetList();
mRecycler.clear();
if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
} else {
mAdapter = adapter;
}
mOldSelectedPosition = INVALID_POSITION;
mOldSelectedRowId = INVALID_ROW_ID;
// AbsListView#setAdapter will update choice mode states.
super.setAdapter(adapter);
if (mAdapter != null) {
mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
mOldItemCount = mItemCount;
mItemCount = mAdapter.getCount();
checkFocus();
/******************看这里,看这里*******************/
mDataSetObserver = new AdapterDataSetObserver();
mAdapter.registerDataSetObserver(mDataSetObserver);
/**************************************************/
mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
int position;
if (mStackFromBottom) {
position = lookForSelectablePosition(mItemCount - 1, false);
} else {
position = lookForSelectablePosition(0, true);
}
setSelectedPositionInt(position);
setNextSelectedPositionInt(position);
if (mItemCount == 0) {
// Nothing selected
checkSelectionChanged();
}
} else {
mAreAllItemsSelectable = true;
checkFocus();
// Nothing selected
checkSelectionChanged();
}
requestLayout();
}
我们要重点关注的就是上面我标记的那两行,可以看到这里也是通过注册了一个观察者来监听adapter的数据变化来对emptyview进行可见性操作的,AdapterDataSetObserver在ListView的父类AdapterView中,我们要实现也很简单,只要在这里面对emptyview进行setVisility操作就好了,这样的话我们的EmptyViewSupportRecyclerView就能够支持设置emptyview并自动显示和隐藏了,代码见:https://github.com/mingyangShang/SupportRecyclerDemo/blob/master/app/src/main/java/com/smy/demo/supportrecyclerdemo/SupportRecyclerView.java
ok,有了这两个类,我们就能在一些合适的场合更加方便地使用他们了,说不定后续官方会给出更好的实现,不过现在来看以上方式应该就够用了。