版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xiaxiazaizai01/article/details/64129831
前段时间见群里有个小伙伴,发了一张电商项目中比较常见的购物车列表的效果图,问这样的购物车列表如何实现?我们第一反应就是用ExpandableListView来实现,在上一篇博客中我们详细的分析了比较实用而且又炫酷的 RecyclerView的ItemDecoration的知识,用上篇博客的思路也能实现今天的内容,只不过用RecyclerView的ItemDecoration通过对item设置padding值的方式来预留空间,但是无法设置点击事件。如果你有什么好的实现方式,欢迎指点。本篇文章则是采用ExpandableListView来实现,实现起来不难,但是关于不同情况的选中状态等的处理,还是需要仔细考虑周全的。
老规矩,无图无真相,先来一张效果图
整体功能需求分析
- 购物车列表中一般都是一家店铺(Group)下面会有几个商品(Child)
- 点击店铺为选中状态时,那么该店铺其下的所有商品应都为选中状态
- 将某一店铺下面的所有商品都点击为选中状态时,那么该店铺也应该变成选中状态,同理,当该店铺下面的商品有一个未选中时,那么该店铺的状态就要变成未选中
- 购物车中所有的店铺都为选中状态时,或者所有的商品都为选中状态时,则表示全选
其实,主要相对麻烦点的就是各种状态的判断。下面我们来看下adapter的实现,通过继承BaseExpandableListAdapter,处理父(Group组)与 子(child)的逻辑实现。
public class GoodsExpandableListAdapter extends BaseExpandableListAdapter{
private Context context;
private List<GoodsBean> lists;
private LayoutInflater inflater;
private CustomDialog dialog;
private OnSelectedAllListner listener;//回调接口
private boolean isSelectedAll = false;
public GoodsExpandableListAdapter(Context context) {
this.context = context;
inflater = LayoutInflater.from(context);
}
public void refreshDatas(List<GoodsBean> lists){
this.lists = lists;
notifyDataSetChanged();
}
@Override
public int getGroupCount() {
return null != lists ? lists.size() : 0;
}
@Override
public int getChildrenCount(int groupPosition) {
return null != lists ? lists.get(groupPosition).getGoods().size() : 0;
}
@Override
public Object getGroup(int groupPosition) {
return lists.get(groupPosition);
}
@Override
public Object getChild(int groupPosition, int childPosition) {
return lists.get(groupPosition).getGoods().get(childPosition);
}
@Override
public long getGroupId(int groupPosition) {
return groupPosition;
}
@Override
public long getChildId(int groupPosition, int childPosition) {
return childPosition;
}
@Override
public boolean hasStableIds() {
return false;
}
@Override
public View getGroupView(final int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
final GroupViewHolder holder;
if(convertView == null){
convertView = inflater.inflate(R.layout.item_group_listview, parent, false);
holder = new GroupViewHolder();
holder.tvShopName = (TextView) convertView.findViewById(R.id.tv_shopName);
holder.ivGroupCheck = (ImageView) convertView.findViewById(R.id.iv_check_group);
convertView.setTag(holder);
}else{
holder = (GroupViewHolder) convertView.getTag();
}
final GoodsBean bean = lists.get(groupPosition);
holder.tvShopName.setText(bean.getShopName());
selectedItem(bean.isGroupSelected(), holder.ivGroupCheck);
holder.ivGroupCheck.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
boolean isGroupSelected = !bean.isGroupSelected();
bean.setGroupSelected(isGroupSelected);//组的状态
for(int i = 0; i < lists.get(groupPosition).getGoods().size(); i++){
lists.get(groupPosition).getGoods().get(i).setChildSelected(isGroupSelected);//子的状态
}
//判断所有组是不是都选中了,都选中的话,通过接口告诉主界面的全选控件,并让其为选中状态的图片
boolean isAllGroup = isAllGroupSelected(lists);
if(listener != null){
listener.isSelectedAll(isAllGroup);
}
notifyDataSetChanged();
}
});
return convertView;
}
@Override
public View getChildView(final int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
final ChildViewHolder holder;
if(convertView == null){
convertView = inflater.inflate(R.layout.item_child_listview, parent, false);
holder = new ChildViewHolder();
holder.ivGoodsUrl = (ImageView) convertView.findViewById(R.id.iv_goods_url);
holder.tvGoodsName = (TextView) convertView.findViewById(R.id.tv_goods_name);
holder.tvGoodsOriPrice = (TextView) convertView.findViewById(R.id.tv_goods_ori_price);
holder.tvGoodsPrice = (TextView) convertView.findViewById(R.id.tv_goods_price);
holder.tvGoodsNum = (TextView) convertView.findViewById(R.id.tv_goods_num);
holder.ivChildCheck = (ImageView) convertView.findViewById(R.id.iv_check_child);
convertView.setTag(holder);
}else{
holder = (ChildViewHolder) convertView.getTag();
}
final GoodsBean.GoodsDetailsBean bean = lists.get(groupPosition).getGoods().get(childPosition);
holder.tvGoodsName.setText(bean.getGoodsName());
holder.tvGoodsOriPrice.setText(bean.getGoodsOriPrice());
holder.tvGoodsPrice.setText(bean.getGoodsPrice());
holder.tvGoodsNum.setText(bean.getGoodsNum());
//这里用本地的测试图片,正式开发则需要从网络取
holder.ivGoodsUrl.setImageResource(bean.getGoodsUrl());
selectedItem(bean.isChildSelected(), holder.ivChildCheck);
holder.ivChildCheck.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
boolean isChildSelected = !bean.isChildSelected();
bean.setChildSelected(isChildSelected);//子项的选中与未选中
//还需要进一步处理子项状态导致的组的状态问题,如果某一组的子项都选中的话,那么所在的组也为选中状态
boolean isSelectedGroup = isAllChildSelected(lists.get(groupPosition).getGoods());
lists.get(groupPosition).setGroupSelected(isSelectedGroup);
//因为子项状态会影响组的状态,判断所有组是不是都选中了,都选中的话,通过接口告诉主界面的全选控件,并让其为选中状态的图片
boolean isAllGroup = isAllGroupSelected(lists);
if(listener != null){
listener.isSelectedAll(isAllGroup);
}
notifyDataSetChanged();
}
});
convertView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//弹窗 dialog
dialog = new CustomDialog.Builder(context)
.setContent("你确定要删除该商品吗?")
.setLeftText("容朕三思")
.setRightText("朕意已决")
.create();
dialog.show();
}
});
return convertView;
}
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return false;
}
class GroupViewHolder{
TextView tvShopName;//店铺名称
ImageView ivGroupCheck;//选择按钮
}
class ChildViewHolder{
ImageView ivGoodsUrl;//商品图片url
TextView tvGoodsName;//商品名称
TextView tvGoodsOriPrice;//原价
TextView tvGoodsPrice;//实际价格
TextView tvGoodsNum;//购买数量
ImageView ivChildCheck;//选择按钮
}
}
下面再来说说稍微有点麻烦的关于选中状态与未选中状态以及全选的逻辑处理,这里封装了几个方法,并且配合着上面给出的adapter中点击事件里的选中状态的逻辑,相互配合即可实现类似京东中的效果
/**
* 选中与未选中状态下对应的图片状态的切换
* @param isSelected 是否选中
* @param iv 图片
*/
private void selectedItem(boolean isSelected, ImageView iv){
if(isSelected){
iv.setImageResource(R.drawable.check_selected);
}else{
iv.setImageResource(R.drawable.check_default);
}
}
/**
* 所有组是不是都为选中状态,因为某一组选中的话,那么该组其下的所有子项也都选中了
* @param mLists
* @return true:所有组都选中,相当于全选
*/
private boolean isAllGroupSelected(List<GoodsBean> mLists){
for(int i = 0; i < mLists.size(); i++){
boolean isGroupSelected = mLists.get(i).isGroupSelected();
if(!isGroupSelected){
return false;
}
}
return true;
}
/**
* 组内所有的子项是否都选中
* @param childLists
* @return true:表示某一组内所有的子项都选中
*/
private boolean isAllChildSelected(List<GoodsBean.GoodsDetailsBean> childLists){
for(int i = 0; i < childLists.size(); i++){
boolean isChildSelected = childLists.get(i).isChildSelected();
if(!isChildSelected){
return false;
}
}
return true;
}
/**
* 全选
* @param lists
* @param isSelectedAll
* @param iv
* @return
*/
public boolean isSelectedAll(List<GoodsBean> lists, boolean isSelectedAll, ImageView iv){
isSelectedAll = !isSelectedAll;
selectedItem(isSelectedAll, iv);
for(int i = 0; i < lists.size(); i++){
lists.get(i).setGroupSelected(isSelectedAll);
for(int j = 0; j < lists.get(i).getGoods().size(); j++){
lists.get(i).getGoods().get(j).setChildSelected(isSelectedAll);
}
}
return isSelectedAll;
}
下面再来分析下全选的情况,我们可以通过接口的方法,将全部选中时的状态传递给MainActivity
public interface OnSelectedAllListner{
void isSelectedAll(boolean flag);
}
public void setOnSelectedAllListner(OnSelectedAllListner listener){
this.listener = listener;
}
public View.OnClickListener getAdapterOnClickListener(){
return clickListener;
}
View.OnClickListener clickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.iv_check_all: //主界面中全选按钮控件的id
isSelectedAll = isSelectedAll(lists, isSelectedAll, (ImageView) v);
notifyDataSetChanged();
break;
}
}
};
最后看下MainActivity的代码,顺便模拟一些数据做测试
public class MainActivity extends AppCompatActivity implements GoodsExpandableListAdapter.OnSelectedAllListner{
private ExpandableListView expandableListView;
private GoodsExpandableListAdapter adapter;
private List<GoodsBean> groupLists;//父类
//private Map<String, List<GoodsBean.GoodsDetailsBean>> map;
private ImageView ivCheckAll;//全选的图片按钮
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化控件
iniViews();
//测试数据
setDatas();
//初始化adapter
adapter = new GoodsExpandableListAdapter(this);
expandableListView.setAdapter(adapter);
adapter.refreshDatas(groupLists);
//展开所有分组
for(int i = 0; i < groupLists.size(); i++){
expandableListView.expandGroup(i);
}
//设置ExpandableListView的样式 去掉默认箭头 以及禁止原有的展开关闭功能
expandableListView.setGroupIndicator(null);
expandableListView.setOnGroupClickListener(new ExpandableListView.OnGroupClickListener() {
@Override
public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) {
return true;
}
});
//设置adapter的监听事件
adapter.setOnSelectedAllListner(this);
//通过监听器关联Activity和Adapter的关系
View.OnClickListener listener = adapter.getAdapterOnClickListener();
if(listener != null){
ivCheckAll.setOnClickListener(listener);
}
}
private void iniViews() {
expandableListView = (ExpandableListView) findViewById(R.id.expand_listview);
ivCheckAll = (ImageView) findViewById(R.id.iv_check_all);//全选
}
private void setDatas() {
groupLists = new ArrayList<>();
//child 类
List<GoodsBean.GoodsDetailsBean> childBeanLists1 = new ArrayList<>();
GoodsBean.GoodsDetailsBean childBean1 = new GoodsBean.GoodsDetailsBean(R.drawable.product, "时尚背包 最新潮流 黑色", "¥ 360", "¥ 260", "2");
childBeanLists1.add(childBean1);
//最外层实体类
GoodsBean bean = new GoodsBean("a1", "京东店铺", childBeanLists1);
groupLists.add(bean);
List<GoodsBean.GoodsDetailsBean> childBeanLists2 = new ArrayList<>();
GoodsBean.GoodsDetailsBean childBean2 = new GoodsBean.GoodsDetailsBean(R.drawable.product, "时尚背包 最新潮流 黑色", "¥ 200", "¥ 100", "3");
GoodsBean.GoodsDetailsBean childBean22 = new GoodsBean.GoodsDetailsBean(R.drawable.product, "时尚背包 最新潮流 黑色", "¥ 150", "¥ 90", "6");
childBeanLists2.add(childBean2);
childBeanLists2.add(childBean22);
//最外层实体类
GoodsBean bean1 = new GoodsBean("a2", "淘宝店铺", childBeanLists2);
groupLists.add(bean1);
List<GoodsBean.GoodsDetailsBean> childBeanLists3 = new ArrayList<>();
GoodsBean.GoodsDetailsBean childBean3 = new GoodsBean.GoodsDetailsBean(R.drawable.product, "时尚背包 最新潮流 黑色", "¥ 500", "¥ 300", "3");
GoodsBean.GoodsDetailsBean childBean31 = new GoodsBean.GoodsDetailsBean(R.drawable.product, "时尚背包 最新潮流 黑色", "¥ 650", "¥ 500", "2");
GoodsBean.GoodsDetailsBean childBean32 = new GoodsBean.GoodsDetailsBean(R.drawable.product, "时尚背包 最新潮流 黑色", "¥ 550", "¥ 40", "9");
childBeanLists3.add(childBean3);
childBeanLists3.add(childBean31);
childBeanLists3.add(childBean32);
//最外层实体类
GoodsBean bean2 = new GoodsBean("a3", "天猫店铺", childBeanLists3);
groupLists.add(bean2);
List<GoodsBean.GoodsDetailsBean> childBeanLists4 = new ArrayList<>();
GoodsBean.GoodsDetailsBean childBean4 = new GoodsBean.GoodsDetailsBean(R.drawable.product, "时尚背包 最新潮流 黑色", "¥ 1200", "¥ 900", "3");
GoodsBean.GoodsDetailsBean childBean41 = new GoodsBean.GoodsDetailsBean(R.drawable.product, "时尚背包 最新潮流 黑色", "¥ 1600", "¥ 1000", "2");
GoodsBean.GoodsDetailsBean childBean42 = new GoodsBean.GoodsDetailsBean(R.drawable.product, "时尚背包 最新潮流 黑色", "¥ 1500", "¥ 600", "9");
GoodsBean.GoodsDetailsBean childBean43 = new GoodsBean.GoodsDetailsBean(R.drawable.product, "时尚背包 最新潮流 黑色", "¥ 888", "¥ 666", "5");
childBeanLists4.add(childBean4);
childBeanLists4.add(childBean41);
childBeanLists4.add(childBean42);
childBeanLists4.add(childBean43);
//最外层实体类
GoodsBean bean3 = new GoodsBean("a4", "苏宁易购店铺", childBeanLists4);
groupLists.add(bean3);
}
@Override
public void isSelectedAll(boolean flag) {
if(flag){
// true 全选
ivCheckAll.setImageResource(R.drawable.check_selected);
}else{
// false 取消全选
ivCheckAll.setImageResource(R.drawable.check_default);
}
}
}
小插曲,正如题目所述,此demo中还用到了大家经常使用的dialog弹窗,为什么要单独拿出来说呢,是因为仿照了系统的AlertDialog的源码,采用Builder的方式实现,由于在Builder中我们所写的方法返回的都是this,所以调用起来就比较爽了,一路小点的链式结构。
/**
* 自定义dialog builder模式
*/
public class CustomDialog extends Dialog{
public CustomDialog(Context context) {
super(context, R.style.myDialog);
}
public static class Builder{
private Context context;
private String content;//内容
private String leftCancle;//左边取消按钮显示的文字
private String rightSure;//右边确定按钮显示的文字
//控件
private TextView tvContent;
private Button btnLeft;
private Button btnRight;
//设置监听事件
private View.OnClickListener leftListener;
private View.OnClickListener rightListener;
public Builder(Context context) {
this.context = context;
}
public Builder setContent(String content){
this.content = content;
return this;
}
public Builder setLeftText(String leftText){
this.leftCancle = leftText;
return this;
}
public Builder setRightText(String rightText){
this.rightSure = rightText;
return this;
}
public Builder setLeftOnclick(View.OnClickListener leftListener){
this.leftListener = leftListener;
return this;
}
public Builder setRightOnClick(View.OnClickListener rightListener){
this.rightListener = rightListener;
return this;
}
public CustomDialog create(){
CustomDialog dialog = new CustomDialog(context);
View view = LayoutInflater.from(context).inflate(R.layout.dialog_layout, null);
tvContent = (TextView) view.findViewById(R.id.tvContent);
btnLeft = (Button) view.findViewById(R.id.btnLeft);
btnRight = (Button) view.findViewById(R.id.btnRight);
if(!TextUtils.isEmpty(content)){
tvContent.setText(content);
}
if(!TextUtils.isEmpty(leftCancle)){
btnLeft.setText(leftCancle);
}
if(!TextUtils.isEmpty(rightSure)){
btnRight.setText(rightSure);
}
if(leftListener != null){
btnLeft.setOnClickListener(leftListener);
}
if(rightListener != null){
btnRight.setOnClickListener(rightListener);
}
//根据屏幕宽来设置dialog的宽高
Window window = dialog.getWindow();
window.getDecorView().setPadding(0, 0, 0, 0);
WindowManager.LayoutParams params = window.getAttributes();
//获取手机屏幕宽度
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
params.width = (int) (metrics.widthPixels * 0.8);
window.setAttributes(params);
window.setGravity(Gravity.CENTER);
//将布局加载到dialog上
dialog.setContentView(view);
dialog.setCanceledOnTouchOutside(true);
return dialog;
}
}
}
调用起来也很优雅
//弹窗 dialog
dialog = new CustomDialog.Builder(context)
.setContent("你确定要删除该商品吗?")
.setLeftText("容朕三思")
.setRightText("朕意已决")
.setLeftOnclick(new View.OnClickListener() {
@Override
public void onClick(View v) {
dialog.dismiss();
}
})
.setRightOnClick(new View.OnClickListener() {
@Override
public void onClick(View v) {
dialog.dismiss();
}
})
.create();
dialog.show();