在ListView上使用CheckBox的自定义Adapter写法
2013-09-19 18:56
197 查看
这似乎是一个经常的UI设计,所以值得记录一下。
写自定义的Adapter的主要麻烦在于其中的getView()方法,又特别在于其中CheckBox状态的保存和恢复。编写的一个原则是,尽可能吻合软件工程需求,比如耦合与分离,尽量简单。
标准的ListView构建过程是:
1、首先构建一个ArrayList(或其他List)来保存数据
2、用上述List构建ArrayAdapter
3、将ArrayAdapter传递给ListView
4、ArrayAdapter提供构建行View的方法给ListView
5、ListView在显示item时调用上述方法构建行View
6、现实行View,其中有CheckBox
7、ListView在不显示item时(比如移除屏幕外)时清除行View
最终目标是统计各行的checked状态。主要的问题在于:在CheckBox的View中虽然有当前的checked状态,但一旦其所在的行View被拖出屏幕外,则CheckBox的checked状态立即丢失。那么,怎样保存和恢复CheckBox的checked状态?在哪里保存?
在官网及其他地方搜罗了很久,看到多个方案,根据自己的需求,设计了一个比较简单的解决方案,要先说明的是:
1、checked状态保存位置是问题之一。我的方案是在最初的数据list中增加保存checked信息的位置。我没看过Android源码,但由于list可能很大,所以不大可能有list的拷贝复制操作,所以这个list相当于一个全局变量,可以用来在多个命名域空间之间传递信息。感觉是一个大胆创举,不过我并不保证肯定正确,也不保证将来正确。
2、匿名内部类在访问外部类变量时有限制,这是问题关键之二。
下面是解决方案,也即在数据list中增加checked标志位,在adapter中使用这个位置,并采用final变量中转,使得匿名内部类访问父类中的position信息,下面是代码和注释。
在主Activity中统计checked信息。
下面是改进方案,也即采用CheckBox增加tag来保存position信息,而不用隐晦的final变量:
感觉View可以增加tag是一件比较奇葩的事,让我进一步思考:其他对象是不是也可以随时增加tag?
考虑到模块之间尽量减低耦合,所以推荐方案二。
以下是截图。注意在行View布局中我设置了android:focusable="false",使得可以分别点击List的item和CheckBox。
测试的时候要小心,在选择了若干CheckBox之后要将其来回拖出屏幕,检查checked状态是否正常,最后再在最初的数据list中统计checked状态。
---------修正------------------------
2014年4月14日
写本文时对某些地方理解不清,并没说到要点,这里给予补充。
主要是两个问题。问题之一题是View被复用,但却没有被清空:
1、当ListView中的一行条目移出显示屏时,其View并未被回收,而是放在旁边,准备复用。注意此刻并不清空View里面的垃圾数据。
2、当有一行条目要进入显示屏时,刚才被回收的View立即被取过来重用,但是由于之前被用过,且没有重新清空的步骤,所以凡是没有被设置的地方均是View上次的数据。
解决方案是,任何表示数据的地方都要做清空或数据设置。
问题之二是,怎样向View的监听函数传递数据,方式之一是通过final类型变量,方式之二是设置在View里面。后者好。
在本例中,向监听函数传递CheckButton所在item在list列表中的位置,于是CheckButton被点击时能够将变动修改到list列表。
2015年1月1日
ListView里面常用findViewById()来寻找view中的子view,这种操作往往很费时,通常可以使用一个静态类来标识View的结构:
写自定义的Adapter的主要麻烦在于其中的getView()方法,又特别在于其中CheckBox状态的保存和恢复。编写的一个原则是,尽可能吻合软件工程需求,比如耦合与分离,尽量简单。
标准的ListView构建过程是:
1、首先构建一个ArrayList(或其他List)来保存数据
2、用上述List构建ArrayAdapter
3、将ArrayAdapter传递给ListView
4、ArrayAdapter提供构建行View的方法给ListView
5、ListView在显示item时调用上述方法构建行View
6、现实行View,其中有CheckBox
7、ListView在不显示item时(比如移除屏幕外)时清除行View
最终目标是统计各行的checked状态。主要的问题在于:在CheckBox的View中虽然有当前的checked状态,但一旦其所在的行View被拖出屏幕外,则CheckBox的checked状态立即丢失。那么,怎样保存和恢复CheckBox的checked状态?在哪里保存?
在官网及其他地方搜罗了很久,看到多个方案,根据自己的需求,设计了一个比较简单的解决方案,要先说明的是:
1、checked状态保存位置是问题之一。我的方案是在最初的数据list中增加保存checked信息的位置。我没看过Android源码,但由于list可能很大,所以不大可能有list的拷贝复制操作,所以这个list相当于一个全局变量,可以用来在多个命名域空间之间传递信息。感觉是一个大胆创举,不过我并不保证肯定正确,也不保证将来正确。
2、匿名内部类在访问外部类变量时有限制,这是问题关键之二。
下面是解决方案,也即在数据list中增加checked标志位,在adapter中使用这个位置,并采用final变量中转,使得匿名内部类访问父类中的position信息,下面是代码和注释。
public class MyArrayAdapter extends ArrayAdapter{ int resource; final List<LvRow> listItems; // 用这个list来指向最初的数据list private class ViewHolder { int position; } public MyArrayAdapter(Context context, int textViewResourceId, List<LvRow> objects) { super(context, textViewResourceId, objects); resource = textViewResourceId; listItems = objects; // 相信这个objects就是构建Adapter的最初的数据list,赶紧保存 } public View getView (int position, View convertView, ViewGroup parent){ LinearLayout fileView; // 方案一,使用final变量 // 进入方法都会生成一个final p,原本方法退出之后会被回收, // 但由于后面兼听类的引用,使其不会被回收,除非兼听类先被回收。 // 下次进入本方法会生成另外一个final p。 // 因此,我们可以用final p来保存position。 // 这是我的理解,不肯定对。 final Integer p; p = position; LvRow lr = (LvRow)getItem(position); if (convertView == null){ fileView = new LinearLayout(getContext()); String inflater = Context.LAYOUT_INFLATER_SERVICE; LayoutInflater li; li = (LayoutInflater)getContext().getSystemService(inflater); li.inflate(resource, fileView, true); }else{ fileView = (LinearLayout)convertView; } TextView name = (TextView)fileView.findViewById(R.id.text1); name.setText(lr.getName()); TextView length = (TextView)fileView.findViewById(R.id.text2); length.setText(lr.getLength()); TextView date = (TextView)fileView.findViewById(R.id.text3); date.setText(lr.getDate()); // 这是我们ListView的宝贝CheckBox CheckBox cb = (CheckBox)fileView.findViewById(R.id.checkbox); if (lr.getName().charAt(0) == '/') // 这是一个文件管理项目的代码,不允许check目录,故隐藏CheckBox { cb.setVisibility(cb.INVISIBLE); }else{ cb.setVisibility(cb.VISIBLE); cb.setChecked(listItems.get(position).getSelected()); // 从数据list中恢复checked状态 cb.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { CheckBox cb = (CheckBox)view; listItems.get(p).setSelected(cb.isChecked()); // 保持对final p的引用,使其不被回收 } }); } return fileView; } } // 注意增加了selected标志位 public class LvRow{ private String name; private String length; private String date; private File file; private boolean selected; // 各种set/get/constructer略掉 }
在主Activity中统计checked信息。
private void shareFiles() { // 共享被选择的文件 List<LvRow> selectedItems = new ArrayList<LvRow>(); // 这个viewListItems就是最初构建Adapter的数据list // 在那边修改后就反映到这里 int size = viewListItems.size(); for(int count = 0;count<size;count++){ if (viewListItems.get(count).getSelected() == true){ selectedItems.add(viewListItems.get(count)); } } // share selectedItems }
下面是改进方案,也即采用CheckBox增加tag来保存position信息,而不用隐晦的final变量:
public class MyArrayAdapter extends ArrayAdapter{ int resource; final List<LvRow> listItems; private class ViewHolder { int position; } public MyArrayAdapter(Context context, int textViewResourceId, List<LvRow> objects) { super(context, textViewResourceId, objects); resource = textViewResourceId; listItems = objects; } public View getView (int position, View convertView, ViewGroup parent){ LinearLayout fileView; LvRow lr = (LvRow)getItem(position); if (convertView == null){ fileView = new LinearLayout(getContext()); String inflater = Context.LAYOUT_INFLATER_SERVICE; LayoutInflater li; li = (LayoutInflater)getContext().getSystemService(inflater); li.inflate(resource, fileView, true); }else{ fileView = (LinearLayout)convertView; } TextView name = (TextView)fileView.findViewById(R.id.text1); name.setText(lr.getName()); TextView length = (TextView)fileView.findViewById(R.id.text2); length.setText(lr.getLength()); TextView date = (TextView)fileView.findViewById(R.id.text3); date.setText(lr.getDate()); CheckBox cb = (CheckBox)fileView.findViewById(R.id.checkbox); // 方案二,直接在View中添加tag来保存position信息 cb.setTag(position); if (lr.getName().charAt(0) == '/') { cb.setVisibility(cb.INVISIBLE); }else{ cb.setVisibility(cb.VISIBLE); cb.setChecked(listItems.get(position).getSelected()); // 恢复checked状态 cb.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { CheckBox cb = (CheckBox)view; // 下面直接用View的tag中获取position信息,并修改对应的最初的数据list listItems.get((Integer)view.getTag()).setSelected(cb.isChecked()); } }); } return fileView; } }
感觉View可以增加tag是一件比较奇葩的事,让我进一步思考:其他对象是不是也可以随时增加tag?
考虑到模块之间尽量减低耦合,所以推荐方案二。
以下是截图。注意在行View布局中我设置了android:focusable="false",使得可以分别点击List的item和CheckBox。
测试的时候要小心,在选择了若干CheckBox之后要将其来回拖出屏幕,检查checked状态是否正常,最后再在最初的数据list中统计checked状态。
---------修正------------------------
2014年4月14日
写本文时对某些地方理解不清,并没说到要点,这里给予补充。
主要是两个问题。问题之一题是View被复用,但却没有被清空:
1、当ListView中的一行条目移出显示屏时,其View并未被回收,而是放在旁边,准备复用。注意此刻并不清空View里面的垃圾数据。
2、当有一行条目要进入显示屏时,刚才被回收的View立即被取过来重用,但是由于之前被用过,且没有重新清空的步骤,所以凡是没有被设置的地方均是View上次的数据。
解决方案是,任何表示数据的地方都要做清空或数据设置。
问题之二是,怎样向View的监听函数传递数据,方式之一是通过final类型变量,方式之二是设置在View里面。后者好。
在本例中,向监听函数传递CheckButton所在item在list列表中的位置,于是CheckButton被点击时能够将变动修改到list列表。
2015年1月1日
ListView里面常用findViewById()来寻找view中的子view,这种操作往往很费时,通常可以使用一个静态类来标识View的结构:
public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null) { convertView = mInflater.inflate(R.layout.your_layout, null); holder = new ViewHolder(); holder.text = (TextView) convertView.findViewById(R.id.text); convertView.setTag(holder); } else { holder = convertView.getTag(); } holder.text.setText("Position " + position); return convertView; } private static class ViewHolder { public TextView text; }以上例子来自:http://lucasr.org/2012/04/05/performance-tips-for-androids-listview/
相关文章推荐
- 使用自定义Adapter的ListView优化方式
- android中ExpandableListView 使用自定义Adapter的方法
- 使用自定义的BaseAdapter实现LIstView的展示(转)
- 使用自定义的BaseAdapter实现LIstView的展示
- 关于ListView中使用自定义Adapter及时更新数据
- 27、ListView使用自定义的Adapter
- 自定义带单选框的SimpleCursorAdapter(ListView中增加CheckBox的OnClick响应)
- ListView中使用自定义Adapter及时更新数据
- android ListView布局之三(使用自定义的Adapter绑定数据,通过contextView.setTag绑定数据)有按钮的ListView
- 使用自定义的BaseAdapter实现LIstView的展示
- Android 使用CheckBox实现ListView自定义单选
- ListFragment使用ListView和自定义Adapter
- [转载]ListView中使用自定义Adapter及时更新数据
- Android中ListView同过自定义布局并使用SimpleAdapter的方式实现数据的绑定
- ListView中使用自定义Adapter及时更xin
- Android 之自定义CheckBox结合ListView使用
- android ListView布局之三(使用自定义的Adapter绑定数据,通过contextView.setTag绑定数据)有按钮的ListView
- ListView的Adapter使用(绑定数据) 之 自定义每一项的布局去绑定数据(一)
- Android定制ListView的界面(使用继承自ArrayAdapter的自定义适配器)--《第一行代码Android》学习笔记
- 自定义ListView使用的Adapter