您的位置:首页 > 其它

从源码上分析ListView/GridView调用setEmptyView不起作用的原因及解决办法

2017-06-29 09:32 429 查看
当我们使用ListView或GridView的时候,当列表为空的时候,我们往往需要一个Loading或者一段提示文字又或者一个特殊的View来提示用户操作,这个时候就用到了setEmptyView()方法。

setEmptyView()其实是AdapterView的方法,而我们开发中常用到的ListView, GridView, ExpandableListView等都是继承于AdapterView的,所以可以直接调用这个方法。

但是问题来了,当你这个emptyview不在当前的View hierarchy上,那么你直接调用setEmptyView(emptyview)是不会有任何效果的,为什么呢?请看源码:

/**
* 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);
}
由上面看到,setEmptyView只是把emptyView赋值到成员变量mEmptyView里,并判断adpater是否为空,进而调用updateEmptyStatus(empty);更新视图,下面再看看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);
}
}
这里大概的逻辑就是判断如果adapter为空,则把mEmptyView的visible属性改为显示,否则把listview显示。从这里看出来,setemptyview不会把emptyviw add到当前的view hierarchy上,而当前界面只是显示当前的view
hierarchy的,所以如果这个emptyview不在当前的View hierarchy上,那么你直接调用setEmptyView(emptyview)是不会有任何效果的。

问题根源我们找到了,那么我们该怎么解决呢?那就是把这个emptyview加入到当前view hierarchy上咯,对此有两种方法可以实现:


1. Empty View和ListView在同一个布局文件里

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/list_view" />

<TextView
android:id="@+id/tv_empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Loading data..." />

</FrameLayout>

ListView listView = (ListView)findViewById(R.id.list_view);
listView.setEmptyView(findViewById(R.id.tv_empty));


2. Empty View在单独的布局文件里,这种一般适用于比较复杂的View或者打算在多个地方复用


setEmptyView()这个方法是有限制的,这个View必须在当前的View hierarchy的节点上,所以当我们写在外面单独的布局文件里时,需要把View添加到当前的View hierarchy的节点上。所以就需要下面的用法:

View emptyView = View.inflate(R.layout.empty_view, null);
((ViewGroup)list.getParent()).addView(emptyView);
ListView listView = (ListView)findViewById(R.id.list_view);
listView.setEmptyView(emptyView);


有些同学可能说,不对啊,我调用的setemptyview不用要求这个View必须在当前的View hierarchy的节点上啊,为什么我的运行好好的?

答:那你一定是使用了第三方的控件,它里面重写了setemptyview。比如著名的PullToRefresh 中的PullToRefreshAdapterViewBase的setemptyView实现如下:
public final void setEmptyView(View newEmptyView) {
FrameLayout refreshableViewWrapper = getRefreshableViewWrapper();

if (null != newEmptyView) {
// New view needs to be clickable so that Android recognizes it as a
// target for Touch Events
newEmptyView.setClickable(true);

ViewParent newEmptyViewParent = newEmptyView.getParent();
if (null != newEmptyViewParent && newEmptyViewParent instanceof ViewGroup) {
((ViewGroup) newEmptyViewParent).removeView(newEmptyView);
}

// We need to convert any LayoutParams so that it works in our
// FrameLayout
FrameLayout.LayoutParams lp = convertEmptyViewLayoutParams(newEmptyView.getLayoutParams());
if (null != lp) {
refreshableViewWrapper.addView(newEmptyView, lp);
} else {
refreshableViewWrapper.addView(newEmptyView);
}
}

if (mRefreshableView instanceof EmptyViewMethodAccessor) {
((EmptyViewMethodAccessor) mRefreshableView).setEmptyViewInternal(newEmptyView);
} else {
mRefreshableView.setEmptyView(newEmptyView);
}
mEmptyView = newEmptyView;
}

注意refreshableViewWrapper.addView(。。)就把emptyview添加了进去。

/**
*  setEmptyView()方法需要把空View放在当前layout中,才起作用;
*  而setEmptyLayout()方法则不需要(自己添加的方法)
* @param emptyView
*/
public void setEmptyLayout(View emptyView) {
this.mEmptyView = emptyView;
ViewParent parent = this.getParent();
if (parent != null && parent instanceof ViewGroup) {
ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
((ViewGroup) parent).addView(mEmptyView, layoutParams);
}
mDataObserver.onChanged();
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