解决ListView列表setEmptyView无效问题及源码解析

很多童鞋们认为ListView的setEmptyView设置空界面无效的。通常在网上查了查,然后来按照网谁上的做法直接复制粘贴一下。效果出来就OK了。身为一个开发者,我们既要知其然,也要知道其所以然。

目前我们大部分是这样做的:

View view;
 inflater = LayoutInflater.from(this);
 view =inflater.inflate(R.layout.empty_nodata,null);
 ((ViewGroup)llr_recycleView_queryist.getParent()).addView(emptyView);
 lr_recycleView_query.setEmptyView(emptyView);

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

so问题来了。

为什么一定要加上

((ViewGroup)llr_recycleView_queryist.getParent()).addView(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);
    }

从上面可以看出当emptyView部位空时,先通过updateEmptyStatus(empty);进行更新当前的View。

在setEmptyView方法中将传过来的emptyView赋值给全局的mEmptyView。在其他地方也可以对其进行处理。

接下来是看一下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);
        }
    }

updateEmptyStatus源码很简单,根据empty这个状态值进行设定mEmptyView 是否是显示。
如果adapter部位空,则执行:

if (mEmptyView != null) mEmptyView.setVisibility(View.GONE);
 setVisibility(View.VISIBLE);

为空时的时候:

 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);
            }

源码中仅仅只是对mEmptyView进行了隐藏和显示。并未对其进行说明。这就表明了,
mEmptyView和当前的list或者是GridView及其其他View是同一级的关系。同时也说明了为什么需要用到((ViewGroup)llr_recycleView_queryist.getParent()).addView(emptyView); 才能起到效果。

这也可能是Android源码时的一个不足之处。针对于Android上的有些老早的老司机早就想好了对策。比如说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;
    }

这既是为什么有些时候不做处理也可以有效果。

对EmptyView的封装
**
 * 功能: 列表空界面显示
 * 用于listView ,GridView ,RecycleView 的setEmpety方法
 * @author yuyahao
 * 备注: 其他童鞋们可对其进行拓展 eg: 添加监听,切换布局
 */
public class EmptyView extends LinearLayout {
    private TextView  tv_no_message;
    private ImageView iv_empety_mageger;
    private LinearLayout ll_no_manager;
    private LinearLayout ll_no_message_root;
    public EmptyView(Context context) {
        super(context);
        init();
        // TODO Auto-generated constructor stub
    }

    public EmptyView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        // TODO Auto-generated method stub
        LayoutInflater inflater = LayoutInflater.from(getContext());
        View view = inflater.inflate(R.layout.empty_view, null);
        iv_empety_mageger = (ImageView) view.findViewById(R.id.iv_empety_mageger);
        tv_no_message = (TextView) view.findViewById(R.id.tv_no_message);
        ll_no_manager = (LinearLayout) view.findViewById(R.id.ll_no_manager);
        ll_no_message_root = (LinearLayout) view.findViewById(R.id.ll_no_message_root);
        addView(view);

    }

    /**
     * 一定要调用该方法
     * @param
     */
    public View mustCallInitWay(View view){
        if(view != null){
            ViewGroup.LayoutParams  params = ll_no_message_root.getLayoutParams();
            params.width = DensityUtil.getScreenIntWidth(MyApplication.getContext());
            params.height = DensityUtil.getScreenIntHeight(MyApplication.getContext()) - DensityUtil.sp2px(MyApplication.getContext(),50);
            ((ViewGroup)view.getParent()).addView(this,params);
        }
        return this;
    }

    public void setNoMessageText(CharSequence text) {
        tv_no_message.setText(text);
        tv_no_message.setVisibility(View.VISIBLE);
    }
    /********修改文字的颜色**********/
    public void setMessageTextColor2(int colorResId) {
        tv_no_message.setTextColor(colorResId);
        tv_no_message.setVisibility(View.VISIBLE);
    }
    /**
     * 显示不同的文字提示及其图片提示
     */
    public View setMyManager(String showNoTip,int showNoIamgeViewResId,int textSize,int colorResId){
        iv_empety_mageger.setImageResource(showNoIamgeViewResId);
        tv_no_message.setTextColor(colorResId);
        tv_no_message.setText(showNoTip);
        tv_no_message.setTextSize(textSize);
        tv_no_message.setVisibility(View.VISIBLE);
        return this;
    }
    /**
     * 显示不同的文字提示及其图片提示
     */
    public View setMyManager(String showNoTip,int showNoIamgeViewResId){
        iv_empety_mageger.setImageResource(showNoIamgeViewResId);
        tv_no_message.setText(showNoTip);
        tv_no_message.setVisibility(View.VISIBLE);
        return this;
    }
    /**
     * 显示不同的文字提示及其图片提示
     */
    public View setMyManager(int showNoIamgeViewResId){
        iv_empety_mageger.setImageResource(showNoIamgeViewResId);
        iv_empety_mageger.setVisibility(View.VISIBLE);
        return this;
    }

    /**
     * 显示不同的文字提示
     */
    public View setMyManager(String showNoTip){
        tv_no_message.setText(showNoTip);
        tv_no_message.setVisibility(View.VISIBLE);
        return this;
    }
    /**
     * 是否显示文字
     */
    public View isShowTextTipMassager(boolean isShow){
        if(isShow){
            tv_no_message.setVisibility(View.VISIBLE);
        }
        return this;
    }
    /**
     * 是否显示文字
     */
    public View isShowTextTipMassager(boolean isShow,String showNoTip){
        if(isShow){
            tv_no_message.setVisibility(View.VISIBLE);
            tv_no_message.setText(showNoTip);
        }else{
            tv_no_message.setVisibility(View.GONE);
            tv_no_message.setText(showNoTip);
        }
        return this;
    }
    /**
     * 是否显示图片
     */
    public View isShowIamgeMassager(boolean isShow,int showNoIamgeViewResId){
        if(isShow){
            iv_empety_mageger.setVisibility(View.VISIBLE);
            iv_empety_mageger.setImageResource(showNoIamgeViewResId);
        }else{
            iv_empety_mageger.setVisibility(View.GONE);
        }
        return this;
    }
    /**
     * 是否显示图片
     */
    public View isShowIamgeMassager(boolean isShow){
        if(isShow){
            iv_empety_mageger.setVisibility(View.VISIBLE);
        }else{
            iv_empety_mageger.setVisibility(View.GONE);
        }
        return this;
    }
}

