实现一个非常简单的购物车模块,这个内部可能还是可能会有一些小bug的出现,希望有大神看出来提出更正,必当心存感激
首先:布局是LinearLayout,是内部是一个ExpandableListView+底部的一个LinearLayout组成,RelativeLayout中有一个Checkbox和一个表示总价的TextView外加一个结算按钮。使用权重进行控件的大小分配是1-9分,ExpandableListView占九成LinearLayout占一成(内部控件另有其他排列方式)
<ExpandableListView
android:id="@+id/explv"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="9"
android:groupIndicator="@null">
</ExpandableListView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="horizontal"
android:gravity="center_vertical">
<CheckBox
android:id="@+id/main_all_checked"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="全选"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:text="总价格是:¥"/>
<TextView
android:id="@+id/main_all_Sum"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0.0"/>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical">
<Button
android:id="@+id/main_all_pay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:text="买单(¥0.0)"/>
</RelativeLayout>
两个条目的布局
<!--GroupItem用的是横向的LinearLayout-->
<CheckBox
android:id="@+id/group_checked"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusable="false"/>
<TextView
android:id="@+id/group_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/app_name"/>
<!--ChildItem用的也是横向的LinearLayout-->
<CheckBox
android:id="@+id/child_checked"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="100dp">
<ImageView
android:id="@+id/child_image"
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@mipmap/ic_launcher"/>
<TextView
android:id="@+id/child_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/child_image"
android:textSize="20sp"
android:text="@string/app_name"/>
<TextView
android:id="@+id/child_price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/child_image"
android:layout_alignParentBottom="true"
android:layout_marginLeft="30dp"
android:textColor="#9F0F"
android:text="单价"/>
<!--此处一个简单的自定义控件 加减值,中间一数字的控件(组合式自定义控件)-->
<com.liu.get.shoppingcart.ui.MyView
android:id="@+id/child_my_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true">
</com.liu.get.shoppingcart.ui.MyView>
</RelativeLayout>
ps:上边使用的自定义控件是一个组合式自定义控件,其样式是:两个Button中间放置一个TextView 简单的模仿一下XX购物车的加减数量功能 布局过于简单就不写了,把自定义控件的类写一下起的名字是MyView继承了一个LinearLayout
private Button my_add;//添加按钮
private TextView my_nums;//中间的数字
private Button my_sub;//减少按钮
int nums=0;//数字
public int getNums(){//用于外界获取该控件的数字
return nums;
}
public MyView(Context context) {//调用自身不再多说
this(context, null);
}
public MyView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//将一个布局转换成一个viwe 注意 后边要写this 不然控件没有效果也就是不展示
View view = inflate(context, R.layout.my_view, this);
//寻找控件内的三个view 实现增减功能
my_add = view.findViewById(R.id.my_add);
my_nums = view.findViewById(R.id.my_nums);
my_sub = view.findViewById(R.id.my_sub);
my_add.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {//增加按钮
nums++;
my_nums.setText(""+nums);
mMyViewGetData.Getdata(nums);//接口方法,数字改变后给外界一个反应,并使其得到值
}
});
my_sub.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {//减少按钮
if(nums>0){//控制数字不会小于0
nums--;
my_nums.setText(""+nums);
mMyViewGetData.Getdata(nums);
}
}
});
}
public void setNums(int numb){//外界修改内部的text
nums = numb;
my_nums.setText(""+nums);
}
MyViewGetData mMyViewGetData;//内部实现的一个接口 接口的固定形式
public interface MyViewGetData{
void Getdata(int num);
}
public void setMyViewGetData(MyViewGetData myViewGetData){
mMyViewGetData = myViewGetData;
}
布局完成后要做的就是在activity中初始化初始化这里使用的是MainActivity直接进行初始化,fragment还需要先获取布局信息,不再多说(main中并没有什么太多的逻辑主要在adapter中)
注意:不论在activity中还是adapter的复选框的状态转换和点击都不要写setOnCheckedChangeListener();这个方法,不然会出现逻辑的冲突执行
//这里是完成购物车后的代码,全部放入了
public class MainActivity extends AppCompatActivity implements View.OnClickListener,NumAndMoney {
//暂时没有接口的同事可以用这个接口http://www.zhaoapi.cn/product/getCarts?uid=
private String url = "假设这个是一个Url(需要使用post请求的Url)";
private ExpandableListView explv;
private CheckBox main_all_checked;
private TextView main_all_Sum;
private Button main_all_pay;
private Beans beans;
ExpLvAdapter adapter;//ExpandableListView的adapter
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();//初始化控件
initData();//初始化数据
main_all_checked.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {//判断全选按钮是否选中
int s = 0;
if(isChecked){
s = 1;
}else{
s = 0;
}//其实可以写三元运算符……没有写
adapter.QuanXuan(s);
}
});
}
private void initData() {//初始化数据
HashMap<String, String> map = new HashMap<>();
map.put("uid", "71");//使用的是post请求所以要使用一个map集合传入数据进行一个formbody的封装提高请求的安全性
HttpUtil.getInstance().doPost(url, map, new HttpUtilInterface() {//利用接口回调获取到请求的状态和请求的结果
@Override
public void Failure() {//失败的方法
Toast.makeText(MainActivity.this, "请求失败", Toast.LENGTH_SHORT).show();
}
@Override
public void Response(String json) {//请求成功并解析其中的json数据
beans = new Gson().fromJson(json, Beans.class);//返回bean类
//获取bean中的集合传入到adapter修改数据
adapter.setList(beans.getData());
explv.setAdapter(adapter);
///展开所有分组 必须写到setAdapter之后
for (int i = 0; i < adapter.getGroupCount(); i++) {
explv.expandGroup(i);
}
}
});
}
private void initView() {//初始化所有控件
explv = (ExpandableListView) findViewById(R.id.explv);
main_all_checked = (CheckBox) findViewById(R.id.main_all_checked);
main_all_Sum = (TextView) findViewById(R.id.main_all_Sum);
main_all_pay = (Button) findViewById(R.id.main_all_pay);
adapter = new ExpLvAdapter(MainActivity.this,this);
explv.setAdapter(adapter);
main_all_pay.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.main_all_pay:
//支付的按钮,并没有写支付的逻辑
break;
}
}
@Override
public void AllNumAndMoey(int num, float money) {
//回调的接口。用于修改main中的总金额和选中数量
main_all_Sum.setText("¥"+money);
main_all_pay.setText("总计("+num+"个)");
//用于改变全选按钮的选中状态
main_all_checked.setChecked(adapter.isAllChecked());
}
}
接下来就是数据请求的okhttp(同样也只是简单的封装一下,没有添加网络日志拦截器)起名为HttpUtil(网络工具类),进行了简单的单例处理和封装一个post请求
注:Okhttp3需要导包---------- implementation ‘com.squareup.okhttp3:okhttp:3.12.0’
public class HttpUtil {
private static HttpUtil mHttpUtil;
private final Handler handler;
private final OkHttpClient client;
//接口↓
private HttpUtilInterface mHttpUtilInterface;
private HttpUtil() {
//os包下的Handler
handler = new Handler(Looper.getMainLooper());
//用于全局初始化
client = new OkHttpClient.Builder()
.connectTimeout(5, TimeUnit.SECONDS)
.writeTimeout(5, TimeUnit.SECONDS)
.readTimeout(5, TimeUnit.SECONDS)
.build();
}
//实现单例
public static HttpUtil getInstance() {
if (mHttpUtil == null) {
synchronized (HttpUtil.class) {
if (mHttpUtil == null) {
mHttpUtil = new HttpUtil();
}
}
}
return mHttpUtil;
}
//封装的post请求----HttpUtilInterface接口
public void doPost(String url, Map<String, String> map, final HttpUtilInterface httpUtilInterface) {
FormBody.Builder form = new FormBody.Builder();
if (map != null) {//防止请求数据为空造成一些bug
for (String key : map.keySet()) {
//将map集合放入请求体
form.add(key, map.get(key));
}
}
//请求体
FormBody body = form.build();
Request request = new Request.Builder()
.post(body)//post请求
.url(url)
.build();
//获取Call对象,并获取成功值
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {//请求失败
handler.post(new Runnable() {
@Override
public void run() {
httpUtilInterface.Failure();
}
});
}
@Override
public void onResponse(Call call, Response response) throws IOException {
final String json = response.body().string();
handler.post(new Runnable() {//请求成功
@Override
public void run() {
httpUtilInterface.Response(json);
}
});
}
});
}
}
注:接口之中只有两个方法----成功和失败 并没有传入博客
void Response(String json);
void Failure();
好了,布局和网络请求类已经写完了,所以目前的准备工作已经做到位了。接下来就是再adapter中写购物车的逻辑(整个购物车的精髓和难点都在这里,其中都是个人想的,难免会出现逻辑的不合理性,希望有大神看到并提醒我,使我改正,谢谢)
adapter继承的是BaseExpandableListAdapter 最后的六个方法可以暂时不做修改 主要是目前这个购物车还用不到这六个方法所以暂不修改减少写的代码量吧(虽然不费什么时间)
public class ExpLvAdapter extends BaseExpandableListAdapter {
ArrayList<Beans.DataBean> list;//获取的集合
Context mContext;//MainActivity中传来的上下文
//↓用于传值的接口 接口功能是通过这个接口将选中数据的总价和选中商品的总数量返回到Main中去进行修改
NumAndMoney mNumAndMoney;
public ExpLvAdapter(Context context,NumAndMoney numAndMoney) {
mNumAndMoney = numAndMoney;//传来的接口(必须有不然空指针)
mContext = context;//传来的上下文
list = new ArrayList<>();//list初始化
}
public void setList(ArrayList<Beans.DataBean> data) {
list.clear();//清空集合,防止东西越来越多
list.addAll(data);//添加集合数据
notifyDataSetChanged();//更新适配器
}
@Override
public int getGroupCount() {
GetNumAndMoney();//计算选中的数据的值。因为每一次选中和不选中都会更新adapter所以需要找个位置去更新数量和和总价和,暂且选择此位置。如果有新的建议留言告知,我会修改
return list.size();
}
@Override
public int getChildrenCount(int groupPosition) {
return list.get(groupPosition).getList().size();
}
@Override
public View getGroupView(final int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
//GroupHolder 获取组的Holper类用于优化
final GroupHolder holder;
if (convertView == null) {//↓寻找布局
convertView = View.inflate(mContext, R.layout.group_item, null);
holder = new GroupHolder(convertView);
convertView.setTag(holder);
} else {
holder = (GroupHolder) convertView.getTag();
}
//修改组的数据 只有一个title 加一个复选框
holder.group_name.setText(list.get(groupPosition).getSellerName());
//GroupChecked(groupPosition)是判断改组内的所有组员是否全部选中,是则选中组标签,反之亦反之
if(GroupChecked(groupPosition)){
holder.group_checked.setChecked(true);
}else{
holder.group_checked.setChecked(false);
}
//组的复选框的点击事件 用于选中所有组内成员 或者全部不选
holder.group_checked.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
boolean isChecked = holder.group_checked.isChecked();
//获取复选框的选中状态↑ 修改选中状态↓
holder.group_checked.setChecked(!isChecked);
for (int i = 0; i <list.get(groupPosition).getList().size(); i++) {
if(isChecked){
list.get(groupPosition).getList().get(i).setSelected(1);
}else{
list.get(groupPosition).getList().get(i).setSelected(0);
}
}//↓更新数据
notifyDataSetChanged();
}
});
return convertView;
}
@Override
public View getChildView(final int groupPosition, final int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
//子布局的帮助类 用于优化子布局
ChildHolder holder;
if (convertView == null) {
convertView = View.inflate(mContext, R.layout.child_item, null);
holder = new ChildHolder(convertView);
convertView.setTag(holder);
} else {
holder = (ChildHolder) convertView.getTag();
}
//修改组内数据 主要是因为子条目布局内数据相对较多, 写在了holper内
holder.setData(list.get(groupPosition).getList().get(childPosition));
//自定义控件数量加减 修改数据
holder.child_my_view.setMyViewGetData(new MyView.MyViewGetData() {
@Override
public void Getdata(int num) {
list.get(groupPosition).getList().get(childPosition).setNum(num);
//要想修改生效就得更新适配器
notifyDataSetChanged();
}
});
//子条目的复选框的修改
holder.child_checked.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if(isChecked){
list.get(groupPosition).getList().get(childPosition).setSelected(1);
}else{
list.get(groupPosition).getList().get(childPosition).setSelected(0);
}
//同样的要更新适配器不然修改后滑动会导致复选框选中后滑动然后补选中的bug
notifyDataSetChanged();
}
});
return convertView;
}
public void QuanXuan(int ischecked){//全选的方法
for (int i = 0; i < list.size(); i++) {
for(int j = 0 ; j < list.get(i).getList().size(); j++){
list.get(i).getList().get(j).setSelected(ischecked);
}
}
//同样也要更新数据
notifyDataSetChanged();
}
public boolean GroupChecked(int index){//判断该组的复选框是否选中
for(int j = 0 ; j < list.get(index).getList().size(); j++){
int selected = list.get(index).getList().get(j).getSelected();
if(selected == 0){//如果有一个未选中就会不选中
return false;
}
}
return true;
}
public void GetNumAndMoney(){//计算总价和总量
float moneys = 0;
int nums = 0;
for (int i = 0; i < list.size(); i++) {
for (int j = 0; j < list.get(i).getList().size(); j++) {
int selected = list.get(i).getList().get(j).getSelected();
if(selected == 1){
int num = list.get(i).getList().get(j).getNum();
double price = list.get(i).getList().get(j).getPrice();
moneys += (float)(num*price);
nums += num;
}
}
}
/*接口---回调信息*/
mNumAndMoney.AllNumAndMoey(nums,moneys);
}
//判断所有商品是否都选中,用于改变全选按钮的状态
//再Git中并没有这个方法
public boolean isAllChecked() {
for (int i = 0; i < list.size(); i++) {
for (int j = 0; j < list.get(i).getList().size(); j++) {
int selected = list.get(i).getList().get(j).getSelected();
if (selected == 0) {
return false;
}
}
}
return true;
}
public static class GroupHolder {//组的帮助类
public View rootView;
public CheckBox group_checked;
public TextView group_name;
public GroupHolder(View rootView) {
this.rootView = rootView;
this.group_checked = (CheckBox) rootView.findViewById(R.id.group_checked);
this.group_name = (TextView) rootView.findViewById(R.id.group_name);
}
}
public static class ChildHolder {//组员的帮助类
public View rootView;
public CheckBox child_checked;
public ImageView child_image;
public TextView child_name;
public TextView child_price;
public MyView child_my_view;
public ChildHolder(View rootView) {
this.rootView = rootView;
this.child_checked = (CheckBox) rootView.findViewById(R.id.child_checked);
this.child_image = (ImageView) rootView.findViewById(R.id.child_image);
this.child_name = (TextView) rootView.findViewById(R.id.child_name);
this.child_price = (TextView) rootView.findViewById(R.id.child_price);
this.child_my_view = (MyView) rootView.findViewById(R.id.child_my_view);
}
public void setData(final Beans.DataBean.ListBean listBean) {
child_name.setText(listBean.getTitle());
child_price.setText(listBean.getPrice()+"¥");
child_my_view.setNums(listBean.getNum());
if(listBean.getSelected()==1){//用于判断这个复选框是否选中(三元运算符亦可)
child_checked.setChecked(true);
}else{
child_checked.setChecked(false);
}
}
}
/
//下列方法暂不修改,暂时用不上//
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return false;
}
@Override
public boolean hasStableIds() {
return false;
}
@Override
public Object getGroup(int groupPosition) {
return null;
}
@Override
public Object getChild(int groupPosition, int childPosition) {
return null;
}
@Override
public long getGroupId(int groupPosition) {
return 0;
}
@Override
public long getChildId(int groupPosition, int childPosition) {
return 0;
}
}
接口中也仅仅只有一个方法 接口名NumAndMoney
内方法是void AllNumAndMoey(int num,float money);
github(源码)链接:
ShoppingCar