爲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,然後把這部分工作放到裏面去。具體可以看看別人的寫法,也是一樣的原理。點我點我

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章