Android兼容性问题 -- ListView.addHeaderView()异常

ListView中添加headerView

在ListView中可以通过addHeaderView()来添加一个View,固定显示在ListView所有item的前面。
但如果此方法调用不当,就可能会产生异常。

例如如下这段代码

UserAdapter adapter = new UserAdapter();
listView.setAdapter(adapter);

View headerView = View.inflate(this, R.layout.list_header, null);
listView.addHeaderView(headerView);

在运行时可能会抛出如下异常。
java.lang.IllegalStateException: Cannot add header view to list – setAdapter has already been called.

问题分析

从异常信息上就可以很容易知道异常的原因,也就是不能再setAdapter()之后再添加header view。
此问题是ListView中的一个设计缺陷,只在Android4.4以下版本中出现,以下是Android4.4和Android4.0版本ListView部分源码的比较。

这里写图片描述
可以看到在android4.0版本中,addHeaderView()一开始就会判断是否已经设置了adapter,如果已经设置了adapter且当前adapter不是HeaderViewListAdapter,则会抛出此异常。而在android4.4版本中去掉了这个判断,注意两个版本注释部分的变化。
此外,在android2.3版本中,addHeaderView()判断条件和android4.0版本又有所不同,在android2.3版本中只判断是否已经设置了adapter,只要设置了adapter,不管该adapter是否是HeaderViewListAdapter都会抛出异常。也就是说在android2.3版本中,以下顺序的调用也会抛出异常。但是在Android4.0及以上版本中,是允许的,且添加的两个header view都是可以正常显示的。

addHeaderView()  -->  setAdapter() --> addHeaderView()

解决方法

解决此问题只需要将setAdapter()和addHeaderView()执行顺序对换一下即可

View headerView = View.inflate(this, R.layout.list_header, null);
listView.addHeaderView(headerView);

UserAdapter adapter = new UserAdapter();
listView.setAdapter(adapter);

然而,有时我们确实需要在ListView显示之后,再根据一些外部输入或额外的条件来决定是否显示以及如何显示header view。在Android4.4及之后的版本中可以直接在需要显示header view的时候执行addHeaderView(),但是如果需要兼容Android4.4之前的版本,只有在setApapter()之前先添加一个不可见的header view,然后在需要显示的时候再将其设置为可见,通过这种方式变相的达到我们的要求。要想添加一个不可见的header view,可以通过设置header view padding的方式将其隐藏。示例代码如下。

View headerView = View.inflate(this, R.layout.list_header, null);
listView.addHeaderView(headerView);

headerView.measure(0, 0);
int height = headerView.getMeasuredHeight();
headerView.setPadding(0, -height, 0, 0);

UserAdapter adapter = new UserAdapter();
listView.setAdapter(adapter);

当需要显示该header view时只需要再次调用setPadding(),将padding设置为0即可。

headerView.setPadding(0, 0, 0, 0);

注意:不能通过headerView.setVisibility(View.GONE);的方式来因此header view,因为ListView在layoutChildren的时候并不会判断child的可见性,将header view的可见性设置为GONE,并不能隐藏header view,而是在ListView中留下一段空白。

关于footer view

前面描述的都是header view,那么对footer view是否也和header view一样呢?
以下是Android4.4和Android4.0版本ListView addFooterView部分源码的比较。

这里写图片描述

可以看到在Android4.0版本中并没有判断adapter是否已设置,也没有抛出异常。
因此,addFooterView()和addHeaderView()并不一样,对android4.4以下版本,addFooterView()在setAdapter()之后执行并不会有异常产生,然后这并不是说addFooterView()可以在setAdapter()之后执行。
对比源码可以看到在Android4.4中多出如下一段代码

if (!(mAdapter instanceof HeaderViewListAdapter)) {
    mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, mAdapter);
}

也就是说,如果在addFooterView()之前已经设置了adapter,且adapter不是HeaderViewListAdapter,在Android4.4会用HeaderViewListAdapter重新包装原先的adapter,而在之前的版本中没有这样做。这就导致在Android4.4以前的系统中,在setAdapter()之后执行addFooterView()通常是没有任何效果的。然而有一种情况例外,那就是如果在addFooterView()的时候,adapter就已经是HeaderViewListAdapter,那么添加的footer view是可以正常显示的。正是这个例外可以让我们在Android4.4以前的系统中,在任意需要的时候执行addFooterView(),而不用担心是否能够正常显示。
具体方法就是在setAdapter()之前先添加一个不可见的header view,然后就可以在任意时候调用addFooterView()来添加footer view了。

