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

本文探讨了在Android中使用ListView或GridView时,setEmptyView方法为何可能无效,从源码角度分析了原因,并提出了两种解决方法:1. 将Empty View与ListView在同一布局文件中;2. 对于复杂或复用的Empty View,将其放在单独布局文件并正确添加到View hierarchy。同时,文章提到了第三方库如PullToRefresh的解决策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

当我们使用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) {
    		//这里把emptyView赋值到成员变量mEmptyView里
        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添加了进去。


    参考http://stormzhang.com/android/2014/05/11/adapterview-setemptyview/













    评论 1
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

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

    抵扣说明:

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

    余额充值