ListView CheckBox多选

来源:互联网 发布:自动化软件开发工程师 编辑:程序博客网 时间:2024/06/15 18:58

本实例可以直接嫁接与各类点餐APP上,当然你也可以发挥你的想象应用到其他项目之上,本实例主要实现的功能是,实现子类项目的单选、多选、全选和反选功能,点击确定时还能对应生成价格,下面结合代码对实例进行讲解。 主布局文件:

<?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:orientation="vertical">    <ListView        android:id="@+id/lv_drink"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:layout_above="@+id/ll_btns"></ListView>    <LinearLayout        android:id="@+id/ll_btns"        android:layout_width="match_parent"        android:layout_height="58dp"        android:layout_alignParentBottom="true"        android:orientation="horizontal">        <Button            android:id="@+id/btn_commit"            android:layout_width="wrap_content"            android:layout_weight="1"            android:layout_height="58dp"            android:text="确定" />        <Button            android:id="@+id/btn_select"            android:layout_width="wrap_content"            android:layout_weight="1"            android:layout_height="58dp"            android:text="点餐" />        <Button            android:id="@+id/btn_select_all"            android:layout_width="wrap_content"            android:layout_weight="1"            android:layout_height="58dp"            android:text="全选" />        <Button            android:id="@+id/btn_select_none"            android:layout_width="wrap_content"            android:layout_weight="1"            android:layout_height="58dp"            android:text="反选" />    </LinearLayout></RelativeLayout> 

这里给每个Button的设置了layout_weight属性并赋值为1,这样所有的Button就可以平分整个屏幕宽度,这点在面试或项目中也是经常会遇到的,需要注意。

ListView的子项布局文件:

<?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:background="#ffffff"    android:orientation="horizontal" >    <CheckBox        android:id="@+id/check_box"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:clickable="false"        android:focusable="false"        android:focusableInTouchMode="true" />    <ImageView        android:id="@+id/food_imager"        android:layout_width="50dp"        android:layout_height="50dp"        android:background="#ffffff" />    <TextView        android:id="@+id/food_name"        android:layout_width="100dp"        android:layout_height="50dp"        android:text="咖啡"        android:gravity="center_vertical"        android:layout_marginLeft="10dp"        android:textSize="18sp" />    <TextView        android:layout_width="wrap_content"        android:layout_height="50dp"        android:text="单价:RMB  "        android:paddingLeft="20dp"        android:textSize="12sp" />    <TextView        android:id="@+id/price"        android:layout_width="wrap_content"        android:layout_height="50dp"        android:paddingRight="10dp"        android:text="18"        android:layout_marginLeft="20dp"        android:textSize="18sp" /></LinearLayout>

采用线性布局,并设置了其orientation属性的值为horizontal即水平布局,包括一个CheckBox用于点餐选择(此处CheckBox的clickable属性设置成了false,意为不处理单击事件,交由父控件处理),一个ImageView用于图片展示,和三个TextView分别显示名称和价格。 为了方面数据操作,可以把属性封装成bean,此举也符合面向对象的思想,在开发中经常会用到,代码如下:

