商城購物車加減控制元件的簡單封裝(續),解決ListView中資料錯亂的問題

四級五次郎發表於2017-08-17

在上一篇文章中,我們學習了商城購物車加減控制元件的簡單封裝,知道了封裝的思路過程和使用方法。還沒有看過上一篇文章的朋友,建議先去閱讀 商城購物車加減控制元件的簡單封裝 。這段時間收到一些小夥伴的反饋,在ListView或者是RecyclerView中存在item複用導致資料錯亂的問題,這篇文章就重點解決item複用導致資料錯亂的問題和在ListView或者RecyclerView中的用法。下面為了方便我們以ListView為例(RecyclerView是一樣的)。

1. 先看下效果圖

效果圖:

這裡寫圖片描述
這裡寫圖片描述

Github地址:github.com/Jmengfei/Ad…

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。

Github地址:github.com/Jmengfei/Ad…

csdn地址:blog.csdn.net/sinat_36668…

相關文章