1. 程式人生 > >Android問題集(2):notifyDataSetChanged()方法無用

Android問題集(2):notifyDataSetChanged()方法無用

問題描述

話說,我接了坑同事的程式碼,據說這是同事接的前同事的程式碼…..不管怎麼說,遇到了一個bug,很容易解決,但原理一直搞不明白,於是網路搜尋,沒搜到結果,但發現了一個類似卻又不同的問題,寫程式碼重現了之後,準備記錄與此。
在使用ListView時,不可避免地要為它設定adapter,並且要為adapter設定資料,那麼就很容易出現該問題,先寫個錯誤程式碼,大家high一下。

問題程式碼

佈局不貼了,下面會貼一張程式碼的執行圖,一看便知。先來看看activity的onCreate方法,如下

ListView mListView;
ArrayAdapter<String> adapter;

@Override
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_adapter_bug); findViewById(R.id.tv_type_1).setOnClickListener(this); findViewById(R.id.tv_type_2).setOnClickListener(this); findViewById(R.id.tv_type_refresh).setOnClickListener(this
); mListView = (ListView) findViewById(R.id.lv_adapter_bug); getData(1); adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, mDatas); mListView.setAdapter(adapter); }

很簡單,有沒有,很單純地為ListView設定了一個adapter,其中涉及到了一個方法,getData(),如下

List<String> mDatas = new
ArrayList<>(); private void getData(int type) { mDatas = new ArrayList<>(); for (int i = 0; i < 10; i++) { if (type == 1) { mDatas.add("--------aaaaaaa" + i); } else { mDatas.add("--------bbbbbbb" + i); } } }

還是很簡單,對不,我們再來看點選事件

@Override
public void onClick(View v) {
    switch (v.getId()) {
        case R.id.tv_type_1:
            getData(1);
            break;

        case R.id.tv_type_2:
            getData(2);
            break;

        case R.id.tv_type_refresh:
            adapter.notifyDataSetChanged();
            break;
    }

}

就是這個樣子,貌似沒啥問題,是不,好吧,我們來看下執行效果,
這裡寫圖片描述

我去,你到底幹了啥,為啥沒啥變化,你操作了嗎?腦袋中有沒有一系列問號,來來來,我們看個正確的執行結果。
這裡寫圖片描述

嗯,貌似看出點不對了,那麼,為什麼我呼叫了notifyDataSetChanged()方法,卻沒有重新整理資料呢?
彆著急,聽我慢慢道來。

問題解析

假設,程式的onCreate()方法已經執行過,之後,我們按步驟來分析
第一步:點選type=2的按鈕
這個方法做了啥,就呼叫了getData(2)方法,我們來看看getData()方法的關鍵程式碼,也就是第一句,

List<String> mDatas = new ArrayList<>();

private void getData(int type) {
    mDatas = new ArrayList<>();
    ...
}

我們重新建立了一個物件,將它指向名為mDatas的引用,然後就是為集合新增元素。
第一步完成。第二步走起!

第二步:點選中間的按鈕,重新整理,程式碼很簡單就是一句

adapter.notifyDataSetChanged();

結束了,沒毛病啊,我點選重新整理了,你為毛不給我刷啊?!!
年輕人,別衝動,電腦是不會騙人的,我們來分析一下adapter的構造方法,在onCreate方法中

adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, mDatas);

最後一個引數,將mDatas傳進去了,之後ListView的原始碼會呼叫adapter的方法,獲取資料,設定資料,都是mDatas這個物件
mDatas這個物件!!!
看到沒,問題就在這裡,呼叫的是名為mDatas的物件。當我們點選type=2的按鈕時,重新建立了一個物件,指向了mDatas,也就是說,現在另一個物件叫mDatas了,而傳入了adapter中的那個集合物件,現在沒有名字了,但是,它還在記憶體中,並沒有消失,僅僅是沒有名字罷了,匿名啊,兄弟!
所以,任憑你怎麼呼叫adapter的notifyDataSetChanged()方法也沒什麼用。

問題解決

原因搞清楚了,解決就很簡單了。而且方式有很多,比如我們改下getData()方法

List<String> mDatas = new ArrayList<>();

private void getData(int type) {
//    mDatas = new ArrayList<>();
    mDatas.clear();
    for (int i = 0; i < 10; i++) {
        if (type == 1) {
            mDatas.add("--------aaaaaaa" + i);
        } else {
            mDatas.add("--------bbbbbbb" + i);
        }
    }

}

不再重建mDatas,每次清空集合即可。
下面是另一種,在點選事件中

adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, mDatas);
mListView.setAdapter(adapter);
//    adapter.notifyDataSetChanged();

還有第三種方法,不過在這個demo中無法使用,比如你使用的是BaseAdapter的子類,那麼就可以新增一個方法,setData(),具體如下

class Adapter extends BaseAdapter{

    Context context;
    List<String> list;
    public Adapter(Context context,List<String> list){
        this.context = context;
        this.list = list;
    }

    public void setData(List<String> list){
        this.list = list;
        notifyDataSetChanged();
    }

    @Override
    public int getCount() {
        return list.size();
    }
     @Override
    public Object getItem(int position) {
        return null;
    }

    @Override
    public long getItemId(int position) {
        return 0;
    }
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View view = LayoutInflater.from(context).inflate(android.R.layout.simple_list_item_1, null, false);
        TextView textView = (TextView) view.findViewById(android.R.id.text1);

        String text = list.get(position);
        textView.setText(text);
        return view;
    }
}

在點選事件中,呼叫方法如下

adapter.setData(mDatas);

之所以寫出這種寫法,是之前的同事很多都用這種寫法,自己雖然不習慣這種寫法,但還是寫出來,供大家參考一下。

總結

簡單一句話,這個問題不是移動端問題(我實在受不了紅色的那個啥,所以用移動端代替),是個物件問題…