Android 购物车功能的实现

时间:2023-03-08 23:34:41
Android 购物车功能的实现

首先,众所周知,ListView是Android最常用的控件,可以说是最简单的控件,也可以说是最复杂的控件。

作为一个Android初级开发者,可能会简单的ListView展示图文信息。

作为一个有一定项目开发经验的Android开发者来说,可能会遇到ListView的列表项中存在各种按钮的需求。

需求最多的就是购物车功能。想必大家都用过某宝某东客户端APP吧 ,就是那个购物车的功能。

Android 购物车功能的实现

-------------------------------------------------------------------------------------------------------------

曾经做过购物车功能,今天项目需求也用到了差不多效果的购物车功能,刚好园友问了这个问题,便帮忙解答了。

之后,想了想还是写一下关于购物车效果的博客吧。

--------------------------------------------------------------------------------------------------------------

那么现在就学习一下购物车功能的实现原理

首先让我们分析下实现购物车功能需要解决的问题:

1、在哪里处理按钮的点击响应事件,是适配器 还是 Activity或者Fragment

2、如何知道你点击的按钮是哪一个列表项中的

3、点击某个按钮的时候,如果列表项所需的数据改变了,如何更新UI

4、列表项中存在会获取焦点的各种按钮,会导致列表项无法点击,只能点击按钮,这种情况怎么解决

首先,我们必须要了解:

1、自定义适配器,不会的看下博客:安卓开发_浅谈ListView(自定义适配器)

2、接口回调,不会接口回调的可以看下博客:Android接口回调机制

一个ListView数据展示的实现,必须要有的 自定义适配器,数据源,ListView,列表项布局

做一个Demo,看下效果

(1)、效果一,点击商品添加删除数量,后面的商品总价随之变化

Android 购物车功能的实现

(2)、效果二,一个列表项发生变化,滑出界面,在滑回来,该列表项的数据依然存在,列表项的复用不存在问题

Android 购物车功能的实现

一、创建布局文件

1、主布局

 <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<ListView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/listView"
/>
</LinearLayout>

main.xml

2、列表项布局

 <?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:background="#fff"
android:descendantFocusability="blocksDescendants"
>
<TextView
android:id="@+id/item_product_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:layout_margin="10dp"
android:text="商品名称"
android:textColor="#000"
/>
<ImageButton
android:id="@+id/item_btn_add"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#0000"
android:src="@drawable/add"
android:layout_below="@+id/item_product_name"
android:layout_marginLeft="10dp"
/>
<TextView
android:id="@+id/item_product_num"
android:text=""
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="25sp"
android:textColor="#000"
android:layout_margin="5dp"
android:layout_toRightOf="@id/item_btn_add"
android:layout_below="@id/item_product_name"
/>
<ImageButton
android:id="@+id/item_btn_sub"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#0000"
android:src="@drawable/sub"
android:layout_below="@id/item_product_name"
android:layout_toRightOf="@id/item_product_num"
/>
<TextView
android:id="@+id/item_product_price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:layout_margin="10dp"
android:text=""
android:textColor="#000"
android:layout_alignParentRight="true"
/> </RelativeLayout>

item_cart.xml

这里解决问题:列表项中存在会获取焦点的各种按钮,会导致列表项无法点击,只能点击按钮,这种情况怎么解决

解决方法,在item列表项布局的最外层父容器中 设置一个属性:

              android:descendantFocusability="blocksDescendants"

Android 购物车功能的实现

二、创建实体类

看上图,只需要三个属性,名称,总价格,数量

 package com.xqx.ShopDemo;

 /**
* 购物车实体类
* 测试
*/
public class Product {
//商品名称
private String name;
// 商品数量
private int num;
// 该商品总价
private int price; @Override
public String toString() {
return "Product{" +
"name='" + name + '\'' +
", num=" + num +
", price=" + price +
'}';
} public void setName(String name) {
this.name = name;
} public void setNum(int num) {
this.num = num;
} public void setPrice(int price) {
this.price = price;
} public String getName() {
return name;
} public int getNum() {
return num;
} public int getPrice() {
return price;
}
}

Product.java

三、创建适配器(关键!!)

1、创建适配器成员变量

   //集合 ,存放ListView的商品实体类数据
private List<Product> products;
//上下文
private Context context; //第一步,设置接口
private View.OnClickListener onAddNum; //加商品数量接口
private View.OnClickListener onSubNum; //减商品数量接口

 接口看你具体需求,我这里是ImageButton ,所以是 View.OnClickListener

