揭秘在ListView等AdapterView上动态添加删除项的陷阱
作者:lijiao 发布时间:2022-11-26 16:02:27
如何避开在ListView等AdapterView上动态添加删除项的陷阱,下面就为大家分享,具体内容如下
首先,定义如下array资源,作为列表的加载内容:
<resources>
<string name="app_name">MyListView</string>
<string-array name="language">
<item>Java</item>
<item>C</item>
<item>C++</item>
<item>PHP</item>
</string-array>
然后简单地写下布局文件,由于我需要不管列表有多长,始终在底部显示编辑框和按钮,所以将ListView中的layout_weight设为1。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ListView
android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="0dip"
android:layout_weight="1" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<EditText
android:id="@+id/addLangEdit"
android:layout_width="200px"
android:layout_height="wrap_content" />
<Button
android:id="@+id/addButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="添加" />
</LinearLayout>
</LinearLayout>
最后添上Activity的代码,似乎没什么问题了,运行一下。
public class MyListView extends ListActivity {
private ArrayAdapter<CharSequence> mAdapter;
private ListView mListView;
private EditText mLanguageText;
private Button mAddButton;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.mylist1);
//get the view
mListView = getListView();
mLanguageText = (EditText) findViewById(R.id.addLangEdit);
mAddButton = (Button) findViewById(R.id.addButton);
//array adapter created from string array resources
mAdapter = ArrayAdapter.createFromResource(
this,
R.array.language,
android.R.layout.simple_list_item_1);
//set the adapter
mListView.setAdapter(mAdapter);
//add listener
mAddButton.setOnClickListener(mOnClickListener);
}
private OnClickListener mOnClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
String text = mLanguageText.getText().toString();
if(null == text || "".equals(text.trim())) {
Toast.makeText(MyListView.this, "输入不能为空", Toast.LENGTH_SHORT).show();
}else {
mAdapter.add(text);
mAdapter.notifyDataSetChanged();
mLanguageText.setText("");
}
}
};
}
界面成功显示,可是每次点击“添加”,就会抛出java.lang.UnsupportedOperationException,这看似很匪夷所思,因为按照android的api文档,确实能通过adapter上的add、remove等方法在运行时增删列表项啊,那问题到底出在哪里呢?
借助google的帮助,找到如下解答:
这里说到,如果要动态的改变列表的大小,必须使用一个可变大小的List(如ArrayList),而不能使用固定长度的array,否则将会得到UnsupportedOperationException。也就是说,由于需要从资源文件中加载内容,所以我自然就想到调用ArrayAdapter.createFromResource(Context context, int textArrayResId, int textViewResId)方法来构造adapter,而此方法导致ArrayAdapter中维护的是一个定长数组!对数组进行add,当然就会出错了。看到这里我不禁感慨,好一个陷阱!!!真相仿佛离我越来越近了,让我们再进入ArrayAdapter源码探个究竟。
public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
// 代表ArrayAdapter中的数据
private List<T> mObjects;
// 其他fields
public ArrayAdapter(Context context, int textViewResourceId, List<T> objects) {
init(context, textViewResourceId, 0, objects);
}
public ArrayAdapter(Context context, int textViewResourceId, T[] objects) {
// 注意这里的Arrays.asList(...)方法!!!
init(context, textViewResourceId, 0, Arrays.asList(objects));
}
public static ArrayAdapter<CharSequence> createFromResource(Context context,
int textArrayResId, int textViewResId) {
CharSequence[] strings = context.getResources().getTextArray(textArrayResId);
return new ArrayAdapter<CharSequence>(context, textViewResId, strings);
}
private void init(Context context, int resource, int textViewResourceId, List<T> objects) {
mContext = context;
mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mResource = mDropDownResource = resource;
mObjects = objects;
mFieldId = textViewResourceId;
}
public void add(T object) {
if (mOriginalValues != null) {
synchronized (mLock) {
mOriginalValues.add(object);
if (mNotifyOnChange) notifyDataSetChanged();
}
} else {
mObjects.add(object); // 若该mObjects为固定长度List,此处将抛异常!!!
if (mNotifyOnChange) notifyDataSetChanged();
}
}
// 其他方法
}
通过源码可以发现,我上面的思考还是有误的。ArrayAdapter并没有单独维护array类型的数据,而是统一转换成了List,并存在了mObjects对象中。
createFromResource(...)调用了ArrayAdapter(Context context, int textViewResourceId, T[] objects)构造方法,而在该方法的内部实现中,android使用Arrays的静态方法asList(...)将一个数组转换为List。熟悉Java的程序员都知道,Arrays.asList(...)方法所返回的并不是一个java.util.ArrayList,而是一个Arrays类的内部类,该List实现是不能进行增删操作的!!(见jdk文档),对该List进行add将抛出UnsupportedOperationException!
哈哈,这下终于真相大白了,这样一来稍微改动一下原来的代码即可成功运行:D
FInal Solution:
/**
*
* @author CodingMyWorld
* 2011-7-31 下午04:43:48
*/
public class MyListView extends ListActivity {
private ArrayAdapter<CharSequence> mAdapter;
private ListView mListView;
private EditText mLanguageText;
private Button mAddButton;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.mylist1);
//get the view
mListView = getListView();
mLanguageText = (EditText) findViewById(R.id.addLangEdit);
mAddButton = (Button) findViewById(R.id.addButton);
//array adapter created from string array resources
List<CharSequence> objects = new ArrayList<CharSequence>(
Arrays.asList(getResources().getTextArray(R.array.language)));
mAdapter = new ArrayAdapter<CharSequence>(
this,
android.R.layout.simple_list_item_1,
objects);
//set the adapter
mListView.setAdapter(mAdapter);
//add listener
mAddButton.setOnClickListener(mOnClickListener);
}
private OnClickListener mOnClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
String text = mLanguageText.getText().toString();
if(null == text || "".equals(text.trim())) {
Toast.makeText(MyListView.this, "输入不能为空", Toast.LENGTH_SHORT).show();
}else {
mAdapter.add(text);
mAdapter.notifyDataSetChanged(); //not required
mLanguageText.setText("");
}
}
};
}