public class MyListAdapter extends BaseAdapter {    // 填充数据的list    private ArrayList<Drink> drinklist;    // 用来控制CheckBox的选中状况    private static HashMap<Integer, Boolean> isSelected;    // 上下文    private Context context;    // 用来导入布局    private LayoutInflater inflater = null;    private Boolean isShow = false;    // 构造器    public MyListAdapter(ArrayList<Drink> list, Context context, Boolean isShow) {        this.context = context;        this.drinklist = list;        inflater = LayoutInflater.from(context);        isSelected = new HashMap<Integer, Boolean>();        this.isShow = isShow;        // 初始化数据        initDatas();    }    // 初始化isSelected的数据    private void initDatas() {        for (int i = 0; i < drinklist.size(); i++) {            getIsSelected().put(i, false);        }    }    @Override    public int getCount() {        return drinklist.size();    }    @Override    public Object getItem(int position) {        return drinklist.get(position);    }    @Override    public long getItemId(int position) {        return position;    }    @Override    public View getView(int position, View convertView, ViewGroup parent) {        ViewHolder holder = null;        if (convertView == null) {            // 获得ViewHolder对象            holder = new ViewHolder();            // 导入布局并赋值给convertview            convertView = inflater.inflate(R.layout.item, null);            holder.imageView = (ImageView) convertView                    .findViewById(R.id.food_imager);            holder.tv_name = (TextView) convertView.findViewById(R.id.food_name);            holder.tv_price = (TextView) convertView.findViewById(R.id.price);            holder.cb = (CheckBox) convertView.findViewById(R.id.check_box);            // 为view设置标签            convertView.setTag(holder);        } else {            // 取出holder            holder = (ViewHolder) convertView.getTag();        }        // 获取数据        Drink food = drinklist.get(position);        // 将数据填充到当前convertView的对应控件中        if (isShow) {            holder.cb.setVisibility(View.VISIBLE);        } else {            holder.cb.setVisibility(View.GONE);        }        holder.imageView.setImageResource(food.drink_img);        holder.tv_name.setText(food.drink_name);        holder.tv_price.setText(food.drink_price);        // 设置list中TextView的显示        // 根据isSelected来设置checkbox的选中状况        holder.cb.setChecked(getIsSelected().get(position));        return convertView;    }    public static HashMap<Integer, Boolean> getIsSelected() {        return isSelected;    }    public static void setIsSelected(HashMap<Integer, Boolean> isSelected) {        MyListAdapter.isSelected = isSelected;    }    public class ViewHolder {        public TextView tv_name;        public TextView tv_price;        public ImageView imageView;        public CheckBox cb;    }}

对部分代码进行讲解:

  • 这里创建了一个Map容器用以保存每个CheckBox的选择情况,当被选择时,Map的key存入CheckBox的序号,其Value存入true,反之,没有没选择Value存入false。为了方面操作这个Map容器,使用了getter和setter进行了封装。

  • InitDatas方法初始化了初始状态下的CheckBox都是未选择的。

  • 构造方法传入了三个参数,分别是数据源、上下文对象、决定CheckBox和全选及反选按钮是否显示的标识。

  • 四个必须覆写的方法上一节已经讲解它们的作用,这里就不再重复,为了提高运行效率,这里也使用了ViewHolder类。在getView方法中根据传入的isShow的布尔值决定是否显示CheckBox。 最后,MainActivity.java代码如下:(代码较长,分段讲解)
public class MainActivity extends Activity implements OnClickListener, OnItemClickListener {    private ListView listView;    private Button mCommitButton, mSelectButton, mSelectAllButton, mCancelAllButton;    private ArrayList<Drink> drinks = new ArrayList<Drink>();    private MyListAdapter adapter;    private CheckBox checkBox;    private int checkNum; // 记录选中的条目数量      private ArrayList<String> list;    private Boolean isShow = false;    private Handler handler = new Handler() {        public void handleMessage(android.os.Message msg) {            if (msg.what == 1) {                show();            } else if (msg.what == 0) {                dismiss();            }        };    };    private void show() {        adapter = new MyListAdapter(drinks, getApplicationContext(), true);        listView.setAdapter(adapter);        isShow = true;        mCancelAllButton.setVisibility(View.VISIBLE);        mSelectAllButton.setVisibility(View.VISIBLE);    }    private void dismiss() {        adapter = new MyListAdapter(drinks, getApplicationContext(), false);        listView.setAdapter(adapter);        isShow = false;        mCancelAllButton.setVisibility(View.GONE);        mSelectAllButton.setVisibility(View.GONE);    }
  • 为了方便统一处理监听事件,这里实现了OnClickListener单击监听和ListView的OnItemClickListener子项点击监听。此外,因为涉及到动态改变UI布局中控件的显示与否,这里采用了Handler机制,Handler机制一般用于更改主线程UI,在后面的章节还会详细讲解。

