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);
之所以寫出這種寫法,是之前的同事很多都用這種寫法,自己雖然不習慣這種寫法,但還是寫出來,供大家參考一下。
總結
簡單一句話,這個問題不是移動端問題(我實在受不了紅色的那個啥,所以用移動端代替),是個物件問題…