猜你喜欢
- 参考资料:alibaba-easyexcel.github.io简介EasyExcel是一个基于Java的简单、省内存的读写Excel的开源
- 一、Stream类概述在.NET Framework中,文件和流是有区别的。文件是存储在磁盘上的数据集,它具有名称和相应的路径。当打开一个文
- 在很多系统开发中,我们希望在指定的方法调用之前或者之后能打印出该方法的调用时间以及方法的出参和入参,就可以使用spring的AOP,还可以结
- 本篇文章基于redisson-3.17.6版本源码进行分析一、主从redis架构中分布式锁存在的问题1、线程A从主redis中请求一个分布式
- Android中有两种主要方式使用Service,通过调用Context的startService方法或调用Context的bindServ
- 在开发过程中,我们经常需要将数据库查询到的值放入jsp页面进行显示,在springmvc的controller中,我们采用request将数
- 本Demo为练手小项目,主要是熟悉目前主流APP的架构模式.此项目中采用MVC设计模式,纯代码和少许XIB方式实现.主要实现了朋友圈功能和摇
- 引言在项目中,时间的使用必不可少,而java 8之前的时间api Date和Calander等在使用上存在着很多问题,于是,jdk1.8引进
- 本文实例讲述了C#使用前序遍历、中序遍历和后序遍历打印二叉树的方法。分享给大家供大家参考。具体实现方法如下:public class Bin
- 本文实例为大家分享了SeekBar拖动条的应用代码,供大家参考,具体内容如下目标效果在该页面中放一个拖动条的状态提示信息,一个拖动条以及一个
- 一、创建web项目1、打开idea软件,点击界面上的Create New Project2、进入如下界面。选中 java Enterpris
- 对于多线程,大家并不陌生,对于如何创建线程也是轻车熟路,对于使用new thread和实现runable接口的方式,不再多说。这篇博文我们介
- 废话不多说,直接给大家贴C#代码了。/// <summary>/// 执行存储过程,返回" 返回值"///
- 前言Stream是一个来自数据源的元素队列并支持聚合操作,其中具有以下特性:Stream只负责计算,不存储任何元素,元素是特定类型的对象,形
- 前言腾讯动漫app v8.1.6 工具:jadx、frida、pixel3 安卓10提示:以下是本篇文章正文内容,案例可供参考一、问题1.1
- 1、将 Jmeter 下 extras 目录中 ant-jmeter-1.1.1.jar 包拷贝至 ant 安装目录下的lib目录中,否则会
- 需求:之前项目一个变动,需要对3张mysql数据库表数据进行清洗,3张表表名不同,表结构完全相同,需要对这3张表进行相同的增、改、查动作,一
- 本文实例为大家分享了android实现简单活动转盘的具体代码,供大家参考,具体内容如下页面public class CircleTurnta
- 一 为什么要使用线程池对于操作系统而言,创建一个线程的代价是十分昂贵的, 需要给它分配内存、列入调度,同时在线程切换时要执行内存换页,清空
- 平时开发,基本不改变的常量我们都放在了配置项里,如properties或yml文件里,这个时候为了只在启动时候进行加载。如何做呢?我们通过s