具体看情况,举三个列子,当然还有很多接口,比如单选按钮的

Android 购物车功能的实现

2、创建构造方法:

  public ShopAdapter(List<Product> products, Context context) {
this.products = products;
this.context = context;
}

3、创建接口方法

    public void setOnAddNum(View.OnClickListener onAddNum){
this.onAddNum = onAddNum;
} public void setOnSubNum(View.OnClickListener onSubNum){
this.onSubNum = onSubNum;
}

4、重写自定义适配器的除了getView()的三个方法

@Override
public int getCount() {
int ret = ;
if (products != null) {
ret = products.size();
}
return ret;
} @Override
public Object getItem(int i) {
return products.get(i);
} @Override
public long getItemId(int i) {
return i;
}

5、接下来就是重点了

定义内部类

private static class ViewHolder{
//商品名称,数量,总价
private TextView item_product_name;
private TextView item_product_num;
private TextView item_product_price;
//增减商品数量按钮
private ImageButton item_btn_add;
private ImageButton item_btn_sub; }

重写最重要的getView()方法,主要看红色颜色部分

@Override
public View getView(int i, View view, ViewGroup viewGroup) {
View v = null;
if (view != null) {
v = view;
}else{
v = LayoutInflater.from(context).inflate(R.layout.item_cart,viewGroup,false);
} ViewHolder holder = (ViewHolder) v.getTag();
if (holder == null) {
holder = new ViewHolder();
holder.item_product_name = (TextView) v.findViewById(R.id.item_product_name);
holder.item_product_num = (TextView) v.findViewById(R.id.item_product_num);
holder.item_product_price = (TextView) v.findViewById(R.id.item_product_price); //设置接口回调,注意参数不是上下文,它需要ListView所在的Activity或者Fragment处理接口回调方法
holder.item_btn_add = (ImageButton) v.findViewById(R.id.item_btn_add);
holder.item_btn_add.setOnClickListener(onAddNum); holder.item_btn_sub = (ImageButton) v.findViewById(R.id.item_btn_sub);
holder.item_btn_sub.setOnClickListener(onSubNum); } holder.item_product_name.setText(products.get(i).getName());
holder.item_product_num.setText(products.get(i).getNum()+"");
holder.item_product_price.setText(products.get(i).getPrice() + "");
      
//设置Tag,用于判断用户当前点击的哪一个列表项的按钮,解决问题:如何知道你点击的按钮是哪一个列表项中的
holder.item_btn_add.setTag(i);
holder.item_btn_sub.setTag(i); v.setTag(holder);
return v;
}

至此,自定义适配器部分完成了。

适配器完整代码:

 import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView; import java.util.List; /**
* 购物车功能
* 适配器
*/
public class ShopAdapter extends BaseAdapter{ //集合 ,存放ListView的商品实体类数据
private List<Product> products;
//上下文
private Context context; //第一步,设置接口
private View.OnClickListener onAddNum;
private View.OnClickListener onSubNum; //第二步,设置接口方法
public void setOnAddNum(View.OnClickListener onAddNum){
this.onAddNum = onAddNum;
} public void setOnSubNum(View.OnClickListener onSubNum){
this.onSubNum = onSubNum;
}
public ShopAdapter(List<Product> products, Context context) {
this.products = products;
this.context = context;
} @Override
public int getCount() {
int ret = ;
if (products != null) {
ret = products.size();
}
return ret;
} @Override
public Object getItem(int i) {
return products.get(i);
} @Override
public long getItemId(int i) {
return i;
} @Override
public View getView(int i, View view, ViewGroup viewGroup) {
View v = null;
if (view != null) {
v = view;
}else{
v = LayoutInflater.from(context).inflate(R.layout.item_cart,viewGroup,false);
} ViewHolder holder = (ViewHolder) v.getTag();
if (holder == null) {
holder = new ViewHolder();
holder.item_product_name = (TextView) v.findViewById(R.id.item_product_name);
holder.item_product_num = (TextView) v.findViewById(R.id.item_product_num);
holder.item_product_price = (TextView) v.findViewById(R.id.item_product_price); //第三步,设置接口回调,注意参数不是上下文,它需要ListView所在的Activity或者Fragment处理接口回调方法
holder.item_btn_add = (ImageButton) v.findViewById(R.id.item_btn_add);
holder.item_btn_add.setOnClickListener(onAddNum); holder.item_btn_sub = (ImageButton) v.findViewById(R.id.item_btn_sub);
holder.item_btn_sub.setOnClickListener(onSubNum); } holder.item_product_name.setText(products.get(i).getName());
holder.item_product_num.setText(products.get(i).getNum()+"");
holder.item_product_price.setText(products.get(i).getPrice() + ""); //第四步,设置Tag,用于判断用户当前点击的哪一个列表项的按钮
holder.item_btn_add.setTag(i);
holder.item_btn_sub.setTag(i); v.setTag(holder);
return v;
}
private static class ViewHolder{
//商品名称,数量,总价
private TextView item_product_name;
private TextView item_product_num;
private TextView item_product_price;
//增减商品数量按钮
private ImageButton item_btn_add;
private ImageButton item_btn_sub; }
}

