支持设置数据为空时打底view的RecyclerView+支持RecyclerView的CursorAdapter

    自从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

主要扩展功能:

  1. SupportRecyclerView#setEmptyView支持设置数据源为空时的view
  2. SupportRecyclerView.SupportAdapter#setOnItemClickListener:设置每个item的点击事件
  3. SupportRecyclerView.SupportAdapter#setOnItemLongClickListener:设置每个item的长按事件
  4. AbstractRecycleCursorAdapter:支持Cursor监听数据实时更新列表

  • RecyclerCursorAdapter
        首先看下CursorAdapter是如何实现自动更新cursor数据的,代码不长,而且其中有很多注释,其实关键的地方也就那么几个。
    1. 观察者模式监听Cursor的变化(init函数):
   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()等方法刷新列表

  • SupportRecyclerView
    既然我们已经知道了CursorAdpter实现的原理,那么实现类似功能的RecyclerView.Adapter也就简单了,上面所说的前两点监听数据和感知到数据变化的方法依然不变,只不过是要把提醒数据刷新的方法改一下就ok了,至于后续的刷新操作,自然还是走RecyclerView.Adapter的createViewHolder和bindViewHolder等等了,这些是要交给子类去实现的,那么这样我们就可以抽象出我们自己的CursorAdapter的基类RecyclerViewCursorAdapter了,代码见:
    https://github.com/mingyangShang/SupportRecyclerDemo/blob/master/app/src/main/java/com/smy/demo/supportrecyclerdemo/AbstractRecycleCursorAdapter.java
    同样的分析方法,我们看ListView是如何实现列表内容是否为空时对空view的展示与隐藏的:
    在ListView的setAdapter方法中:
@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,有了这两个类,我们就能在一些合适的场合更加方便地使用他们了,说不定后续官方会给出更好的实现,不过现在来看以上方式应该就够用了。

你可能感兴趣的:(自定义控件,android)