为RecyclerView设置emptyView

RecyclerView不像ListView,它没有提供emptyView的支持,但我们可以自己来实现这个功能。

解决思路

通过监听Adapter中数据的变化,当数据为空时让我们自定义的emptyView为可见的。

方法一

看到一篇文章说可以通过多布局来实现:

private static final int VIEW_TYPE_EMPTY_LIST_PLACEHOLDER = 0;
private static final int VIEW_TYPE_OBJECT_VIEW = 1;
private List<Object> myData;

@Override
public int getItemViewType(int position) {
    if (myData.isEmpty()) {
        return VIEW_TYPE_EMPTY_LIST_PLACEHOLDER;
    } else {
        return VIEW_TYPE_OBJECT_VIEW;
    }
}        

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    switch(viewType) {
        case VIEW_TYPE_EMPTY_LIST_PLACEHOLDER:
            // return view holder for your placeholder
            break;
        case VIEW_TYPE_OBJECT_VIEW:
            // return view holder for your normal list item
            break;
    }
}

但我试了之后,发现只要你的getItemCount返回0,就不会再执行下去了,所以这种方法行不通。

方法二

回想一下,当我们的数据变化时,我们会去调用哪些方法。没错,就是notify*这一系列的方法了。看一下其中notifyItemInserted的实现:

public final void notifyItemInserted(int position) {
    mObservable.notifyItemRangeInserted(position, 1);
}

再继续跟踪下去:

public void notifyItemRangeInserted(int positionStart, int itemCount) {
    // since onItemRangeInserted() is implemented by the app, it could do anything,
    // including removing itself from {@link mObservers} - and that could cause problems if
    // an iterator is used on the ArrayList {@link mObservers}.
    // to avoid such problems, just march thru the list in the reverse order.
    for (int i = mObservers.size() - 1; i >= 0; i--) {
        mObservers.get(i).onItemRangeInserted(positionStart, itemCount);
    }
}

看到这里,已经很明显了,这就是一个观察者模式!

static class AdapterDataObservable extends Observable<AdapterDataObserver> {
    public boolean hasObservers() {
        return !mObservers.isEmpty();
    }

    public void notifyChanged() {
        // since onChanged() is implemented by the app, it could do anything, including
        // removing itself from {@link mObservers} - and that could cause problems if
        // an iterator is used on the ArrayList {@link mObservers}.
        // to avoid such problems, just march thru the list in the reverse order.
        for (int i = mObservers.size() - 1; i >= 0; i--) {
            mObservers.get(i).onChanged();
        }
    }

    public void notifyItemRangeChanged(int positionStart, int itemCount) {
        notifyItemRangeChanged(positionStart, itemCount, null);
    }

    public void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) {
        // since onItemRangeChanged() is implemented by the app, it could do anything, including
        // removing itself from {@link mObservers} - and that could cause problems if
        // an iterator is used on the ArrayList {@link mObservers}.
        // to avoid such problems, just march thru the list in the reverse order.
        for (int i = mObservers.size() - 1; i >= 0; i--) {
            mObservers.get(i).onItemRangeChanged(positionStart, itemCount, payload);
        }
    }

    public void notifyItemRangeInserted(int positionStart, int itemCount) {
        // since onItemRangeInserted() is implemented by the app, it could do anything,
        // including removing itself from {@link mObservers} - and that could cause problems if
        // an iterator is used on the ArrayList {@link mObservers}.
        // to avoid such problems, just march thru the list in the reverse order.
        for (int i = mObservers.size() - 1; i >= 0; i--) {
            mObservers.get(i).onItemRangeInserted(positionStart, itemCount);
        }
    }

    public void notifyItemRangeRemoved(int positionStart, int itemCount) {
        // since onItemRangeRemoved() is implemented by the app, it could do anything, including
        // removing itself from {@link mObservers} - and that could cause problems if
        // an iterator is used on the ArrayList {@link mObservers}.
        // to avoid such problems, just march thru the list in the reverse order.
        for (int i = mObservers.size() - 1; i >= 0; i--) {
            mObservers.get(i).onItemRangeRemoved(positionStart, itemCount);
        }
    }

    public void notifyItemMoved(int fromPosition, int toPosition) {
        for (int i = mObservers.size() - 1; i >= 0; i--) {
            mObservers.get(i).onItemRangeMoved(fromPosition, toPosition, 1);
        }
    }
}

