我们在android的APP开发中有时候会碰到提供一个选项列表供用户选择的需求,如在投票类型的项目中,我们提供一些主题给用户选择,每个主题有若干选项,用户对这些主题的选项进行选择,然后提交。
本文以一个支持单选和多选投票项目为例,演示了在一个ListView中如何构建CheckBox列表和RadioButton列表,并分析了实现的原理和思路,提供有需要的朋友参考。
项目的演示效果如下。
通常我们的数据源来自于数据库。首先,我们构建投票项目类SubjectItem。
/** * 主题项目类 * @author zoupeiyang * */ public class SubjectItem { /** * 主题id */ private String subjectId; /** * 主题名称 */ private String subjectName; /** * 主题id */ private String itemId; /** * 主题名称 */ private String itemName; /** * 是否多选 */ private Boolean isMultiChoice; public String getSubjectId() { return subjectId; } public void setSubjectId(String subjectId) { this.subjectId = subjectId; } public String getSubjectName() { return subjectName; } public void setSubjectName(String subjectName) { this.subjectName = subjectName; } public String getItemId() { return itemId; } public void setItemId(String itemId) { this.itemId = itemId; } public String getItemName() { return itemName; } public void setItemName(String itemName) { this.itemName = itemName; } public Boolean getIsMultiChoice() { return isMultiChoice; } public void setIsMultiChoice(Boolean isMultiChoice) { this.isMultiChoice = isMultiChoice; } }
然后我们构造一个SubjectItem对象的List集合作为我们这个投票项目的数据源,实际项目中这个数据源可以来自数据库投票项目表。
/** * 模拟从数据库表获取投票主题项目的数据源 * * @return */ public static List<SubjectItem> getSubjectItems() { List<SubjectItem> list = new ArrayList<SubjectItem>(); HashMap<String, Boolean> subjectMap = new HashMap<String, Boolean>(); for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { SubjectItem item = new SubjectItem(); item.setSubjectId(i + ""); //为了方便主题标题和主题项目的布局,集合中同一个主题的项目,只有主题第一个项目的对象的主题名称不为空,其它为空 //这样显示ListView的每列时如果主题名称为空就隐藏主题名称 if (subjectMap.containsKey(item.getSubjectId())) { item.setSubjectName(""); } else { item.setSubjectName("投票主题" + i); subjectMap.put(item.getSubjectId(), true); } item.setItemId(i + "" + j); item.setItemName("项目名称" + i + "" + j); item.setIsMultiChoice(i % 2 == 1 ? true : false); list.add(item); } } return list; }
首先我们先来了解下在ListView控件展示列表数据的流程。
1、定义一个展示列表每一行的布局layout,我们这里定义这个layout的文件名为listview_subject_item.xml。
2、定义展示listview的布局layout,我们这里定义这个layout的文件名为listview_subject_activity.xml。
3、定义listview的数据适配器SubjectAdapter。
listview_subject_item.xml文件定义的ListView控件中每列view的布局。我们这里的投票项目是支持单选和多项,可以每列view的布局都包含了CheckBox和RadioButton控件,在手机界面显示视图的时候根据当前项目的投票类型(单选或多选)来自动显示(隐藏)对应的控件。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <!-- 投票主题ID,默认隐藏 --> <TextView android:id="@+id/tv_subject_id" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="" android:visibility="gone" /> <!-- 投票主题下项目ID,默认隐藏 --> <TextView android:id="@+id/tv_subject_item_id" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="" android:visibility="gone" /> <!-- 投票主题类型,true为多选,否则为单选,默认隐藏 --> <TextView android:id="@+id/tv_is_multi_choice" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="" android:visibility="gone" /> <!-- 投票主题名称,只有主题下的第一个项目才会显示主题名称,其它项目不显示 --> <TextView android:id="@+id/tv_subject_name" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_marginTop="20dp" android:layout_marginBottom="10dp" android:textSize="14sp" android:textColor="#1387DD" android:textStyle="bold" android:text="" /> <LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="horizontal" > <!-- 投票项目名称 --> <TextView android:id="@+id/tv_subject_item_name" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:gravity="center_vertical" /> <!-- 多选项目显示CheckBox --> <CheckBox android:id="@+id/cb_subject_item" android:layout_width="wrap_content" android:layout_height="wrap_content" android:clickable="false" android:focusable="false" android:focusableInTouchMode="false" android:gravity="center_vertical" /> <!-- 单选项目显示RadioButton --> <RadioButton android:id="@+id/rb_subject_item" android:layout_width="wrap_content" android:layout_height="wrap_content" android:clickable="false" android:focusable="false" android:focusableInTouchMode="false" android:gravity="center_vertical" /> </LinearLayout> </LinearLayout>
定义展示listview的布局layou,文件名为listview_subject_activity.xml这里使用了RelativeLayout布局,将提交按钮固定在屏幕底部,方便用户提交投票信息。
<?xml version="1.0" encoding="UTF-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#ffffffff" > <RelativeLayout android:id="@+id/rl_head" android:layout_width="match_parent" android:layout_height="45dp" android:layout_alignParentTop="true" android:background="#0C99EF" android:paddingLeft="10dp" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="投票项目" android:textColor="#ffffffff" android:textSize="16sp" /> </RelativeLayout> <!-- 投票项目ListView --> <ListView android:id="@+id/lv_subject" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/rl_head" android:layout_marginBottom="50dp" android:layout_marginLeft="10dp" > </ListView> <Button android:id="@+id/btn_add" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_marginBottom="10dp" android:text="提交" /> </RelativeLayout>
listview_subject_activity.xml文件定义了名为lv_subject的ListView,这个ListView如何和listview控件中每列view的布局listview_subject_item.xml进行关联,还有我们前面定义了投票主题项目数据源,它又如何和listview进行关联数据绑定,要完成这些,我们必须依赖一个Apdater适配器类。
ListView控件通过方法setAdapter和Adapter关联。
在Adapter中通过getView方法和列view的布局listview_subject_item.xml进行关联。
数据源通过Adapter的自定义构造函数的参数传人Adapter。
package com.example.listviewcheckbox.adapter; import java.util.HashMap; import java.util.List; import com.example.listviewcheckbox.R; import com.example.listviewcheckbox.entity.SubjectItem; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.CheckBox; import android.widget.RadioButton; import android.widget.TextView; public class SubjectAdapter extends BaseAdapter { private List<SubjectItem> list; private Context context; //存储所有主题的项目的选中状态,遍历这个容器可以获取选中的项目信息 private HashMap<String,Boolean> subjectItemMap; private LayoutInflater inflater; public class ViewHolder{ //投票主题id控件 public TextView tvSubjectId; //投票主题名称控件 public TextView tvSubjectName; //投票项目名称控件 public TextView tvSubjectItemName; //投票项目id控件 public TextView tvSubjectItemId; //投票主题类型(单选或多选)控件 public TextView tvIsMultiChoice; //选中CheckBox控件(主题类型为多选时显示) public CheckBox cbSubjectItem; //选中RadioButton控件(主题类型为单选时显示) public RadioButton rbSubjectItem; } public SubjectAdapter(List<SubjectItem> list,Context context) { this.list=list; this.context=context; inflater = LayoutInflater.from(context); this.subjectItemMap=new HashMap<String, Boolean>(); //初始化subjectItemMap,默认所有项目为未选中状态 for (int i = 0; i < list.size(); i++) { this.subjectItemMap.put(list.get(i).getItemId(), false); } } @Override public int getCount() { // TODO Auto-generated method stub return list.size(); } @Override public Object getItem(int position) { // TODO Auto-generated method stub return 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 ViewHolder viewHolder = null; SubjectItem item = list.get(position); if(convertView!=null&&convertView.getId()==R.id.lv_subject) { viewHolder=(ViewHolder)convertView.getTag(); } else { viewHolder = new ViewHolder(); convertView=inflater.inflate(R.layout.listview_subject_item, null); viewHolder.tvSubjectId=(TextView)convertView.findViewById(R.id.tv_subject_id); viewHolder.tvSubjectName=(TextView) convertView.findViewById(R.id.tv_subject_name); viewHolder.tvSubjectItemId = (TextView) convertView.findViewById(R.id.tv_subject_item_id); viewHolder.tvSubjectItemName = (TextView) convertView.findViewById(R.id.tv_subject_item_name); viewHolder.cbSubjectItem = (CheckBox) convertView.findViewById(R.id.cb_subject_item); viewHolder.rbSubjectItem = (RadioButton) convertView.findViewById(R.id.rb_subject_item); viewHolder.tvIsMultiChoice = (TextView) convertView.findViewById(R.id.tv_is_multi_choice ); } //如果项目名称为空就隐藏当前项的产品名称,即所有子项目只允许第一个子项目出现产品名称 if(item.getSubjectName().equals("")) { viewHolder.tvSubjectName.setVisibility(View.GONE); } else { viewHolder.tvSubjectName.setText(item.getSubjectName()); } viewHolder.tvSubjectItemId.setText(item.getItemId()); viewHolder.tvSubjectId.setText(item.getSubjectId()); viewHolder.tvSubjectItemName.setText(item.getItemName()); viewHolder.tvIsMultiChoice.setText(item.getIsMultiChoice().toString()); //当前项目为多选项目 if(item.getIsMultiChoice().toString().equals("true")) { viewHolder.cbSubjectItem.setVisibility(View.VISIBLE); viewHolder.rbSubjectItem.setVisibility(View.GONE); viewHolder.cbSubjectItem.setChecked(this.subjectItemMap.get(item.getItemId())); } //当前项目为单选项目 else { viewHolder.cbSubjectItem.setVisibility(View.GONE); viewHolder.rbSubjectItem.setVisibility(View.VISIBLE); viewHolder.rbSubjectItem.setChecked(this.subjectItemMap.get(item.getItemId())); } convertView.setTag(viewHolder); return convertView; } /** * 获取所有主题的项目的选中状态容器 * @return */ public HashMap<String,Boolean> getSubjectItemMap() { return this.subjectItemMap; } }
最后我们定义一个Activity组件,将投票项目显示出来。
为了解决单选项目选中后同时要将同主题原来已经选中的项目取消,定义了一个Map(radioButtonSelectedMaps)来存储单选主题的选中的项目信息,key为单选主题ID,value为选中的项目ID。
这样在用户选择某个单选项目时,程序先将SubjectAdapter对象中subjectItemMap该项目主题之前选中的项目的状态设置为false,然后将当前选中的项目设置为true,然后更新ListView,实现单选效果。
public class SubjectActivity extends Activity { private ListView lvSubject; private SubjectAdapter subjectAdapter; private List<SubjectItem> list; private Button btnAdd; // 用来保存单选主题当前选中的项目,这样用户在切换选择同一个主题下其它选项时能够将之前选中的项目的状态设置为未选状态 private HashMap<String, String> radioButtonSelectedMaps; @Override public void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); setContentView(R.layout.listview_subject_activity); lvSubject = (ListView) findViewById(R.id.lv_subject); btnAdd = (Button) findViewById(R.id.btn_add); //从数据源获取投票主题和项目信息 list = DataService.getSubjectItems(); subjectAdapter = new SubjectAdapter(list, this); lvSubject.setAdapter(subjectAdapter); radioButtonSelectedMaps = new HashMap<String, String>(); // 提交投票事件处理 btnAdd.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub String selectValues="选中信息:"; //遍历用户选中项目,可以根据实际需求获取选中项目的任何信息 for (int i = 0; i < list.size(); i++) { if(subjectAdapter.getSubjectItemMap().get(list.get(i).getItemId())) { selectValues+="项目ID:"+list.get(i).getItemId()+"项目名称:"+list.get(i).getItemName(); } } Toast.makeText(SubjectActivity.this, selectValues.equals("选中信息:")?"未选中任何信息":selectValues, Toast.LENGTH_LONG).show(); } }); // ListView控件每一行点击事件处理 lvSubject.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { // TODO Auto-generated method stub ViewHolder viewHolder = (ViewHolder) view.getTag(); // 如果当前行是多选项目 if (viewHolder.tvIsMultiChoice.getText().equals("true")) { viewHolder.cbSubjectItem.toggle(); subjectAdapter.getSubjectItemMap().put(viewHolder.tvSubjectItemId.getText().toString(),viewHolder.cbSubjectItem.isChecked()); } //如果当前行为单选项目,注意单选项目选中后需要将同一主题下已经选中的项目设置为未选中状态 else { String currentSubjectIdSelected=viewHolder.tvSubjectId.getText().toString(); String currentSubjectItemId=viewHolder.tvSubjectItemId.getText().toString(); //判断该单选主题是否有已经选中项目,如果有需要将它的选中状态设置为未选中 if (radioButtonSelectedMaps.containsKey(currentSubjectIdSelected)) { subjectAdapter.getSubjectItemMap().put(radioButtonSelectedMaps.get(currentSubjectIdSelected),false); } //将当前选中的项目设置为该单选主题的选中项目 radioButtonSelectedMaps.put(currentSubjectIdSelected,currentSubjectItemId); viewHolder.rbSubjectItem.toggle(); subjectAdapter.getSubjectItemMap().put(currentSubjectItemId,viewHolder.rbSubjectItem.isChecked()); //更新ListView updateListView(); }}}); } /** * 更新ListView */ private void updateListView() { subjectAdapter.notifyDataSetChanged(); } }
// 提交投票事件处理 btnAdd.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub String selectValues="选中信息:"; //遍历用户选中项目,可以根据实际需求获取选中项目的任何信息 for (int i = 0; i < list.size(); i++) { if(subjectAdapter.getSubjectItemMap().get(list.get(i).getItemId())) { selectValues+="项目ID:"+list.get(i).getItemId()+"项目名称:"+list.get(i).getItemName(); } } Toast.makeText(SubjectActivity.this, selectValues.equals("选中信息:")?"未选中任何信息":selectValues, Toast.LENGTH_LONG).show(); } });
有需要的朋友可以访问我的GitHub下载本文示例的完整代码。
https://github.com/zoupeiyang/ListViewCheckBox