如何调用:

 EmptyView emptyView = new EmptyView(this);
 emptyView.mustCallInitWay(lr_recycleView_query);
 lr_recycleView_query.setEmptyView(emptyView);

注意:如果你的子布局中有剁成父布局进行嵌套,这个时候

((ViewGroup)view.getParent()).addView(this);

这个方法是无效的。addView的时候布局中的大小match_parent是没有效果的,它总是以包裹内容进行填充的。

显示效果:

Markdown

这个时候一定要调用

((ViewGroup)view.getParent()).addView(this,params);

对整体布局用代码进行设置大小才起到效果。
调用addView(this,params)之后正确显示:

Markdown

这里的params是最外层的布局大小:

 ViewGroup.LayoutParams  params = ll_no_message_root.getLayoutParams();
params.width = DensityUtil.getScreenIntWidth(MyApplication.getContext());
params.height = DensityUtil.getScreenIntHeight(MyApplication.getContext()) 
                - DensityUtil.sp2px(MyApplication.getContext(),50);
((ViewGroup)view.getParent()).addView(this,params);

该工具类封装好之后可用于ListView,GridView,RecycleView,PullToRefreshView及其自定义相关的组件的应用。直接调用其方法setEmptyView()即可。

曾经踩过的坑:

addView调用之后导致子布局的android:layout_width=”match_parent”属性或者 android:layout_width=”fll_parent”

((ViewGroup)view.getParent()).addView(params);

属性无效。

我们先看源码addView(View view):

public void addView(View child) {  
    addView(child, -1);  
}  

public void addView(View child, int index) {  
    LayoutParams params = child.getLayoutParams();  
    if (params == null) {  
        params = generateDefaultLayoutParams();  
        if (params == null) {  
            throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");  
        }  
    }  
    addView(child, index, params);  
}  

从源码中可以知道,如果不传入 LayoutParams.params的话,就会有一个默认的params。

那么再看看源码:

/**  
     * Returns a set of default layout parameters. These parameters are requested  
     * when the View passed to {@link #addView(View)} has no layout parameters  
     * already set. If null is returned, an exception is thrown from addView.  
     *  
     * @return a set of default layout parameters or null  
     */  
    protected LayoutParams generateDefaultLayoutParams() {  
        return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);  
    }  

到这里我们就可以清楚的看到:

如果一个View没有set LayoutParams,在该View被添加到一个ViewGroup里时,ViewGroup会为该View创建一个默认的LayoutParams。所以如果题中的view已经存在于一个ViewGroup中,view.getLayoutParams()便会得到ViewGroup为其创建的默认LayoutParams。而这个默认LayoutParams会因ViewGroup而变,而这里的ViewGroup是指得当前的LinearLayout。LayoutParams是LayoutParams.WRAP_CONTENT将会是子布局的包裹内容。

因此如果只调用addView(View v)不设置params参数将会无效。

SwipeRefreshLayout和ListView的EmptyView共存冲突的问题

最好的方式是: 将ListView和EmptyView分离,让他们两个分别被两个SwipeRefreshLayout包裹

参考链接:http://gundumw100.iteye.com/blog/2177595

相信自己,没有做不到的,只有想不到的

如果你觉得此文对您有所帮助,欢迎入群 QQ交流群 :232203809
微信公众号:终端研发部

Markdown

(欢迎关注学习和交流)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

androidstarjack

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值