  • 代码中有两个方法,一个是show,用于显示子项中的CheckBox(重新调用构造方法,第三个参数传入true)和主布局文件中的反选和全选按钮,dismiss方法则用于隐藏子项中的CheckBox(重新调用构造方法,第三个参数传入false)和主布局文件中的反选和全选按钮。
protected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    requestWindowFeature(Window.FEATURE_NO_TITLE);    setContentView(R.layout.activity_main);    initView();// 初始化控件      initData();// 初始化数据    adapter = new MyListAdapter(drinks, getApplicationContext(), false);    isShow = false;    listView.setAdapter(adapter);}/** * 初始化控件  */public void initView() {    View view = LayoutInflater.from(this).inflate(R.layout.item, null);    checkBox = (CheckBox) view.findViewById(R.id.check_box);    listView = (ListView) findViewById(R.id.lv_drink);// listview列表控件    mCommitButton = (Button) findViewById(R.id.btn_commit);// 确定按钮      list = new ArrayList<String>();    mSelectButton = (Button) findViewById(R.id.btn_select);    mSelectAllButton = (Button) findViewById(R.id.btn_select_all);    mCancelAllButton = (Button) findViewById(R.id.btn_select_none);    mSelectAllButton.setOnClickListener(this);    mCancelAllButton.setOnClickListener(this);    if (isShow) {        mSelectButton.setText("取消");        mCancelAllButton.setVisibility(View.VISIBLE);        mSelectAllButton.setVisibility(View.VISIBLE);    } else {        mSelectButton.setText("点餐");        mCancelAllButton.setVisibility(View.GONE);        mSelectAllButton.setVisibility(View.GONE);    }    mSelectButton.setOnClickListener(this);    mCommitButton.setOnClickListener(this);    listView.setOnItemClickListener(this);}/** * 初始化虚拟数据  */public void initData() {    Class cls = R.drawable.class;// 反射    try {        drinks.add(new Drink(cls.getDeclaredField("d1").getInt(null), "猕猴桃汁", "10"));        drinks.add(new Drink(cls.getDeclaredField("d2").getInt(null), "橙汁", "12"));        drinks.add(new Drink(cls.getDeclaredField("d3").getInt(null), "啤酒", "15"));        drinks.add(new Drink(cls.getDeclaredField("d4").getInt(null), "葡萄汁", "10"));        drinks.add(new Drink(cls.getDeclaredField("d5").getInt(null), "纯麦奶茶", "8"));        drinks.add(new Drink(cls.getDeclaredField("d6").getInt(null), "薄荷汁", "10"));        drinks.add(new Drink(cls.getDeclaredField("d7").getInt(null), "柠檬薄荷", "12"));        drinks.add(new Drink(cls.getDeclaredField("d8").getInt(null), "椰子汁", "10"));        drinks.add(new Drink(cls.getDeclaredField("d9").getInt(null), "珍珠奶茶", "9"));        drinks.add(new Drink(cls.getDeclaredField("d10").getInt(null), "石榴汁", "10"));        for (int i = 0; i < drinks.size(); i++) {            list.add("data" + " " + i);        }    } catch (Exception e) {        e.printStackTrace();    }}

这里主要有三个方法

  • onCreate方法,界面加载时首先会调用的方法,用于完成界面的初始化工作。这里初始化了适配器类MyListAdapter,这个类要传入三个参数,分别是数据源、上下文对象和控件显示标识,因为初始化时CheckBox、反选和全选按钮都不显示,所以这里传入false,最后调用setAdapter方法,设置适配器。

  • initView方法,为了控制子项中的控件,这里通过LayoutInflater.from(this).inflate(R.layout.item, null)方法获得了子项的布局文件,然后通过findViewById获得每一个控件对象。根据标识位isShow在初始化时控制第二个Button的text属性,并控制全选和反选按钮的显示与否。方法最后设置了三个控件的点击事件监听。

