商城购物车加减控件的简单封装(续),解决ListView中数据错乱的问题

在上一篇文章中,我们学习了商城购物车加减控件的简单封装,知道了封装的思路过程和使用方法。还没有看过上一篇文章的朋友,建议先去阅读 商城购物车加减控件的简单封装 。这段时间收到一些小伙伴的反馈,在ListView或者是RecyclerView中存在item复用导致数据错乱的问题,这篇文章就重点解决item复用导致数据错乱的问题和在ListView或者RecyclerView中的用法。下面为了方便我们以ListView为例(RecyclerView是一样的)。

1. 先看下效果图

效果图:

这里写图片描述

Github地址:https://github.com/Jmengfei/AddSubUtils

2. 为什么复用会导致数据错乱?

有过开发经验的人都知道ListView的缓存复用机制虽然提升了它的性能,但是同样也带来了其他问题,复用item导致数据错乱就是其中一个。在这里我们不讨论为什么会这样。因为如果展开说这个问题的话就失去了本文章的重点。这里主要说下解决方法的思路。

3. 解决思路

解决这个问题的思路很多,网上一般给出这两种办法

  • 上来就说是因为convertview对象共用的原因,不能用convetView,而是每次getView()的时候都new一个对象的view出来.这种办法大概是用屁股想出来的.

  • 即然错乱,那我就自己再弄一个集合保存checkBox的状态,再错乱,弄死你.即然adapter里有一个list集合里保存checkBox的状态了,为什么还要自己再保存一次checkBox的状态呢,不是多此一举吗?

这里我们先看一个例子

这里写图片描述

这是再正常不过的例子了,item布局非常简单。点击item条目改变CheckBox的选中状态。看下adapter中getView()的代码

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        final ViewHolder holder;
        if(convertView == null) {
            convertView = View.inflate(mContext, R.layout.lv_item,null);
            holder = new ViewHolder(convertView);
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        holder.tvItem.setText(datas.get(position));
        // 点击事件
        holder.rlItem.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                holder.cbItem.setChecked(!holder.cbItem.isChecked());
            }
        });
        return convertView;
    }

现在咱们修改datas数据bean的数据,增加一个控制checkbox的字段

public class ListBean {
    // 为了便于组装数据
    public ListBean(String title, boolean itemChecked) {
        this.title = title;
        this.itemChecked = itemChecked;
    }
    private String title;
    private boolean itemChecked; // 新增字段控制是否被点击,并设置get,set方法

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public boolean isItemChecked() {
        return itemChecked;
    }

    public void setItemChecked(boolean itemChecked) {
        this.itemChecked = itemChecked;
    }
}

现在咱们利用bean中的属性进行控制checkbox的选中状态,现在再来看getView()中代码

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        final ViewHolder holder;
        if(convertView == null) {
            convertView = View.inflate(mContext, R.layout.lv_item,null);
            holder = new ViewHolder(convertView);
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        ListBean listBean = datas.get(position);
        holder.tvItem.setText(listBean.getTitle());
        holder.cbItem.setChecked(listBean.isItemChecked());
        
        // 点击事件
        holder.rlItem.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                datas.get(position).setItemChecked(!(listBean.isItemChecked()));
                notifyDataSetChanged();
            }
        });
        
        holder.cbItem.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                boolean ischecked = holder.cbItem.isChecked();
                datas.get(position).setItemChecked(ischecked);
            }
        });
        return convertView;
    }

现在我们再次运行起来,发现已经解决了那个问题。

这里写图片描述

思路很简单,就是利用集合数据中新增字段itemChecked来控制,当我们点击的时候要把checkbox的属性也同步到数据集合中。然后利用数据集合中的itemChecked来设置checkbox的状态。记住一点,同步集合中的状态是解决的关键点。

4. 项目改版

对于项目中,复用的是item中的当前数量,咱们正好是要通过数量来控制数据,所以不用更改集合样式,这简直太棒了有没有?对于外层,咱们需要知道当前输入框的值,怎么时时获取呢,这里我通过接口的方式来传递数据,而且为了适应不在ListView中的情况,我这里重新声明了一个接口,代码如下:

    // 当数量改变的接口
    public interface OnChangeValueListener {

        void onChangeValue(int value,int position);
    }

