鍍金池/ 教程/ Android/ 第十三章-ListView擴(kuò)展(多選、全選、反選)
第十八章-ViewPager+FragmentStatePagerAdapter實(shí)現(xiàn)仿微信Tab
第十五章-GridView實(shí)現(xiàn)動(dòng)態(tài)添加和刪除子項(xiàng)
第九章-進(jìn)度條ProgressBar
第十二章-經(jīng)典的ListView
第十四章-GridView控件
第八章-時(shí)間相關(guān)控件
第七章-下拉框Spinner控件
第二章-EditText探秘
第二十章-Android菜單之上下文菜單
第十一章-各種對(duì)話框Dialog集錦
第二十一章-Android菜單之子菜單
第六章-切換類TextSwitcher和ImageSwitcher
第十七章-ViewPager切換界面
第五章-開關(guān)按鈕ToggleButton和Switch
第二十二章-PopupWindow浮動(dòng)窗
第十六章-幻燈片ViewFlipper
第二十四章-RecyclerView動(dòng)態(tài)添加、刪除及點(diǎn)擊事件
第三章-交互之王Button控件
第二十三章-全新控件RecyclerView
第一章-好玩的TextView
第十三章-ListView擴(kuò)展(多選、全選、反選)
第四章-玩轉(zhuǎn)單選和多選按鈕
第十章-可以拖動(dòng)的ProgressBar-SeekBar
第十九章-Android菜單之選項(xiàng)菜單

第十三章-ListView擴(kuò)展(多選、全選、反選)

本實(shí)例可以直接嫁接與各類點(diǎn)餐APP上,當(dāng)然你也可以發(fā)揮你的想象應(yīng)用到其他項(xiàng)目之上,本實(shí)例主要實(shí)現(xiàn)的功能是,實(shí)現(xiàn)子類項(xiàng)目的單選、多選、全選和反選功能,點(diǎn)擊確定時(shí)還能對(duì)應(yīng)生成價(jià)格,下面結(jié)合代碼對(duì)實(shí)例進(jìn)行講解。 主布局文件:

<?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="點(diǎn)餐" />
        <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> 

這里給每個(gè)Button的設(shè)置了layout_weight屬性并賦值為1,這樣所有的Button就可以平分整個(gè)屏幕寬度,這點(diǎn)在面試或項(xiàng)目中也是經(jīng)常會(huì)遇到的,需要注意。

ListView的子項(xiàng)布局文件:

<?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="單價(jià):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>

采用線性布局,并設(shè)置了其orientation屬性的值為horizontal即水平布局,包括一個(gè)CheckBox用于點(diǎn)餐選擇(此處CheckBox的clickable屬性設(shè)置成了false,意為不處理單擊事件,交由父控件處理),一個(gè)ImageView用于圖片展示,和三個(gè)TextView分別顯示名稱和價(jià)格。 為了方面數(shù)據(jù)操作,可以把屬性封裝成bean,此舉也符合面向?qū)ο蟮乃枷?,在開發(fā)中經(jīng)常會(huì)用到,代碼如下:

public class MyListAdapter extends BaseAdapter {
    // 填充數(shù)據(jù)的list
    private ArrayList<Drink> drinklist;
    // 用來(lái)控制CheckBox的選中狀況
    private static HashMap<Integer, Boolean> isSelected;
    // 上下文
    private Context context;
    // 用來(lái)導(dǎo)入布局
    private LayoutInflater inflater = null;

    private Boolean isShow = false;
    // 構(gòu)造器
    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;
        // 初始化數(shù)據(jù)
        initDatas();
    }
    // 初始化isSelected的數(shù)據(jù)
    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對(duì)象
            holder = new ViewHolder();
            // 導(dǎo)入布局并賦值給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設(shè)置標(biāo)簽
            convertView.setTag(holder);
        } else {
            // 取出holder
            holder = (ViewHolder) convertView.getTag();
        }
        // 獲取數(shù)據(jù)
        Drink food = drinklist.get(position);
        // 將數(shù)據(jù)填充到當(dāng)前convertView的對(duì)應(yīng)控件中
        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);
        // 設(shè)置list中TextView的顯示
        // 根據(jù)isSelected來(lái)設(shè)置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;
    }
}

