ListView想要添加headerview的话,就要通过addHeaderView这个方法,然后想要为ListView设置数据的话,就要调用setAdapter方法了。但是,在调用addHeaderView和setAdapter的顺序上,有时候会报这样的一个异常:
java.lang.IllegalStateException: Cannot add header view to list -- setAdapter has already been called.
产生原因:addHeaderView()调用在setAdapter()之后,并且该代码运行在Android4.3之前的系统版本。就是因为我们在addHeaderView之前调用了setAdapter。
比如下面的代码:
mDataHelper = new RepliesDataHelper(); mAdapter = new RepliesAdapter(this); mListView.setAdapter(mAdapter); mListView.setOnScrollListener(new PauseOnScrollListener(ImageLoader.getInstance(), true, true)); if (getTopicData()) { initTopicData(); getLoaderManager().initLoader(4, null, this); getRepliesData(); } else { RequestManager.getInstance().getTopic(this, mTopicId, new Response.Listener<Topic>() { @Override public void onResponse(Topic response) { initTopicData(); getLoaderManager().initLoader(4, null, TopicActivity.this); getRepliesData(); } }, new SimpleErrorListener(this)); }
在低版本 Android 系统会报以上的错。改成下面这样就没事了:
mDataHelper = new RepliesDataHelper(); mAdapter = new RepliesAdapter(this); if (getTopicData()) { initTopicData(); getLoaderManager().initLoader(4, null, this); getRepliesData(); } else { RequestManager.getInstance().getTopic(this, mTopicId, new Response.Listener<Topic>() { @Override public void onResponse(Topic response) { initTopicData(); getLoaderManager().initLoader(4, null, TopicActivity.this); getRepliesData(); } }, new SimpleErrorListener(this)); } mListView.setAdapter(mAdapter); mListView.setOnScrollListener(new PauseOnScrollListener(ImageLoader.getInstance(), true, true));
initTopicData(); 里包含了 addHeaderView(headerView, null, false); 所以把它放在 setAdapter 之前,错误就不会发生。
建议setAdapter需要在addHeaderView和addfooterView之后调用。这样就安全了。下面,我们来看看源码吧,看看究竟是什么原因造成的。下面看看 addHeaderView 在不同版本的代码。
Android-17(4.2)的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."); } FixedViewInfo info = new FixedViewInfo(); info.view = v; info.data = data; info.isSelectable = isSelectable; mHeaderViewInfos.add(info); // in the case of re-adding a header view, or adding one later on, // we need to notify the observer if (mAdapter != null && mDataSetObserver != null) { mDataSetObserver.onChanged(); } }
Android-18(4.3)的addHeaderView源码:
public void addHeaderView(View v, Object data, boolean isSelectable) { final FixedViewInfo info = new FixedViewInfo(); info.view = v; info.data = data; info.isSelectable = isSelectable; mHeaderViewInfos.add(info); // 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(); } } }
在上面,我们可以对比出代码中的处理的不同。在17版本中,只要adapter不为空的话,那就直接会抛出异常,而这个异常恰好就是我们文章开头说到的异常。在18版本中,如果adapter不为空的话,则会新建一个adapter,这个adapter会包含了headerview和footerview以及我们传进来的原来的adapter。这是在18版本以后做的一个处理。虽然有了处理,但是建议大家还是按照上面说的调用顺序来使用addHeaderView,addFooterView和setAdapter吧。
现代魔法 推荐于 2013-02-27 10:23