  • initData方法,采用Java反射机制获取图片资源,然后将数据存储到list 集合中。
//按钮点击事件处理    @Override    public void onClick(View v) {        int mID = v.getId();        switch (mID) {            case R.id.btn_commit:                myPrice();// 计算总价并输出                break;            case R.id.btn_select:                whatToShow();                break;            case R.id.btn_select_all:                selectAll();                break;            case R.id.btn_select_none:                selectNone();                break;        }    }    //全选方法    private void selectAll() {        // 遍历list的长度,将MyAdapter中的map值全部设为true          for (int i = 0; i < list.size(); i++) {            MyListAdapter.getIsSelected().put(i, true);        }        // 数量设为list的长度          checkNum = list.size();        // 刷新listview和TextView的显示          adapter.notifyDataSetChanged();    }    //决定第二个按钮显示    private void whatToShow() {        if (isShow) {            Message message = Message.obtain();            message.what = 0;            handler.sendMessage(message);            mSelectButton.setText("点餐");        } else {            Message message = Message.obtain();            message.what = 1;            handler.sendMessage(message);            mSelectButton.setText("取消");        }    }    //反选方法    private void selectNone() {        // 遍历list的长度,将MyAdapter中的map值全部设为true        for (int i = 0; i < list.size(); i++) {            MyListAdapter.getIsSelected().put(i, false);        }        // 数量设为list的长度        checkNum = list.size();        // 刷新listview和TextView的显示        adapter.notifyDataSetChanged();    }    /**     * 计算总价格的方法      */    public void myPrice() {        HashMap<Integer, Boolean> map = MyListAdapter.getIsSelected();        String str = "";        int money = 0;        for (int i = 0; i < map.size(); i++) {            if (map.get(i)) {                str += (i + " ");                money += Integer.parseInt(drinks.get(i).drink_price);            }        }        MyListAdapter.getIsSelected().get("");        Toast.makeText(getApplicationContext(), "已选中了" + str + "项,总价钱为:" + money, Toast.LENGTH_SHORT).show();    }    /**     * listview的item的选择的方法      */    @Override    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {        // 取得ViewHolder对象,这样就省去了通过层层的findViewById去实例化我们需要的cb实例的步骤          MyListAdapter.ViewHolder holder = (MyListAdapter.ViewHolder) view.getTag();        // 改变CheckBox的状态          holder.cb.toggle();        // 将CheckBox的选中状况记录下来          MyListAdapter.getIsSelected().put(position, holder.cb.isChecked());    }}
  • 覆写了onClick方法,根据参数view并调用其getId的方法用来判断单击了哪一个按钮,然后进行相应的逻辑操作。

  • selectAll是全选的方法,通过遍历整个数据集合,将Map集合中的所有value改成true,并调用notifyDataSetChanged方法刷新ListView,selectNone则是反选的方法,同理遍历数据集合将Map中所有value的值改成false,并调用notifyDataSetChanged即可。

  • whatToShow方法,根据isShow的值判断是否显示CheckBox、反选和全选按钮,并动态这时mSelectButton的text属性。这里用到了Hanlder机制进行传值。

  • myPrice方法用于计算选择子项的总价格,通过getIsSelected方法获取是否选定的Map集合,然后进行数据集合的遍历,将选定的子项的价格求和即得到总的价格。

  • 最后覆写了ListView的子项单击事件监听,将是否选定的结果记录到MyListAdapter.getIsSelected()方法获得的Map集合中。这里单击子项即可以选择CheckBox,因为在布局文件中将CheckBox的clickable属性设置成了false,由事件分发机制可知,子控件不消费点击事件,交由父控件(这里是ListView的子项)拦截消费单击监听,事件分发机制后面还会讲解。

  • 这里传递数据的方式也可以选择接口回调的方式,对于回调方式,下一节会介绍,这里读者可以尝试自行实现。 运行实例如下:

这里写图片描述

这里写图片描述

单击点餐按钮后,点餐按钮本身会变成取消按钮,同时全选、反选按钮和CheckBox控件会显示出来,选择要点的饮料,点击确定按钮,会自动计算出选择饮料的总价格,通过Toast输出。

原创粉丝点击