你的位置:首页 > 操作系统

[操作系统]Android 购物车功能的实现


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

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

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

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

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

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

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

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

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

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

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

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

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

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

 

首先,我们必须要了解:

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

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

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

做一个Demo,看下效果

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

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

一、创建布局文件

1、主布局

 1 <?"1.0" encoding="utf-8"?> 2 <LinearLayout "http://schemas.android.com/apk/res/android" 3        android:orientation="vertical" 4        android:layout_width="fill_parent" 5        android:layout_height="fill_parent" 6     > 7   <ListView 8       android:layout_width="fill_parent" 9       android:layout_height="wrap_content"10       android:id="@+id/listView"11       />12 </LinearLayout>

main.

2、列表项布局

 1 <?"1.0" encoding="utf-8"?> 2 <RelativeLayout "http://schemas.android.com/apk/res/android" 3        android:layout_width="match_parent" 4        android:layout_height="match_parent" 5        android:background="#fff" 6        android:descendantFocusability="blocksDescendants" 7     > 8   <TextView 9       android:id="@+id/item_product_name"10       android:layout_width="wrap_content"11       android:layout_height="wrap_content"12       android:textSize="20sp"13       android:layout_margin="10dp"14       android:text="商品名称"15       android:textColor="#000"16       />17   <ImageButton18       android:id="@+id/item_btn_add"19       android:layout_width="wrap_content"20       android:layout_height="wrap_content"21       android:background="#0000"22       android:src="@drawable/add"23       android:layout_below="@+id/item_product_name"24       android:layout_marginLeft="10dp"25       />26   <TextView27       android:id="@+id/item_product_num"28       android:text="1"29       android:layout_width="wrap_content"30       android:layout_height="wrap_content"31       android:textSize="25sp"32       android:textColor="#000"33       android:layout_margin="5dp"34       android:layout_toRightOf="@id/item_btn_add"35       android:layout_below="@id/item_product_name"36       />37   <ImageButton38       android:id="@+id/item_btn_sub"39       android:layout_width="wrap_content"40       android:layout_height="wrap_content"41       android:background="#0000"42       android:src="@drawable/sub"43       android:layout_below="@id/item_product_name"44       android:layout_toRightOf="@id/item_product_num"45       />46   <TextView47       android:id="@+id/item_product_price"48       android:layout_width="wrap_content"49       android:layout_height="wrap_content"50       android:textSize="20sp"51       android:layout_margin="10dp"52       android:text="0"53       android:textColor="#000"54       android:layout_alignParentRight="true"55       />56 57 </RelativeLayout>

item_cart.

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

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

       android:descendantFocusability="blocksDescendants"

 

二、创建实体类

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

 1 package com.xqx.ShopDemo; 2  3 /** 4  * 购物车实体类 5  * 测试 6 */ 7 public class Product { 8   //商品名称 9   private String name;10   // 商品数量11   private int num;12   // 该商品总价13   private int price;14 15   @Override16   public String toString() {17     return "Product{" +18         "name='" + name + '\'' +19         ", num=" + num +20         ", price=" + price +21         '}';22   }23 24   public void setName(String name) {25     this.name = name;26   }27 28   public void setNum(int num) {29     this.num = num;30   }31 32   public void setPrice(int price) {33     this.price = price;34   }35 36   public String getName() {37     return name;38   }39 40   public int getNum() {41     return num;42   }43 44   public int getPrice() {45     return price;46   }47 }

Product.java

 

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

1、创建适配器成员变量

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

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

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

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 = 0;    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;  }

 

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

适配器完整代码:

 1 import android.content.Context; 2 import android.view.LayoutInflater; 3 import android.view.View; 4 import android.view.ViewGroup; 5 import android.widget.BaseAdapter; 6 import android.widget.ImageButton; 7 import android.widget.ImageView; 8 import android.widget.TextView; 9  10 import java.util.List; 11  12 /** 13  * 购物车功能 14  * 适配器 15 */ 16 public class ShopAdapter extends BaseAdapter{ 17  18   //集合 ,存放ListView的商品实体类数据 19   private List<Product> products; 20   //上下文 21   private Context context; 22  23   //第一步,设置接口 24   private View.OnClickListener onAddNum; 25   private View.OnClickListener onSubNum; 26  27   //第二步,设置接口方法 28   public void setOnAddNum(View.OnClickListener onAddNum){ 29     this.onAddNum = onAddNum; 30   } 31  32   public void setOnSubNum(View.OnClickListener onSubNum){ 33     this.onSubNum = onSubNum; 34   } 35   public ShopAdapter(List<Product> products, Context context) { 36     this.products = products; 37     this.context = context; 38   } 39  40   @Override 41   public int getCount() { 42     int ret = 0; 43     if (products != null) { 44       ret = products.size(); 45     } 46     return ret; 47   } 48  49   @Override 50   public Object getItem(int i) { 51     return products.get(i); 52   } 53  54   @Override 55   public long getItemId(int i) { 56     return i; 57   } 58  59   @Override 60   public View getView(int i, View view, ViewGroup viewGroup) { 61     View v = null; 62     if (view != null) { 63       v = view; 64     }else{ 65       v = LayoutInflater.from(context).inflate(R.layout.item_cart,viewGroup,false); 66     } 67  68     ViewHolder holder = (ViewHolder) v.getTag(); 69     if (holder == null) { 70       holder = new ViewHolder(); 71       holder.item_product_name = (TextView) v.findViewById(R.id.item_product_name); 72       holder.item_product_num = (TextView) v.findViewById(R.id.item_product_num); 73       holder.item_product_price = (TextView) v.findViewById(R.id.item_product_price); 74  75       //第三步,设置接口回调,注意参数不是上下文,它需要ListView所在的Activity或者Fragment处理接口回调方法 76       holder.item_btn_add = (ImageButton) v.findViewById(R.id.item_btn_add); 77       holder.item_btn_add.setOnClickListener(onAddNum); 78  79       holder.item_btn_sub = (ImageButton) v.findViewById(R.id.item_btn_sub); 80       holder.item_btn_sub.setOnClickListener(onSubNum); 81  82     } 83  84     holder.item_product_name.setText(products.get(i).getName()); 85     holder.item_product_num.setText(products.get(i).getNum()+""); 86     holder.item_product_price.setText(products.get(i).getPrice() + ""); 87  88     //第四步,设置Tag,用于判断用户当前点击的哪一个列表项的按钮 89     holder.item_btn_add.setTag(i); 90     holder.item_btn_sub.setTag(i); 91  92     v.setTag(holder); 93     return v; 94   } 95   private static class ViewHolder{ 96     //商品名称,数量,总价 97     private TextView item_product_name; 98     private TextView item_product_num; 99     private TextView item_product_price;100     //增减商品数量按钮101     private ImageButton item_btn_add;102     private ImageButton item_btn_sub;103 104   }105 }

适配器代码

四、主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 = 0; i < 30; i++) {      product = new Product();      product.setName("商品:"+i+":单价:"+i);      product.setNum(1);      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>0) { 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

如图:

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

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

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

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