不像 ListView,RecyclerView 是没有提供 setEmptyView() 方法的,为了开发方便,就实现了一个。
ListView.setEmptyView() 的原理是什么?
在实现 RecyclerView.setEmptyView() 之前,先看一下 ListView.setEmptyView() 的原理是什么。
从 ListView.setEmptyView() 方法看起,下面是方法的实现
/**
* Sets the view to show if the adapter is empty
*/
@android.view.RemotableViewMethod
public void setEmptyView(View emptyView) {
mEmptyView = emptyView;
// If not explicitly specified this view is important for accessibility.
if (emptyView != null
&& emptyView.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
emptyView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
}
final T adapter = getAdapter();
final boolean empty = ((adapter == null) || adapter.isEmpty());
updateEmptyStatus(empty);
}
注意到方法体的最后两句
final boolean empty = ((adapter == null) || adapter.isEmpty());
updateEmptyStatus(empty);
这两句代码的作用是判断 Adapter 和数据源是否为空,然后以此做相应的操作。是看一下 updateEmptyStatus(empty) 的具体实现
/**
* Update the status of the list based on the empty parameter. If empty is true and
* we have an empty view, display it. In all the other cases, make sure that the listview
* is VISIBLE and that the empty view is GONE (if it's not null).
*/
private void updateEmptyStatus(boolean empty) {
if (isInFilterMode()) {
empty = false;
}
if (empty) {
if (mEmptyView != null) {
mEmptyView.setVisibility(View.VISIBLE);
setVisibility(View.GONE);
} else {
// If the caller just removed our empty view, make sure the list view is visible
setVisibility(View.VISIBLE);
}
// We are now GONE, so pending layouts will not be dispatched.
// Force one here to make sure that the state of the list matches
// the state of the adapter.
if (mDataChanged) {
this.onLayout(false, mLeft, mTop, mRight, mBottom);
}
} else {
if (mEmptyView != null) mEmptyView.setVisibility(View.GONE);
setVisibility(View.VISIBLE);
}
}
可以看到,如果数据为空,就把 EmptyView 显示,ListView 本身隐藏起来。
在看一下 updateEmptyStatus(boolean empty) 有再哪里调用,发现会在 checkFocus() 方法里调用,而这个 checkFocus() 方法会在 AdapterDataSetObserver 里调用到
class AdapterDataSetObserver extends DataSetObserver {
private Parcelable mInstanceState = null;
@Override
public void onChanged() {
mDataChanged = true;
mOldItemCount = mItemCount;
mItemCount = getAdapter().getCount();
// Detect the case where a cursor that was previously invalidated has
// been repopulated with new data.
if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
&& mOldItemCount == 0 && mItemCount > 0) {
AdapterView.this.onRestoreInstanceState(mInstanceState);
mInstanceState = null;
} else {
rememberSyncState();
}
checkFocus();
requestLayout();
}
@Override
public void onInvalidated() {
mDataChanged = true;
if (AdapterView.this.getAdapter().hasStableIds()) {
// Remember the current state for the case where our hosting activity is being
// stopped and later restarted
mInstanceState = AdapterView.this.onSaveInstanceState();
}
// Data is invalid so we should reset our state
mOldItemCount = mItemCount;
mItemCount = 0;
mSelectedPosition = INVALID_POSITION;
mSelectedRowId = INVALID_ROW_ID;
mNextSelectedPosition = INVALID_POSITION;
mNextSelectedRowId = INVALID_ROW_ID;
mNeedSync = false;
checkFocus();
requestLayout();
}
public void clearSavedState() {
mInstanceState = null;
}
}
从这个类来看说明数据变化的时候,会通知 AdapterDataSetObserver 调用响应的方法。
在 BaseAdapter 中就存在 DataSetObserver,在调用 notifyDataSetChanged() 就会调用 mDataSetObservable.notifyChanged();
public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
private final DataSetObservable mDataSetObservable = new DataSetObservable();
public boolean hasStableIds() {
return false;
}
public void registerDataSetObserver(DataSetObserver observer) {
mDataSetObservable.registerObserver(observer);
}
public void unregisterDataSetObserver(DataSetObserver observer) {
mDataSetObservable.unregisterObserver(observer);
}
/**
* Notifies the attached observers that the underlying data has been changed
* and any View reflecting the data set should refresh itself.
*/
public void notifyDataSetChanged() {
mDataSetObservable.notifyChanged();
}
// 其他代码省略
总结一下:
Adapter 通过 DataSetObservable 的回调方法通知 ListView 显示或隐藏 EmptyView
RecyclerView.setEmptyView() 的原理
RecyclerView 内部有一个类:AdapterDataObserver,从代码注释来看
Observer base class for watching changes to an {@link Adapter}. See {@link Adapter#registerAdapterDataObserver(AdapterDataObserver)}.
AdapterDataObserver 是用来监听 Adapter 的数据源变化的,其实就是一个观察者模式的实现。
这个类提供了几个方法
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
}
}
和数据变化有关的方法有几个:onChanged()、onItemRangeInserted() 和 onItemRangeRemoved()。在这三个方法里把 EmptyView 显示出来就可以实现 setEmptyView() 的特性。
代码实现
import android.content.Context;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.View;
public class EmptyRecyclerView extends RecyclerView {
private View mEmptyView;
public EmptyRecyclerView(Context context) {
super(context);
}
public EmptyRecyclerView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public EmptyRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void setEmptyView(View emptyView) {
mEmptyView = emptyView;
}
private AdapterDataObserver mObserver = new AdapterDataObserver() {
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
checkIfEmpty();
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
checkIfEmpty();
}
@Override
public void onChanged() {
checkIfEmpty();
}
};
@Override
public void setAdapter(Adapter adapter) {
super.setAdapter(adapter);
adapter.registerAdapterDataObserver(mObserver);
checkIfEmpty();
}
private void checkIfEmpty() {
if (mEmptyView != null && getAdapter() != null) {
boolean emptyViewVisible = getAdapter().getItemCount() == 0;
// 显示为空视图
mEmptyView.setVisibility(emptyViewVisible ? View.VISIBLE : View.GONE);
// RecyclerView本身隐藏
this.setVisibility(emptyViewVisible ? View.GONE : View.VISIBLE);
}
}
}
使用
布局文件。添加了两个按钮、一个 EmptyView 和一个 EmptyRecyclerView
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_recycler_view_empty_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.okada.RecyclerViewEmptyViewActivity">
<LinearLayout
android:layout_width="match_parent"
android:orientation="horizontal"
android:layout_height="wrap_content">
<Button
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:id="@+id/btn_clearDataSource"
android:text="clear dataSource"/>
<Button
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:id="@+id/btn_setDataSource"
android:text="set dataSource"/>
</LinearLayout>
<TextView
android:id="@+id/tv_empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="啥也没有"
android:textSize="18sp"
android:visibility="gone"/>
<com.okada.EmptyRecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
然后就是使用了
public class RecyclerViewEmptyViewActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_recycler_view_empty_view);
final MyAdapter myAdapter = new MyAdapter(generateDataSource());
EmptyRecyclerView recyclerView = (EmptyRecyclerView) findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setEmptyView(findViewById(R.id.tv_empty));
recyclerView.setAdapter(myAdapter);
findViewById(R.id.btn_clearDataSource).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
myAdapter.clearDataSource();
}
});
findViewById(R.id.btn_setDataSource).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
myAdapter.setDataSource(generateDataSource());
}
});
}
private List<Integer> generateDataSource() {
List<Integer> dataSource= new ArrayList<>(20);
for (int i = 0; i < 20; i++) {
dataSource.add(i);
}
return dataSource;
}
}