public static abstract class AdapterDataObserver {
    public void onChanged() {
        // Do nothing
    }

    public void onItemRangeChanged(int positionStart, int itemCount) {
        // do nothing
    }

    public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
        // fallback to onItemRangeChanged(positionStart, itemCount) if app
        // does not override this method.
        onItemRangeChanged(positionStart, itemCount);
    }

    public void onItemRangeInserted(int positionStart, int itemCount) {
        // do nothing
    }

    public void onItemRangeRemoved(int positionStart, int itemCount) {
        // do nothing
    }

    public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
        // do nothing
    }
}

RecyclerView内部定义了一个观察者:

private class RecyclerViewDataObserver extends AdapterDataObserver {
    @Override
    public void onChanged() {
        assertNotInLayoutOrScroll(null);
        if (mAdapter.hasStableIds()) {
            // TODO Determine what actually changed.
            // This is more important to implement now since this callback will disable all
            // animations because we cannot rely on positions.
            mState.mStructureChanged = true;
            setDataSetChangedAfterLayout();
        } else {
            mState.mStructureChanged = true;
            setDataSetChangedAfterLayout();
        }
        if (!mAdapterHelper.hasPendingUpdates()) {
            requestLayout();
        }
    }

    @Override
    public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
        assertNotInLayoutOrScroll(null);
        if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
            triggerUpdateProcessor();
        }
    }

    @Override
    public void onItemRangeInserted(int positionStart, int itemCount) {
        assertNotInLayoutOrScroll(null);
        if (mAdapterHelper.onItemRangeInserted(positionStart, itemCount)) {
            triggerUpdateProcessor();
        }
    }

    @Override
    public void onItemRangeRemoved(int positionStart, int itemCount) {
        assertNotInLayoutOrScroll(null);
        if (mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) {
            triggerUpdateProcessor();
        }
    }

    @Override
    public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
        assertNotInLayoutOrScroll(null);
        if (mAdapterHelper.onItemRangeMoved(fromPosition, toPosition, itemCount)) {
            triggerUpdateProcessor();
        }
    }

    void triggerUpdateProcessor() {
        if (mPostUpdatesOnAnimation && mHasFixedSize && mIsAttached) {
            ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
        } else {
            mAdapterUpdateDuringMeasure = true;
            requestLayout();
        }
    }
}

然后当我们调用setAdapter方法的时候,它会调用setAdapterInternal方法,里面有这样的一个片段:

private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious,
        boolean removeAndRecycleViews) {
    if (mAdapter != null) {
        mAdapter.unregisterAdapterDataObserver(mObserver);
        mAdapter.onDetachedFromRecyclerView(this);
    }
    ...
    ...
    if (adapter != null) {
        adapter.registerAdapterDataObserver(mObserver);
        adapter.onAttachedToRecyclerView(this);
    } 
}

在这里注册后,当数据变化的时候RecyclerViewDataObserver就会收到相关的信息,从而完成相应的工作。

所以我们要做的就是继承AdapterDataObserver,然后重写相应的方法,在里面判断数据是否为空,为空则显示emptyView,然后让我们自定义的Adapter去注册它,这样就能实现emptyView了。

方法三

其实,方法三跟方法二是一样的,如果你觉得每写一个adapter都要注册一个观察者太麻烦的话,可以自定义一个RecyclerView,然后把这部分工作放到里面去。具体可以看看别人的写法,也是一样的原理。点我点我

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值