没想到,生产一辆跑车,车轮上的螺丝出问题就得花去一定时间。ListView就是这款螺丝,也不知道Android的开发人员是怎么的,这个破ListView竟然折磨了开发人员很久,而且还是在一些基础的控件RadioButton(rb)与CheckBox(cb)的状态使用上,太抓狂,现象如下:
现象一:listview放置rb后onItemClickListener事件失效;
现象二:listview 放置rb或cb后,用户希望在点击listview中的整条item时,就能变更rb/cb状态,但实际上,用户必须点击到rb/cb后才选中。
现象三:cb(checkbox) 滚动状态丢失现象: listview 中,如果有10项,其中手机屏幕显示1-6项,其余的7-10项在屏幕中不可见,得向下滚动后才能看到,这个时候,如果选中1、2项,再滚动到7-10项,之后再滚动回来1-6项,就发现1、2项并未被选中。(其现象和asp.net中翻页后丢失item的checkbox状态丢失现象是一样)
现象四:rb单选功能失效:listview中的单选按钮如果不进行处理,是不可能实现单选功能,而且,现象三的情况也会出现在这里。
以上这四种现象,哪怕是一种,都足够折磨人的了,现象一、二解决方法如下:
现象一:在listview 中rb对象XML属性中(cb同理),增加:android:focusable="false" android:focusableInTouchMode="false",因为rb的焦点是先于listview的,所以,设置屏蔽后,listview事件才能触发
现象二:修改listview每个条目所需要用的View项对应的xml文件(如rowitem.xml),在LinearLayout对象中,增加: android:descendantFocusability="blocksDescendants"
<!--现象一、二解决方法-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/row_checkbox_item"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:descendantFocusability="blocksDescendants" <!-- 务必设置descendantFocusability项 -->
android:orientation="horizontal" >
<TextView
android:id="@+id/row_checkbox_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<CheckBox
android:id="@+id/row_checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="4dp"
android:layout_marginRight="10dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:clickable="false" <!-- 设置clickable不可选-->
android:focusable="false" <!-- 设置foucusable失去焦点-->
android:focusableInTouchMode="false" /><!-- 设置foucsableInTouchMode失去焦点(点击模式) -->
<RadioButton
android:id="@+id/rbtn_phone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="false" <!-- 设置clickable不可选-->
android:focusable="false" <!-- 设置foucusable失去焦点 -->
android:focusableInTouchMode="false"/> <!-- 设置foucsableInTouchMode失去焦点(点击模式)-->
</LinearLayout>
这样,就可以通过修改xml布局文件来初步达到rb和cb的选中和事件触发问题,接下来解决另外两个问题。
现象三解决办法:
产生原因:每个item对应一个view,每个view中,可以有rb/cb/txtview等子view控件。listview在滚动时,会重新对每个进入屏幕的item项(view)进行数据载入(可以理解为网页上的翻页,屏幕滚动时,就是翻页),譬如,listview有10条item,,屏幕每次只能显示5条,开始时1-5条显示在屏幕上,另外5条在屏幕外,向下滚动到第7条后,第1-2条item从屏幕消失,第6-7条item显示在屏幕上,此时,listview会重新对6-7条进行数据载入,对于1-2条的item,listview一点也不为他们保留任何状态,所以,现象就产生了。
另外,滚动屏幕后,譬如,屏幕从1-5条滚动到6-10条,1-5条在屏幕外,第6条在屏幕最顶端,此时如果尝试使用parent.getChildAt(6)访问屏幕顶端的第6条item时,你会发现,会返回null,包括7-10条,都是返回null,但是,getChildAt(0)时却能返回一个对象,原来,系统为了节约资源,会用之前第0项来初始化滚屏后的第6条,而不是重新创建一个item对象(其对应关系一次类推),所以为什么会返回null,就是这个原因,一不留意,极容易产生异常。
如果对于cb来说,要实现多选 解决办法就是,建立一个List对象,将选中的item对应的状态保存到List中,List下标即是item在listview中的位置position),而值则是cb的状态标志(true/false)。通过重载getview方法实现。代码如下:
使用ArrayAdapter为例子:
public class CustomizedAdapter extends ArrayAdapter<List>{
static int selectitem=-1; //用于记录单选rb的最新选中位置
private LayoutInflater inflater;
private Context context;
private List list;
public CustomizedAdapter(Context context, List listitem)
{
super(context, R.layout.rowcheckoutlist, listitem);
this.context=context;
this.list=listitem;
inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
@Override
public int getCount() {
// TODO Auto-generated method stub
return this.list.size();
}
@Override
public ArrayList getItem(int position) {
// TODO Auto-generated method stub
return (ArrayList) this.list.get(position);
}
@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
//pos必须声明为final,如果声明成普通类型,那么每次触发cb事件时pos永为0,
final int pos = position;
View rowView = (View)convertView;
//如果rowView为空时,必须实例化一个xml,
if (rowView==null)
{
rowView = inflater.inflate(R.layout.rowcheckoutlist, null, true);
}
CheckBox cb= (CheckBox) rowView.findViewById(R.id.row_checkbox);
cb.setTag("tagCheckBox");
//为每个cb设置监听,当cb状态改变时,保存cb状态到list中,pos是当前listview中cb的位置,
//譬如,用户点击第二个cb,那么pos的值为2
cb.setOnCheckedChangeListener(new OnCheckedChangeListener(){
@Override
public void onCheckedChanged(CompoundButton buttonView,boolean isChecked) {
//保存checked状态到list对应的位置中
list.set(pos, isChecked);
}});
cb.setChecked((Boolean) list.get(pos));
//处理radioButton
RadioButton rb = (RadioButton) rowView.findViewById(R.id.rbtn_phone);
rb.setTag("tabRadioButton");
//为每个rb设置监听,当前rb选择,保存当前rb对应的pos值到selecteitem(static类型),
//譬如,用户点击第一个rb,那么selectitem为1,当再点击第二个rb时,
//selectitem则为2,selectitem永远保存最后单击的rb的位置
rb.setOnCheckedChangeListener(new OnCheckedChangeListener(){
@Override
public void onCheckedChanged(CompoundButton buttonView,boolean isChecked) {
if (isChecked)
{
selectitem = pos;
}
}});
if (pos == selectitem)
rb.setChecked(true);
else
rb.setChecked(false);
return rowView;
}
}
主函数:
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
this.setContentView(R.layout.main);
myListView = (ListView) this.findViewById(R.id.listView1);
//新建一个List,用于保存cb的状态
final List listitem = new ArrayList();
for (int i=0;i<list.size();i++)
{
//初始化list,
listitem.add(i,false);
}
CustomizedAdapter adapter = new CustomizedAdapter(this,listitem);
myListView.setAdapter(adapter);
//现象四rb解决方法:
//设置listview监听事件,该事件主要处理rb的单击时,将屏幕中所有rb状态
//设置为false,再将当前rb设置成true,完成单选功能
myListView.setOnItemClickListener(new setOnItemClickListener(){
@Override
public void onItemClick(AdapterView<?> parent, View convertView, int position,long id)
{
//遍历当前Item,设置所有Item的cb状态为false
for(int i=0;i<parent.getCount();i++)
{
View childView = parent.getChildAt(i);
if (childView==null) break;
RadioButton mRadioButton = (RadioButton) childView.findViewById(R.id.rbtn_phone);
mRadioButton.setChecked(false);
}
//设置选中rb状态为true;
RadioButton rb = (RadioButton) convertView.findViewById(R.id.rbtn_phone);
rb.setChecked(true);
}
);
}
通过以上方法,彻底解决listview的checkbox和radiobox的单、多选问题。如果有更好方法,欢迎交流
部分参考:http://blog.csdn.net/pathuang68/article/details/6455925