對(duì)部分代碼進(jìn)行講解:

  • 這里創(chuàng)建了一個(gè)Map容器用以保存每個(gè)CheckBox的選擇情況,當(dāng)被選擇時(shí),Map的key存入CheckBox的序號(hào),其Value存入true,反之,沒(méi)有沒(méi)選擇Value存入false。為了方面操作這個(gè)Map容器,使用了getter和setter進(jìn)行了封裝。

  • InitDatas方法初始化了初始狀態(tài)下的CheckBox都是未選擇的。

  • 構(gòu)造方法傳入了三個(gè)參數(shù),分別是數(shù)據(jù)源、上下文對(duì)象、決定CheckBox和全選及反選按鈕是否顯示的標(biāo)識(shí)。

  • 四個(gè)必須覆寫的方法上一節(jié)已經(jīng)講解它們的作用,這里就不再重復(fù),為了提高運(yùn)行效率,這里也使用了ViewHolder類。在getView方法中根據(jù)傳入的isShow的布爾值決定是否顯示CheckBox。 最后,MainActivity.java代碼如下:(代碼較長(zhǎng),分段講解)
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; // 記錄選中的條目數(shù)量  
    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);
    }
  • 為了方便統(tǒng)一處理監(jiān)聽事件,這里實(shí)現(xiàn)了OnClickListener單擊監(jiān)聽和ListView的OnItemClickListener子項(xiàng)點(diǎn)擊監(jiān)聽。此外,因?yàn)樯婕暗絼?dòng)態(tài)改變UI布局中控件的顯示與否,這里采用了Handler機(jī)制,Handler機(jī)制一般用于更改主線程UI,在后面的章節(jié)還會(huì)詳細(xì)講解。

  • 代碼中有兩個(gè)方法,一個(gè)是show,用于顯示子項(xiàng)中的CheckBox(重新調(diào)用構(gòu)造方法,第三個(gè)參數(shù)傳入true)和主布局文件中的反選和全選按鈕,dismiss方法則用于隱藏子項(xiàng)中的CheckBox(重新調(diào)用構(gòu)造方法,第三個(gè)參數(shù)傳入false)和主布局文件中的反選和全選按鈕。
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    setContentView(R.layout.activity_main);
    initView();// 初始化控件  
    initData();// 初始化數(shù)據(jù)
    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("點(diǎn)餐");
        mCancelAllButton.setVisibility(View.GONE);
        mSelectAllButton.setVisibility(View.GONE);
    }
    mSelectButton.setOnClickListener(this);
    mCommitButton.setOnClickListener(this);
    listView.setOnItemClickListener(this);
}
/**
 * 初始化虛擬數(shù)據(jù) 
 */
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();
    }
}

這里主要有三個(gè)方法

  • onCreate方法,界面加載時(shí)首先會(huì)調(diào)用的方法,用于完成界面的初始化工作。這里初始化了適配器類MyListAdapter,這個(gè)類要傳入三個(gè)參數(shù),分別是數(shù)據(jù)源、上下文對(duì)象和控件顯示標(biāo)識(shí),因?yàn)槌跏蓟瘯r(shí)CheckBox、反選和全選按鈕都不顯示,所以這里傳入false,最后調(diào)用setAdapter方法,設(shè)置適配器。

  • initView方法,為了控制子項(xiàng)中的控件,這里通過(guò)LayoutInflater.from(this).inflate(R.layout.item, null)方法獲得了子項(xiàng)的布局文件,然后通過(guò)findViewById獲得每一個(gè)控件對(duì)象。根據(jù)標(biāo)識(shí)位isShow在初始化時(shí)控制第二個(gè)Button的text屬性,并控制全選和反選按鈕的顯示與否。方法最后設(shè)置了三個(gè)控件的點(diǎn)擊事件監(jiān)聽。

  • initData方法,采用Java反射機(jī)制獲取圖片資源,然后將數(shù)據(jù)存儲(chǔ)到list 集合中。
