Android ListView使用问题

Android中ListView用来处理View数组的显示,我们这里不介绍ListView的用法,而是介绍一下我在使用ListView时遇到的问题。对ListView的使用方法尚存疑问的同学,可以去搜索一下ListView的用法。

Header/Footer的添加和删除

在4.3(API-18)及以前的ListView中对Header的添加有限制,如果你在setAdapter(参数非空)之前没有调用addHeaderView,那么在setAdapter(参数非空)之后调用就会得到一个异常:

IllegalStateException Cannot add header view to list -- setAdapter has already been called."

让我们看一下该问题的原因。
首先看一下addHeaderView方法的源码:

public void addHeaderView(View v, Object data, boolean isSelectable) {
        if (mAdapter != null && ! (mAdapter instanceof HeaderViewListAdapter)) {
            throw new IllegalStateException(
                    "Cannot add header view to list -- setAdapter has already been called.");
        }
        //...
    }

明了了,如果有Adapter,那么这个Adapter必须是HeaderViewListAdapter才能正常的添加Header。那么,这个HeaderViewListAdapter又是怎么来的呢?继续看源码会发现,这个HeaderViewListAdapter是在setAdapter中创建的:

public void setAdapter(ListAdapter adapter) {
        //...

        if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
            mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
        } else {
            mAdapter = adapter;
        }
        //...
    }

很明显,创建HeaderViewListAdapter的前提是要有表头或者表尾数据。
相对的,添加Footer就没有这么多限制了,官方还特意在addFooterView方法上加了一段说明:

// NOTE: do not enforce the adapter being null here, since unlike in
// addHeaderView, it was never enforced here, and so existing apps are
// relying on being able to add a footer and then calling setAdapter to
// force creation of the HeaderViewListAdapter wrapper

在下学识浅薄不敢妄自翻译,敬请读者自己揣摩。
当一个ListView仅仅添加了Header和Footer(内容正在等待或没有),为了将Header和Footer显示出来,仍然要调用一下setAdapter,即使是使用null作为参数,也要执行一下,因为addHeaderView和addFooterView中,在添加了数据之后都有这样的代码:

if (mAdapter != null && mDataSetObserver != null) {
    mDataSetObserver.onChanged();
}

幸运的是,Android4.4上已经对上述问题做了修改,现在的addHeaderView不再抛出异常了,让我们看一下源码:

public void addHeaderView(View v, Object data, boolean isSelectable) {
    //...

    // Wrap the adapter if it wasn't already wrapped.
    if (mAdapter != null) {
        if (!(mAdapter instanceof HeaderViewListAdapter)) {
        mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, mAdapter);
        }

        // In the case of re-adding a header view, or adding one later on,
        // we need to notify the observer.
        if (mDataSetObserver != null) {
        mDataSetObserver.onChanged();
        }
    }
}

也就是说,在Android4.4上可以随时添加Header而不用关心有没有调用setAdapter了,但是仍然需要调用setAdapter以便Header能够显示出来,因为mDataSetObserver是在setAdapter方法中创建的。
addFooterView方法也修改成和addHeaderView相似的了,之前版本上的那个好心的NOTE已经被删掉了。
不过,ListView对Header/Footer的支持到底有限,比如如果我想删除Header/Footer,我必须调用这两个方法:

public boolean removeHeaderView(View v) 
public boolean removeFooterView(View v)

也就是说,我必须知道View的引用,那么如果想用这个功能,就必须自己维护一个Header/Footer的实时镜像引用列表,可谓复杂。如果有下面这样的方法,相信使用起来会方便的多。

public boolean removeHeaderView(int index) 
public boolean removeFooterView(int index)

点击事件

点击事件包括单击事件和长按事件。
ListView有两个设置事件的方法:

public void android.widget.AdapterView.setOnItemClickListener(OnItemClickListener listener)
public void android.widget.AdapterView.setOnItemLongClickListener(OnItemLongClickListener listener)

这两个方法都是定义在AdapterView类中的,理所当然的,这个方法回调不会区分Header/footer以及ListAdapter中的View,所以当回调到OnItemClickListener的如下方法时

public void onItemClick(AdapterView<?> parent, View view, int position, long id)

方法参数position指示的顺序是这样的:
Header:[0 ~ HeaderLength-1]
Content:[HeaderLength ~ HeaderLengh+AdapterLength-1]
Footer:[HeaderLength+AdapterLength ~ End]
这样的方法回调对于复杂的界面(包含可变数目的Header,Footer等)来说,不啻是个噩梦。可惜的是ListView没有提供如下这样的回调方法:

public void setOnHeaderItemClickListener(OnItemClickListener listener)
public void setOnHeaderItemLongClickListener(OnItemLongClickListener listener)
public void setOnFooterItemClickListener(OnItemClickListener listener)
public void setOnFooterItemLongClickListener(OnItemLongClickListener listener)
public void setOnContentItemClickListener(OnItemClickListener listener)
public void setOnContentItemLongClickListener(OnItemLongClickListener listener)

ListView 中,你不能单独设置某个项目支持LongClick或不支持,如果你调用了setOnItemLongClickListener,那么所有的项目都将会有长按事件回调,如果不想处理某些项目的长按事件,就必须自己写些代码了。

EmptyView

EmptyView,顾名思义,就是在ListView为空的时候显示的View。理论是,他可以是任何View,不过一般情况下,EmptyView和ListView在View树上是兄弟View。
但是,如果你在一个ListView中添加了Header和EmptyView,就会出现bug:当你设置的Adapter中没有内容的时候,EmptyView显示,但是Header却消失了。
跟进去查看EmptyView的显示逻辑是这样的:

updateEmptyStatus((adapter == null) || adapter.isEmpty());
private void updateEmptyStatus(boolean empty)

updateEmptyStatus接收一个boolean参数指示当前List是否为空,但是在adapter.isEmpty()上出了点差错,我们知道,带有Header的ListView的Adapter是HeaderViewListAdapter,然而看到该类的isEmpty方法是这么实现的:

    public boolean isEmpty() {
        return mAdapter == null || mAdapter.isEmpty();
    }

这里的mAdapter是我们调用ListView.setAdapter设置的。
好了,原因出来了,因为这里没有考虑Header和Footer的数目,直接就给ListView判断成空的了!而EmptyView显示的时候AdapterView会把自己设置为GONE,于是,EmptyView和Header/Footer就不能共存了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值