适配器代码

四、主Activity

public class MainActivity extends Activity implements View.OnClickListener, AdapterView.OnItemClickListener {

    private List<Product> datas; //数据源
private ShopAdapter adapter; //自定义适配器
private ListView listView; //ListView控件
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main); listView = (ListView) findViewById(R.id.listView); // 模拟数据
datas = new ArrayList<Product>();
Product product = null;
for (int i = ; i < ; i++) {
product = new Product();
product.setName("商品:"+i+":单价:"+i);
product.setNum();
product.setPrice(i);
datas.add(product);
}
adapter = new ShopAdapter(datas,this);
listView.setAdapter(adapter); //以上就是我们常用的自定义适配器ListView展示数据的方法了

//解决问题:在哪里处理按钮的点击响应事件,是适配器 还是 Activity或者Fragment,这里是在Activity本身处理接口
//执行添加商品数量,减少商品数量的按钮点击事件接口回调
adapter.setOnAddNum(this);
adapter.setOnSubNum(this);
listView.setOnItemClickListener(this);
}

//


3、点击某个按钮的时候,如果列表项所需的数据改变了,如何更新UI

    @Override
public void onClick(View view) {
Object tag = view.getTag();
switch (view.getId()){
case R.id.item_btn_add: //点击添加数量按钮,执行相应的处理
// 获取 Adapter 中设置的 Tag
if (tag != null && tag instanceof Integer) { //解决问题:如何知道你点击的按钮是哪一个列表项中的,通过Tag的position
int position = (Integer) tag;
//更改集合的数据
int num = datas.get(position).getNum();
num++;
datas.get(position).setNum(num); //修改集合中商品数量
datas.get(position).setPrice(position*num); //修改集合中该商品总价 数量*单价
            //解决问题:点击某个按钮的时候,如果列表项所需的数据改变了,如何更新UI
adapter.notifyDataSetChanged();
}
break;
case R.id.item_btn_sub: //点击减少数量按钮 ,执行相应的处理
// 获取 Adapter 中设置的 Tag
if (tag != null && tag instanceof Integer) {
int position = (Integer) tag;
//更改集合的数据
int num = datas.get(position).getNum();
if (num>) {
num--;
datas.get(position).setNum(num); //修改集合中商品数量
datas.get(position).setPrice(position * num); //修改集合中该商品总价 数量*单价
adapter.notifyDataSetChanged();
}
}
break;
}
}
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
Toast.makeText(MainActivity.this,"点击了第"+i+"个列表项",Toast.LENGTH_SHORT).show();
}
}

----------------------------------------------------------------------------------------------------

总结下:

1、有人说列表项中最好不要用ImageButton,而尽可能的用ImageView替代,目前没有发现使用ImageButton会发生什么错误

2、有人说列表项中 解决焦点问题需要两步:

(1)、最外层父容器需要加属性:

android:descendantFocusability="blocksDescendants"

(2)、能获取焦点的控件,Button,ImageButton等等  需要 有属性:android:focusable="false"

但是我实际测试 发现子空间不需要设置focusable属性也不会产生问题,当然加上也没有问题

3、没有做过列表项中存在EditText控件的情况,可能会有焦点冲突。毕竟购物车中加一个编辑框也很少见

最后,一个实际的购物车,当然还需要显示当前的总金额,包含“去结算”按钮的功能的那一个框,这不属于ListView

如图:

Android 购物车功能的实现

那么怎么处理当你操作列表项中的按钮,不仅列表项中的数据发生变哈,而且不属于列表项的下面部分的“合计”数据也发生变化呢,

这就要学习Adapter中观察者模式的应用 了。

---------------------------------------------------------------------------------------------

以上内容,如有错误,欢迎指出!