//按鈕點(diǎn)擊事件處理
    @Override
    public void onClick(View v) {
        int mID = v.getId();
        switch (mID) {
            case R.id.btn_commit:
                myPrice();// 計(jì)算總價(jià)并輸出
                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的長(zhǎng)度,將MyAdapter中的map值全部設(shè)為true  
        for (int i = 0; i < list.size(); i++) {
            MyListAdapter.getIsSelected().put(i, true);
        }
        // 數(shù)量設(shè)為list的長(zhǎng)度  
        checkNum = list.size();
        // 刷新listview和TextView的顯示  
        adapter.notifyDataSetChanged();
    }

    //決定第二個(gè)按鈕顯示
    private void whatToShow() {
        if (isShow) {
            Message message = Message.obtain();
            message.what = 0;
            handler.sendMessage(message);
            mSelectButton.setText("點(diǎn)餐");
        } else {
            Message message = Message.obtain();
            message.what = 1;
            handler.sendMessage(message);
            mSelectButton.setText("取消");
        }
    }
    //反選方法
    private void selectNone() {
        // 遍歷list的長(zhǎng)度,將MyAdapter中的map值全部設(shè)為true
        for (int i = 0; i < list.size(); i++) {
            MyListAdapter.getIsSelected().put(i, false);
        }
        // 數(shù)量設(shè)為list的長(zhǎng)度
        checkNum = list.size();
        // 刷新listview和TextView的顯示
        adapter.notifyDataSetChanged();
    }
    /**
     * 計(jì)算總價(jià)格的方法 
     */
    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 + "項(xiàng),總價(jià)錢為:" + money, Toast.LENGTH_SHORT).show();
    }

    /**
     * listview的item的選擇的方法 
     */
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        // 取得ViewHolder對(duì)象,這樣就省去了通過(guò)層層的findViewById去實(shí)例化我們需要的cb實(shí)例的步驟  
        MyListAdapter.ViewHolder holder = (MyListAdapter.ViewHolder) view.getTag();
        // 改變CheckBox的狀態(tài)  
        holder.cb.toggle();
        // 將CheckBox的選中狀況記錄下來(lái)  
        MyListAdapter.getIsSelected().put(position, holder.cb.isChecked());
    }
}
  • 覆寫了onClick方法,根據(jù)參數(shù)view并調(diào)用其getId的方法用來(lái)判斷單擊了哪一個(gè)按鈕,然后進(jìn)行相應(yīng)的邏輯操作。

  • selectAll是全選的方法,通過(guò)遍歷整個(gè)數(shù)據(jù)集合,將Map集合中的所有value改成true,并調(diào)用notifyDataSetChanged方法刷新ListView,selectNone則是反選的方法,同理遍歷數(shù)據(jù)集合將Map中所有value的值改成false,并調(diào)用notifyDataSetChanged即可。

  • whatToShow方法,根據(jù)isShow的值判斷是否顯示CheckBox、反選和全選按鈕,并動(dòng)態(tài)這時(shí)mSelectButton的text屬性。這里用到了Hanlder機(jī)制進(jìn)行傳值。

  • myPrice方法用于計(jì)算選擇子項(xiàng)的總價(jià)格,通過(guò)getIsSelected方法獲取是否選定的Map集合,然后進(jìn)行數(shù)據(jù)集合的遍歷,將選定的子項(xiàng)的價(jià)格求和即得到總的價(jià)格。

  • 最后覆寫了ListView的子項(xiàng)單擊事件監(jiān)聽,將是否選定的結(jié)果記錄到MyListAdapter.getIsSelected()方法獲得的Map集合中。這里單擊子項(xiàng)即可以選擇CheckBox,因?yàn)樵诓季治募袑heckBox的clickable屬性設(shè)置成了false,由事件分發(fā)機(jī)制可知,子控件不消費(fèi)點(diǎn)擊事件,交由父控件(這里是ListView的子項(xiàng))攔截消費(fèi)單擊監(jiān)聽,事件分發(fā)機(jī)制后面還會(huì)講解。

  • 這里傳遞數(shù)據(jù)的方式也可以選擇接口回調(diào)的方式,對(duì)于回調(diào)方式,下一節(jié)會(huì)介紹,這里讀者可以嘗試自行實(shí)現(xiàn)。 運(yùn)行實(shí)例如下:

http://wiki.jikexueyuan.com/project/twenty-four-Scriptures/images/13-1.png" alt="這里寫圖片描述" />

http://wiki.jikexueyuan.com/project/twenty-four-Scriptures/images/13-2.png" alt="這里寫圖片描述" />

單擊點(diǎn)餐按鈕后,點(diǎn)餐按鈕本身會(huì)變成取消按鈕,同時(shí)全選、反選按鈕和CheckBox控件會(huì)顯示出來(lái),選擇要點(diǎn)的飲料,點(diǎn)擊確定按鈕,會(huì)自動(dòng)計(jì)算出選擇飲料的總價(jià)格,通過(guò)Toast輸出。