View headerView = View.inflate(this, R.layout.list_header, null);
listView.addHeaderView(headerView);

headerView.measure(0, 0);
int height = headerView.getMeasuredHeight();
headerView.setPadding(0, -height, 0, 0);

UserAdapter adapter = new UserAdapter();
listView.setAdapter(adapter);
......
View footerView = View.inflate(this, R.layout.list_footer, null);
listView.addFooterView(footerView);
已标记关键词 清除标记
本人新手,望解答 谢谢 10-13 19:53:37.732: E/AndroidRuntime(12285): FATAL EXCEPTION: main 10-13 19:53:37.732: E/AndroidRuntime(12285): Process: com.example.mydemo, PID: 12285 10-13 19:53:37.732: E/AndroidRuntime(12285): java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.ListView.setAdapter(android.widget.ListAdapter)' on a null object reference 10-13 19:53:37.732: E/AndroidRuntime(12285): at com.example.mydemo.SubFragment1$MyTask.onPostExecute(SubFragment1.java:131) 10-13 19:53:37.732: E/AndroidRuntime(12285): at com.example.mydemo.SubFragment1$MyTask.onPostExecute(SubFragment1.java:1) 10-13 19:53:37.732: E/AndroidRuntime(12285): at android.os.AsyncTask.finish(AsyncTask.java:632) 10-13 19:53:37.732: E/AndroidRuntime(12285): at android.os.AsyncTask.access$600(AsyncTask.java:177) 10-13 19:53:37.732: E/AndroidRuntime(12285): at android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:645) 10-13 19:53:37.732: E/AndroidRuntime(12285): at android.os.Handler.dispatchMessage(Handler.java:102) 10-13 19:53:37.732: E/AndroidRuntime(12285): at android.os.Looper.loop(Looper.java:155) 10-13 19:53:37.732: E/AndroidRuntime(12285): at android.app.ActivityThread.main(ActivityThread.java:5696) 10-13 19:53:37.732: E/AndroidRuntime(12285): at java.lang.reflect.Method.invoke(Native Method) 10-13 19:53:37.732: E/AndroidRuntime(12285): at java.lang.reflect.Method.invoke(Method.java:372) 10-13 19:53:37.732: E/AndroidRuntime(12285): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1029) 10-13 19:53:37.732: E/AndroidRuntime(12285): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:824) 代码如下 10-13 19:53:37.732: E/AndroidRuntime(12285): FATAL EXCEPTION: main 10-13 19:53:37.732: E/AndroidRuntime(12285): Process: com.example.mydemo, PID: 12285 10-13 19:53:37.732: E/AndroidRuntime(12285): java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.ListView.setAdapter(android.widget.ListAdapter)' on a null object reference 10-13 19:53:37.732: E/AndroidRuntime(12285): at com.example.mydemo.SubFragment1$MyTask.onPostExecute(SubFragment1.java:131) 10-13 19:53:37.732: E/AndroidRuntime(12285): at com.example.mydemo.SubFragment1$MyTask.onPostExecute(SubFragment1.java:1) 10-13 19:53:37.732: E/AndroidRuntime(12285): at android.os.AsyncTask.finish(AsyncTask.java:632) 10-13 19:53:37.732: E/AndroidRuntime(12285): at android.os.AsyncTask.access$600(AsyncTask.java:177) 10-13 19:53:37.732: E/AndroidRuntime(12285): at android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:645) 10-13 19:53:37.732: E/AndroidRuntime(12285): at android.os.Handler.dispatchMessage(Handler.java:102) 10-13 19:53:37.732: E/AndroidRuntime(12285): at android.os.Looper.loop(Looper.java:155) 10-13 19:53:37.732: E/AndroidRuntime(12285): at android.app.ActivityThread.main(ActivityThread.java:5696) 10-13 19:53:37.732: E/AndroidRuntime(12285): at java.lang.reflect.Method.invoke(Native Method) 10-13 19:53:37.732: E/AndroidRuntime(12285): at java.lang.reflect.Method.invoke(Method.java:372) 10-13 19:53:37.732: E/AndroidRuntime(12285): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1029) 10-13 19:53:37.732: E/AndroidRuntime(12285): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:824)
©️2020 CSDN 皮肤主题: 像素格子 设计师:CSDN官方博客 返回首页