Android购物车
一口一个9 人气:0这里我用到的都是Android自带SDK中的资源,做了一个极其简单的购物车实现,总结购物车难点包含两个方面:
1、CheckBox的联动:
全选框、商铺复选框以及商品复选框要做到滴水不漏的联动,我的经验是在监听多选框时尽量采用click事件,避免使用checkChange事件(因为它总是能在你意想不到的地方调用),全选框可以通过商品价格来判断,这个在代码中也有体现。
2、数据的联动和UI的联动:
适配器的都是在外部类创建,而总价格等控件都是在调用适配器的地方,这个要做到联动,最简单的方式必然就是接口的回调,熟练使用可以节省很多代码,提高编程效率。
再有一个比较容易出现问题的地方就在于,我们经常是首先更改数据,然后通知适配器刷新数据(notifyDataSetChanged()),这里要注意的一点就是在更新数据的时候,一定确保更新的传递到适配器中的数据集合,否则会发现这个更新适配器的方法是无效的。
其他相关问题代码中均有体现,如果和我一样是一个编程小白,仔细阅读会有收获滴。
代码中没有添加自己的资源,逻辑都有实现就是UI丑了一点
activity_main:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout android:orientation="vertical" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.bwie.test.test1025two.MainActivity"> <RelativeLayout android:layout_gravity="center_horizontal" android:background="@color/colorAccent" android:layout_width="match_parent" android:layout_height="50dp"> <TextView android:textSize="38sp" android:gravity="center" android:textColor="#fff" android:text="购物车" android:layout_width="match_parent" android:layout_height="wrap_content" /> <TextView android:textColor="#fff" android:textSize="38sp" android:layout_alignParentRight="true" android:text="2" android:id="@+id/main_num" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </RelativeLayout> <ExpandableListView android:layout_weight="1" android:id="@+id/expand_able_view" android:layout_width="match_parent" android:layout_height="match_parent"/> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <CheckBox android:layout_weight="1" android:id="@+id/main_check_all" android:text="全选" android:layout_width="0dp" android:layout_height="wrap_content" /> <TextView android:id="@+id/main_price" android:gravity="center_horizontal" android:text="0" android:layout_weight="1" android:layout_width="0dp" android:layout_height="wrap_content" /> <Button android:id="@+id/btn_delete" android:background="#aaa" android:layout_weight="1" android:text="删除" android:layout_width="0dp" android:layout_height="wrap_content" /> <Button android:id="@+id/btn_buy" android:background="#f99" android:layout_weight="1" android:text="购买" android:layout_width="0dp" android:layout_height="wrap_content" /> </LinearLayout> </LinearLayout>
group_item:
注:这里图了个简单,商铺名称我是通过设置CheckBox的text来显示的,另外取消焦点是为了不影响二级列表的点击展开与收回子集列表
<?xml version="1.0" encoding="utf-8"?> <LinearLayout android:orientation="horizontal" xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <CheckBox android:focusable="false" android:id="@+id/group_check" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout>
child_item:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout android:orientation="horizontal" xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <CheckBox android:id="@+id/child_check" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <ImageView android:id="@+id/child_img" android:scaleType="center" android:src="@mipmap/ic_launcher" android:layout_width="80dp" android:layout_height="80dp" /> <TextView android:id="@+id/child_price" android:textSize="22sp" android:textColor="@color/colorPrimary" android:text="2888" android:layout_width="wrap_content" android:layout_height="80dp" /> <RelativeLayout android:layout_weight="1" android:layout_width="match_parent" android:layout_height="80dp"> <TextView android:text="名字" android:id="@+id/child_name" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <LinearLayout android:orientation="horizontal" android:layout_alignParentBottom="true" android:layout_width="wrap_content" android:layout_height="wrap_content"> <TextView android:id="@+id/child_jian" android:gravity="center_horizontal" android:text="—" android:layout_width="30dp" android:layout_height="30dp" /> <TextView android:gravity="center_horizontal" android:text="2" android:id="@+id/child_num" android:layout_width="30dp" android:layout_height="30dp" /> <TextView android:id="@+id/child_jia" android:gravity="center_horizontal" android:text="+" android:layout_width="30dp" android:layout_height="30dp" /> </LinearLayout> </RelativeLayout> </LinearLayout>
groupBean:
package com.bwie.test.test1025two; import java.util.ArrayList; /** * Created by Zzw on 2017/10/25. */ public class Group { private String name; private boolean check; private ArrayList<Child> children; public Group(String name, boolean check,ArrayList<Child> children) { this.name = name; this.check = check; this.children = children; } public Group() { } public String getName() { return name; } public void setName(String name) { this.name = name; } public boolean isCheck() { return check; } public void setCheck(boolean check) { this.check = check; } public void setChildren(ArrayList<Child> children){ this.children = children; } public ArrayList<Child> getChildren(){ return children; } @Override public String toString() { return "Group{" + "name='" + name + '\'' + ", check=" + check + '}'; } }
childBean:
package com.bwie.test.test1025two; /** * Created by Zzw on 2017/10/25. */ public class Child { private String name; private String img; private int num; private boolean check; private int price; public Child(String name, String img, int num, boolean check, int price) { this.name = name; this.img = img; this.num = num; this.check = check; this.price = price; } public Child() { } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getImg() { return img; } public void setImg(String img) { this.img = img; } public int getNum() { return num; } public void setNum(int num) { this.num = num; } public boolean isCheck() { return check; } public void setCheck(boolean check) { this.check = check; } public int getPrice() { return price; } public void setPrice(int price) { this.price = price; } @Override public String toString() { return "Child{" + "name='" + name + '\'' + ", img='" + img + '\'' + ", num=" + num + ", check=" + check + ", price=" + price + '}'; } }
MyAdapter:
package com.bwie.test.test1025two; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseExpandableListAdapter; import android.widget.CheckBox; import android.widget.ImageView; import android.widget.TextView; import java.util.ArrayList; /** * Created by Zzw on 2017/10/25. */ public class MyAdapter extends BaseExpandableListAdapter { Context context; ArrayList<Group> groups; public MyAdapter(Context context, ArrayList<Group> groups) { this.context = context; this.groups = groups; } //监听加减事件回调接口 public interface onNumChangeListener{ void onNumChange(int groupID,int childID,boolean isAdd); } private onNumChangeListener mOnNumChangeListener; public void setOnNumChangeListener(onNumChangeListener mOnNumChangeListener){ this.mOnNumChangeListener = mOnNumChangeListener; } //监听多选框点击事件回调接口。 public interface onCheckChangeListener{ void onGroupClick(int groupID); void onChildClick(int groupID,int childID); } private onCheckChangeListener mOnCheckChangeListener; public void setmOnCheckChangeListener(onCheckChangeListener mOnCheckChangeListener){ this.mOnCheckChangeListener = mOnCheckChangeListener; } //监听价格需要更新回调接口 public interface onShouldChangeMoneyListener{ void onShouldChnageMoney(); } private onShouldChangeMoneyListener mOnShouldChangeMoneyListener; public void setmOnShouldChangeMoneyListener(onShouldChangeMoneyListener mOnShouldChangeMoneyListener){ this.mOnShouldChangeMoneyListener = mOnShouldChangeMoneyListener; } @Override public int getGroupCount() { return groups.size(); } @Override public int getChildrenCount(int i) { return groups.get(i).getChildren().size(); } @Override public Object getGroup(int i) { return groups.get(i); } @Override public Object getChild(int i, int i1) { return groups.get(i).getChildren().get(i1); } @Override public long getGroupId(int i) { return i; } @Override public long getChildId(int i, int i1) { return i1; } @Override public boolean hasStableIds() { return false; } @Override public View getGroupView(final int i, boolean b, View view, ViewGroup viewGroup) { GroupHolder holder = null; if (view == null){ view = LayoutInflater.from(context).inflate(R.layout.group_item,viewGroup,false); holder = new GroupHolder(); holder.checkBox = (CheckBox) view.findViewById(R.id.group_check); view.setTag(holder); }else{ holder = (GroupHolder) view.getTag(); } holder.checkBox.setText(groups.get(i).getName()); holder.checkBox.setChecked(groups.get(i).isCheck()); if (mOnCheckChangeListener != null&&mOnShouldChangeMoneyListener != null){ holder.checkBox.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { mOnCheckChangeListener.onGroupClick(i); mOnShouldChangeMoneyListener.onShouldChnageMoney(); } }); } return view; } @Override public View getChildView(final int i,final int i1, boolean b, View view, ViewGroup viewGroup) { ChildHolder holder = null; if (view == null){ view = LayoutInflater.from(context).inflate(R.layout.child_item,viewGroup,false); holder = new ChildHolder(); holder.checkBox = (CheckBox) view.findViewById(R.id.child_check); holder.imageView = (ImageView) view.findViewById(R.id.child_img); holder.name = (TextView) view.findViewById(R.id.child_name); holder.num = (TextView) view.findViewById(R.id.child_num); holder.jian = (TextView) view.findViewById(R.id.child_jian); holder.jia = (TextView) view.findViewById(R.id.child_jia); holder.price = (TextView) view.findViewById(R.id.child_price); view.setTag(holder); }else{ holder = (ChildHolder) view.getTag(); } holder.checkBox.setChecked(groups.get(i).getChildren().get(i1).isCheck()); holder.imageView.setImageResource(R.mipmap.ic_launcher); holder.name.setText(groups.get(i).getChildren().get(i1).getName()); holder.num.setText(groups.get(i).getChildren().get(i1).getNum()+""); holder.price.setText(groups.get(i).getChildren().get(i1).getPrice()+""); if (mOnNumChangeListener != null&&mOnShouldChangeMoneyListener != null){ holder.jian.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { mOnNumChangeListener.onNumChange(i,i1,false); mOnShouldChangeMoneyListener.onShouldChnageMoney(); } }); } if (mOnNumChangeListener != null&&mOnShouldChangeMoneyListener != null){ holder.jia.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { mOnNumChangeListener.onNumChange(i,i1,true); mOnShouldChangeMoneyListener.onShouldChnageMoney(); } }); } if (mOnCheckChangeListener != null&&mOnShouldChangeMoneyListener != null){ holder.checkBox.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { mOnCheckChangeListener.onChildClick(i,i1); mOnShouldChangeMoneyListener.onShouldChnageMoney(); } }); } return view; } @Override public boolean isChildSelectable(int i, int i1) { return true; } class GroupHolder { CheckBox checkBox; } class ChildHolder{ CheckBox checkBox; ImageView imageView; TextView name,num,jian,jia,price; } }
MainActivity:
package com.bwie.test.test1025two; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.Button; import android.widget.CheckBox; import android.widget.ExpandableListView; import android.widget.TextView; import android.widget.Toast; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity { TextView num,price;//右上角当前商品数量和底部当前已选中商品的价格 ExpandableListView expandableListView;//展示商品信息的二级列表 CheckBox checkAll;//左下角全选 Button btnDel,btnBuy;//底部删除当前选中按钮、购买按钮 ArrayList<Group> groups = new ArrayList<>();//数据源集合 MyAdapter adapter;//自定义baseExpandable适配器 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView();//控件初始化 initData();//数据初始化 changeGoodsNum();//初始化当前商品个数。 /** * 自定义加减按钮回调 * params: groupID:商铺id childID:商品在当前商铺的id isADD:非加即减 */ adapter.setOnNumChangeListener(new MyAdapter.onNumChangeListener() { @Override public void onNumChange(int groupID, int childID, boolean isAdd) { //获得当前点击商品的数量 int num = groups.get(groupID).getChildren().get(childID).getNum(); if (isAdd){//加 //在数据源中该商品数量自增1 groups.get(groupID).getChildren().get(childID).setNum(++num); }else{//减 if (num == 1){//数量为1给出提示 Toast.makeText(MainActivity.this, "受不了了,不能再少了", Toast.LENGTH_SHORT).show(); }else{//在数据源中该商品数量自减1 groups.get(groupID).getChildren().get(childID).setNum(--num); } } //更新UI adapter.notifyDataSetChanged(); changeMoney();//更新价格显示 } }); //自定义商铺和商品多选框点击回调 adapter.setmOnCheckChangeListener(new MyAdapter.onCheckChangeListener() { @Override public void onGroupClick(int groupID) {//组点击 //将数据源置反,以保持同步 groups.get(groupID).setCheck(!(groups.get(groupID).isCheck())); //获取当前选中状态 boolean flag = groups.get(groupID).isCheck(); //更新其下所有商品CheckBox for (int i = 0 ; i < groups.get(groupID).getChildren().size(); i++){ groups.get(groupID).getChildren().get(i).setCheck(flag); } //更新UI adapter.notifyDataSetChanged(); //更新价格显示 changeMoney(); } @Override public void onChildClick(int groupID, int childID) {//商品点击 //将数据源置反以保持同步 groups.get(groupID).getChildren().get(childID).setCheck(!(groups.get(groupID).getChildren().get(childID).isCheck())); //判断该条目及所有兄弟条目是否全部选中,以及时更新商铺CheckBox int flag = 0; for (int i = 0 ; i < groups.get(groupID).getChildren().size() ; i++){ if (groups.get(groupID).getChildren().get(i).isCheck()){ flag++; } } //如果该组下的选中数量与该集合长度相等,说明全部选中,更新组CheckBox if (flag == groups.get(groupID).getChildren().size()){ groups.get(groupID).setCheck(true); }else{ groups.get(groupID).setCheck(false); } //更新UI adapter.notifyDataSetChanged(); //更新价格显示 changeMoney(); } }); //删除按钮点击 btnDel.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { List<Group> toBeDeleteGroups = new ArrayList<Group>();// 待删除的组元素列表 for (int i = 0; i < groups.size(); i++) { Group group = groups.get(i); if (group.isCheck()) { toBeDeleteGroups.add(group); } List<Child> toBeDeleteChildren = new ArrayList<Child>();// 待删除的子元素列表 List<Child> childs = group.getChildren(); for (int j = 0; j < childs.size(); j++) { if (childs.get(j).isCheck()) { toBeDeleteChildren.add(childs.get(j)); } } childs.removeAll(toBeDeleteChildren); } groups.removeAll(toBeDeleteGroups); //更新UI adapter.notifyDataSetChanged(); //更新当前商品数量显示 changeGoodsNum(); //更新当前价格显示 changeMoney(); } }); //购买按钮,点击提示当前选中金额 btnBuy.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { String money = price.getText().toString(); Toast.makeText(MainActivity.this, "当前总金额:"+money, Toast.LENGTH_SHORT).show(); } }); //全选按钮,点击更新视图所有CheckBox checkAll.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { for (int i = 0 ; i< groups.size() ; i++){ groups.get(i).setCheck(checkAll.isChecked()); for (int j = 0 ; j < groups.get(i).getChildren().size() ; j ++){ groups.get(i).getChildren().get(j).setCheck(checkAll.isChecked()); } } //更新UI adapter.notifyDataSetChanged(); //更新总价显示 changeMoney(); } }); //自定义回调更新总价 adapter.setmOnShouldChangeMoneyListener(new MyAdapter.onShouldChangeMoneyListener() { @Override public void onShouldChnageMoney() { //更新总价显示 changeMoney(); } }); } //初始化数据,设置适配器 private void initData() { for (int i = 0 ; i < 5 ; i++){ ArrayList<Child> children = new ArrayList<>(); for (int j = 0 ; j <= i ; j++){ children.add(new Child("店铺"+i+"的商品:"+j,"",2,false,j+1)); } groups.add(new Group("商铺:"+i,false,children)); } adapter = new MyAdapter(this,groups); expandableListView.setAdapter(adapter); for (int i = 0; i < groups.size(); i++) { expandableListView.expandGroup(i);// 初始化时,将ExpandableListView以展开的方式呈现 } } //获得控件资源 private void initView() { num = (TextView) findViewById(R.id.main_num); expandableListView = (ExpandableListView) findViewById(R.id.expand_able_view); checkAll = (CheckBox) findViewById(R.id.main_check_all); btnDel = (Button) findViewById(R.id.btn_delete); btnBuy = (Button) findViewById(R.id.btn_buy); price = (TextView) findViewById(R.id.main_price); } //当前购物车商品数量 private void changeGoodsNum(){ int currentNum = 0; for (int i = 0 ; i < groups.size(); i++){ for (int j = 0 ; j < groups.get(i).getChildren().size(); j++){ currentNum++; } } num.setText(currentNum+""); } //更新总价 private void changeMoney(){ int money = 0; int allMoney = 0;//获得当前全部商品价格 for (int i = 0 ; i < groups.size(); i++){ for (int j = 0 ; j < groups.get(i).getChildren().size(); j++){ if (groups.get(i).getChildren().get(j).isCheck()){ money += groups.get(i).getChildren().get(j).getNum() * groups.get(i).getChildren().get(j).getPrice(); } allMoney += groups.get(i).getChildren().get(j).getNum() * groups.get(i).getChildren().get(j).getPrice(); } } //当选中价格与全部价格相等,更新全选框 if (money == allMoney){ checkAll.setChecked(true); }else{ checkAll.setChecked(false); } price.setText(money + ""); } }
效果图:
加载全部内容