揭秘在ListView等AdapterView上动态添加删除项的陷阱

如何避开在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("");
  }
 }
 };
}

以上就是关于Android Listview相关内容介绍,希望对大家的学习有所帮助。

(0)

相关推荐

  • Android ListView适配器(Adapter)优化方法详解

    Android ListView的优化,在做Android项目的时候,在用到ListView 界面及数据显示,这个时候如果资源过大,对项目来说,用户体验肯定是不好的,这里就对如何优化做了详细介绍: Adapter的作用就是ListView界面与数据之间的桥梁,当列表里的每一项显示到页面时,都会调用Adapter的getView方法返回一个View.想过没有? 在我们的列表有1000000项时会是什么样的?是不是会占用极大的系统资源? ListView的Adapter的作用如下图所示: 先看看下面

  • android开发中ListView与Adapter使用要点介绍

    1. Adapter.getView() public View getView(int position, View convertView , ViewGroup parent){...} 这个方法就是用来获得指定位置要显示的View.官网解释如下: Get a View that displays the data at the specified position in the data set. You can either create a View manually or infl

  • Android ListView自定义Adapter实现仿QQ界面

    PS:listview中有一些简单使用的适配器,如:SimpleAdapter:构造方法SimpleAdapter(Context context,List<Map<String,?>> data,reString [] from,int [] to),但这种适配器过于单调,往往不能达到用户想要的效果,想要随心所欲,就用到了BaseAdapter,自定义适配器. 如图: 1.首先写布局文件 activity_layout.xml <?xml version="1.0

  • Adapter实现ListView带多选框等状态的自定义控件的注意事项

    Android本身为ListView提供了几个方便的Adapter,比如ArrayAdapter.SimpleCurrentAdapter等等.但是为了实现更复杂的列表视图和控制,一般都要继承BaseAdapter来实现自己的Adapter. 我需要的ListView是如图一样的实现SD卡资源文件浏览列表,每个列表项由一个ImageView.TextView.CheckBox组成,并且要求当整个列表中有一个或一个以上的Checkbox被选中时,右上角的搜索按钮就显示出来,否则就隐藏,因此需要对每

  • ListView的Adapter使用 之 初学ArrayAdapter String

    ListView是Android中经常会使用的东西,绑定数据对于初学者来说,尤其是刚接触编程的人来说,往往会觉得很难理解,我上大二的时候学的java,但是基本上相当于没有学,什么都没写过,真正接触编程就是开始上手学android,把这些记录下来,自己可以回头看下,也可以让新手更好的理解.高手绕过.... Android中Adapter我是这么理解的,是数据和视图之间的桥梁,数据在adapter中做处理,然后显示到视图上面. Adapter有很多种,有ArrayAdapter<T>, BaseA

  • ListView的Adapter使用(绑定数据) 之 自定义每一项的布局去绑定数据

    大家先看第一个例子显示: 这个界面相信大家都看到过的,这次比上一个例子多的是ListView 的每一项绑定的是不再是单纯的一个字符串了,ListView 的每一个条目我们需要显示两个信息,编号和姓名.可以看到,我们绑定了十条数据,用了一个for 循环,每一条绑定的时候创建一个HashMap,然后往里面放数据,放的数据有相对应的key, 即id,name,到时候我们取数据的时候就根据这个key 找到我们放进去的数据,有点类似与键值对的意思,不知道这样解释大家明白不明白. 贴上源代码,然后接下来讲解

  • Android开发中ListView自定义adapter的封装

    [引入] 我们一般编写listView的时候顺序是这样的:  •需要展示的数据集List<T>  •为这个数据集编写一个ListView  •为这个ListView编写一个Adapter,一般继承自BaseAdapter  •在BaseAdapter内部编写一个ViewHolder类,对应ListView里面的item控件,提高控件的查询效率 分析: List<T>:ListView --> Adapter extends BaseAdapter --> ViewHol

  • Android自定义Adapter的ListView的思路及代码

    在开发中,我们经常使用到ListView这个控件.Android的API也提供了许多创建ListView适配器的快捷方式.例如ArrayAdapter.SimpleAdapter和SimpleCursorAdapter等.但你是否发现,如果采用这些系统自带的适配器,对于事件的响应只能局限在一个行单位.假设一行里面有一个按钮和一个图片控件,它们之间的响应操作是不一样的.若采用系统自带的适配器,就不能精确到每个控件的响应事件.这时,我们一般采取自定义适配器来实现这个比较精确地请求. ListView

  • ListView Adapter优化 实例

    复制代码 代码如下: //方案一 - adapter优化public class listviewAdapterDemo1 extends BaseAdapter { private String[] mArrData;    private ListView listView;    private AsyncImageLoader asyncImageLoader;    private final LayoutInflater inflater; public listviewAdapte

  • Android listview与adapter详解及实例代码

    一个ListView通常有两个职责. (1)将数据填充到布局. (2)处理用户的选择点击等操作. 第一点很好理解,ListView就是实现这个功能的.第二点也不难做到,在后面的学习中读者会发现,这非常简单. 一个ListView的创建需要3个元素. (1)ListView中的每一列的View. (2)填入View的数据或者图片等. (3)连接数据与ListView的适配器. 也就是说,要使用ListView,首先要了解什么是适配器.适配器是一个连接数据和AdapterView(ListView就

随机推荐