咱们在输入框数量改变的监听函数里面来调用:

    /**
     * 监听输入的数据变化
     */
    private void onNumberInput() {
        //当前数量
        int count = getNumber();
        if (count < mBuyMin) {
            //手动输入
            inputValue = mBuyMin;
            etInput.setText(inputValue + "");
            if(mOnChangeValueListener != null) {
                mOnChangeValueListener.onChangeValue(inputValue,mPosition);
            }
            return;
        }
        int limit = Math.min(mBuyMax, inventory);
        if (count > limit) {
            if (inventory < mBuyMax) {
                //库存不足
                warningForInventory();
            } else {
                //超过最大购买数
                warningForBuyMax();
            }
        }else{
           inputValue = count;
            if(mOnChangeValueListener != null) {
                mOnChangeValueListener.onChangeValue(inputValue,mPosition);
            }
        }
    }

这里有人会问?为什么要传一个position过来,这个position又是什么呢?

大家都知道数据的复用是因为convertView的复用导致了position位置的数据也跟着乱了,假如我知道这个位置,然后用这个位置的数据来赋值给该位置的数据的话,不就解决了,这里就是利用这一点。我们新加入一个字段,并且实现get、set方法,注意set方法返回该类的实例(这样做是为了链式调用),代码如下:

 private int mPosition = 0; // 设置改变的位置,默认是0; //集合数据中会用到
 
 public AddSubUtils setPosition(int position) {
        mPosition = position;
        return this;
    }
    public int getPosition() {
        return mPosition;
    }

到这里库中的代码已经修改完毕了,现在咱们看看怎么使用

  1. 首先你需要在build.gradle中添加引用:
dependencies {
 compile 'com.mengfei:AddSubUtils:1.5.0'
}
  1. 在ListView中Adapter中的getView()方法中:
        @Override
        public View getView( int position, View convertView, ViewGroup parent) {
            ViewHolder holder;
            if (convertView == null) {
                convertView = View.inflate(ListViewActivity.this, R.layout.listview_item, null);
                holder = new ViewHolder(convertView);
                convertView.setTag(holder);
            } else {
                holder = (ViewHolder) convertView.getTag();
            }
            AddSubBean dataBean = mList.get(position);
            holder.tv_item.setText(dataBean.getName());
            Glide.with(ListViewActivity.this).load(dataBean.getImageId()).into(holder.iv_item);

            holder.list_item_utils
                    .setStep(1)
                    .setBuyMax(30)
                    .setPosition(position)    // 传入当前位置,一定要传,不然数据会错乱
                    .setCurrentNumber(dataBean.getValue())
                    .setInventory(50)
                    .setOnWarnListener(new AddSubUtils.OnWarnListener() {
                        @Override
                        public void onWarningForInventory(int inventory) {
                            Toast.makeText(ListViewActivity.this, "不能超过当前库存:" + inventory, Toast.LENGTH_SHORT).show();
                        }

                        @Override
                        public void onWarningForBuyMax(int max) {
                            Toast.makeText(ListViewActivity.this, "超过最大购买数:" + max, Toast.LENGTH_SHORT).show();
                        }

                        @Override
                        public void onWarningForBuyMin(int min) {
                            Toast.makeText(ListViewActivity.this, "低于最小购买数:" + min, Toast.LENGTH_SHORT).show();
                        }
                    })
                    .setOnChangeValueListener(new AddSubUtils.OnChangeValueListener() {
                        @Override
                        public void onChangeValue(int value,int position) {
                            setValue(position,value);    // 使用传回来的position设置数据
                        }
                    });
            return convertView;
        }
    }

代码里的两处标注是重点。当然,如果你不在ListView中使用,那就还和之前一样,是不用传入position的,也不用实现AddSubUtils.OnChangeValueListener接口。setValue()方法就简单了:

    private void setValue(final int position, int inputValue) {
        mList.get(position).setValue(inputValue);
    }

到这里你已经解决了在ListView中item复用导致数据错乱的问题,当然,RecyclerView同样适用,这里我就不再给出。是不是用起来很简单?如果你还在为商城购物车加减控件的各种问题而烦恼,那么你不妨试一下这个库,可以帮助你快速的完成这块的工作。需要源码的请到Github上下载,AddSubUtils ,你如果还有其他的需求,欢迎留言,我们共同改进。如果觉得对你有帮助,欢迎star。

完整效果图

addsubutils_all.gif

Github地址:https://github.com/Jmengfei/AddSubUtils

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容