本實(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í)。
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ì)講解。
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)聽。
//按鈕點(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ì)講解。
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輸出。