转载原文 http://www.cnblogs.com/xqxacm/p/4963145.html
首先,众所周知,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 2 <LinearLayout xmlns:android="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.xml
2、列表项布局
1 2 <RelativeLayout xmlns:android="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 <ImageButton 18 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 <TextView 27 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 <ImageButton 38 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 <TextView 47 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.xml
这里解决问题:列表项中存在会获取焦点的各种按钮,会导致列表项无法点击,只能点击按钮,这种情况怎么解决
解决方法,在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 @Override
16 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 products;
//上下文
private Context context;
//第一步,设置接口
private View.OnClickListener onAddNum; //加商品数量接口
private View.OnClickListener onSubNum; //减商品数量接口
接口看你具体需求,我这里是ImageButton ,所以是 View.OnClickListener
具体看情况,举三个列子,当然还有很多接口,比如单选按钮的
2、创建构造方法:
public ShopAdapter(List 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 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 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 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 = 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中观察者模式的应用 了。
转载 原文http://www.cnblogs.com/xqxacm/p/4963145.html